Feature: Mode-agnostic transact tool + firmware console ergonomics #1

Closed
opened 2026-02-12 17:32:21 +00:00 by rsp2k · 0 comments
Owner

Feature: Mode-agnostic transact tool + firmware console ergonomics

Context

Spent a long session reverse-engineering Winegard Carryout G2 satellite dish firmware via RS-422 serial console (115200 baud, prompt-terminated responses). The firmware has 12+ submenus, each with dozens of commands returning prompt-terminated responses (> character). mcserial worked great for getting connected and doing basic I/O, but the workflow for interactive firmware consoles has significant friction.

Problem

The main pain point: every single command requires 3-5 tool calls when it should require 1.

Current workflow for every firmware command:

write_serial(port, "rssi 5\r")     # send command
read_serial(port, size=2048, timeout=3)  # hope we get enough data
# ...often need another read_serial because response was split...
# ...sometimes need flush_serial to drain a streaming command...
# ...then read_serial again to verify we got the prompt back...

What I want:

transact(port, data="rssi 5\r", response_terminator=">", timeout=5)
# → returns everything between echo and ">", done.

rs485_transact is exactly the right API for this — it has data, response_terminator, response_timeout, all perfect. But it's gated behind _require_mode(port, "rs485") (server.py:1602), so it can't be used on RS-232/RS-422 connections. The actual send→wait→read logic (lines 1636-1648) is completely mode-agnostic.

Proposed Changes (ranked by impact)

1. Mode-agnostic transact tool

New tool that works in any mode. Basically rs485_transact minus the RTS toggling:

@mcp.tool()
def transact(
    port: str,
    data: str,
    response_terminator: str | None = None,
    response_timeout: float = 1.0,
    response_length: int | None = None,
    line_ending: str | None = "\r\n",  # auto-appended to data
    strip_echo: bool = False,  # remove echoed command from response
    encoding: str = "utf-8",
) -> dict: ...
  • line_ending: Auto-append to sent data (firmware consoles typically need \r)
  • strip_echo: Most serial devices echo back the command — strip it from the response
  • Works in RS-232, RS-422, and RS-485 modes

The existing rs485_transact could become a thin wrapper that adds RTS control around transact.

2. timeout parameter on read_until

read_until has terminator and size but no per-call timeout override. When exploring unknown firmware, some commands respond instantly and others are streaming. Currently inherits port default which may not be right.

3. Configurable default line ending per port

A line_ending setting in configure_serial that auto-appends to write_serial calls. Would eliminate the manual \r / \n appending on every write.

4. Issue reporting discoverability

The MCP server description and/or a dedicated tool could tell users where to file issues. Something like:

@mcp.tool()
def server_info() -> dict:
    """Return server version, capabilities, and issue tracker URL."""
    return {
        "version": __version__,
        "issues": "https://git.supported.systems/rsp2k/mcserial/issues",
        ...
    }

Or just add the repo/issues URL to the server description that MCP clients see.

Impact

In a ~4 hour firmware RE session, I made roughly 200+ serial tool calls. A transact tool would have reduced that to ~60-80. The compound effect of 3-5x fewer tool calls per command is massive for context window usage and session productivity.

Environment

  • mcserial 2026.2.5 via uvx mcserial
  • Connection: USB RS-422 adapter → Winegard G2 @ 115200 8N1
  • Firmware: prompt-terminated responses (> = ASCII 62)
  • OS: Arch Linux 6.16.5
# Feature: Mode-agnostic `transact` tool + firmware console ergonomics ## Context Spent a long session reverse-engineering Winegard Carryout G2 satellite dish firmware via RS-422 serial console (115200 baud, prompt-terminated responses). The firmware has 12+ submenus, each with dozens of commands returning prompt-terminated responses (`>` character). mcserial worked great for getting connected and doing basic I/O, but the workflow for interactive firmware consoles has significant friction. ## Problem The main pain point: **every single command requires 3-5 tool calls** when it should require 1. Current workflow for every firmware command: ``` write_serial(port, "rssi 5\r") # send command read_serial(port, size=2048, timeout=3) # hope we get enough data # ...often need another read_serial because response was split... # ...sometimes need flush_serial to drain a streaming command... # ...then read_serial again to verify we got the prompt back... ``` What I want: ``` transact(port, data="rssi 5\r", response_terminator=">", timeout=5) # → returns everything between echo and ">", done. ``` `rs485_transact` is *exactly* the right API for this — it has `data`, `response_terminator`, `response_timeout`, all perfect. But it's gated behind `_require_mode(port, "rs485")` (server.py:1602), so it can't be used on RS-232/RS-422 connections. The actual send→wait→read logic (lines 1636-1648) is completely mode-agnostic. ## Proposed Changes (ranked by impact) ### 1. Mode-agnostic `transact` tool New tool that works in any mode. Basically `rs485_transact` minus the RTS toggling: ```python @mcp.tool() def transact( port: str, data: str, response_terminator: str | None = None, response_timeout: float = 1.0, response_length: int | None = None, line_ending: str | None = "\r\n", # auto-appended to data strip_echo: bool = False, # remove echoed command from response encoding: str = "utf-8", ) -> dict: ... ``` - `line_ending`: Auto-append to sent data (firmware consoles typically need `\r`) - `strip_echo`: Most serial devices echo back the command — strip it from the response - Works in RS-232, RS-422, and RS-485 modes The existing `rs485_transact` could become a thin wrapper that adds RTS control around `transact`. ### 2. `timeout` parameter on `read_until` `read_until` has `terminator` and `size` but no per-call `timeout` override. When exploring unknown firmware, some commands respond instantly and others are streaming. Currently inherits port default which may not be right. ### 3. Configurable default line ending per port A `line_ending` setting in `configure_serial` that auto-appends to `write_serial` calls. Would eliminate the manual `\r` / `\n` appending on every write. ### 4. Issue reporting discoverability The MCP server description and/or a dedicated tool could tell users where to file issues. Something like: ```python @mcp.tool() def server_info() -> dict: """Return server version, capabilities, and issue tracker URL.""" return { "version": __version__, "issues": "https://git.supported.systems/rsp2k/mcserial/issues", ... } ``` Or just add the repo/issues URL to the server description that MCP clients see. ## Impact In a ~4 hour firmware RE session, I made roughly 200+ serial tool calls. A `transact` tool would have reduced that to ~60-80. The compound effect of 3-5x fewer tool calls per command is massive for context window usage and session productivity. ## Environment - mcserial 2026.2.5 via `uvx mcserial` - Connection: USB RS-422 adapter → Winegard G2 @ 115200 8N1 - Firmware: prompt-terminated responses (`>` = ASCII 62) - OS: Arch Linux 6.16.5
rsp2k closed this issue 2026-02-12 17:52:51 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rsp2k/mcserial#1
No description provided.