MCP server (experimental)¶
Terms: MCP (Model Context Protocol) lets an agent call named tools; stdio is the process's standard input/output; a capture epoch identifies one serial supervisor lifetime. See the glossary.
panio mcp serves panio's runtime commands as
Model Context Protocol tools over stdio. An
agent harness that speaks MCP can then drive a target without a shell. For
example, a screenshot comes back as an image in the tool result instead of a
file the agent has to open.
It is experimental: the tool set covers looking at a target (screen, screen
text, serial log, power state) and acting on it (keyboard and mouse, serial
input, power). Configuration stays in the CLI. The design is in
notes/mcp-server.md.
Set it up¶
Run it on the machine that holds your lab file, usually your own machine, not a control host. Tools that touch a remote target dispatch over SSH exactly as the CLI does.
Claude Code:
claude mcp add panio -- panio mcp
# a lab file other than the default:
claude mcp add panio -- panio --lab ~/lab.toml mcp
Any other harness: configure a stdio server whose command is panio with
arguments ["mcp"]. In the common JSON form:
The harness starts the server and keeps it running for the session. The server holds no state of its own, so restarting it loses nothing.
Tools¶
| Tool | Arguments | Returns |
|---|---|---|
target_list |
none | JSON: each target's name, description, hosts, and channels (kind, name, host). |
video_shot |
target (required); changed_since (a hash); stable (bool); timeout_ms (0–120000, default 2000) |
The screen as a PNG image, plus a text line signal=<s> hash=<h>. |
video_read |
target; stable; timeout_ms |
The text on the screen (OCR), as panio video read. |
serial_status |
target; interface |
Read-only JSON: configured/active identity, process and port state, capture epoch and RX/TX progress. Missing evidence is unknown; none proves target command execution. |
serial_wait |
target; epoch and since (required); interface; pattern; timeout_ms (0–3600000); max_bytes (1–1048576); max_output_bytes (4096–8388608); include_pending; follow |
Bounded fresh raw evidence with timing and explicit matched/timeout/cap/disconnect/fault status. Accepted bytes survive incomplete waits. Follow objects are returned together when the call finishes. |
serial_evidence |
target; interface; epoch; since (requires epoch); max_bytes (1–1048576, default 65536) |
Read-only JSON: immutable exact-byte receive records, original RX/capture times, observation time, epoch/generation, retained cursors and cap/gap/fault diagnostics. See receive evidence. |
serial_log |
target; interface; a window: tail (≤ 5000), since, from, to (sequence numbers) |
Console lines [time] #<seq> <text>, as panio serial log; the last 200 when no window is given. |
serial_send |
target; text (required); interface; newline (default true); pace_ms (≤ 10000) |
Writes the text to the console, as panio serial send. |
serial_transact |
target; text and until (required); interface; timeout_ms (1–3600000, default 30000); max_bytes (1–1048576, default 65536); newline (default true); pace_ms (≤ 10000) |
Sends the text and waits for the reply under the interface's transaction lock, as panio serial transact: the serial_wait result JSON for the bytes received after the send, plus sent_bytes. See transactions. |
hid_send |
target; command (required): one HID command as an array of words, e.g. ["key", "ENTER"] |
Injects keyboard or mouse input, as panio hid send. |
action_list |
target |
JSON array of the target's named actions (name, description, requires_power, host), as panio action list --json. See Named actions. |
action_run |
target; name (required) |
Runs one named action, as panio action run. |
power_state |
target; level |
A line like Power ON (pi5), as panio power-state. With no level, a target with power levels gets one line per level, the default first (Power OFF (pi5, level hard); Power UNKNOWN … no state_cmd for a level that cannot be read). With level, only that level. |
power_on, power_off, power_cycle |
target; level |
Switches power, as panio power on/off and panio power-cycle, with --level when level is given. A level without the needed hook fails as not_configured (3); it never falls back to the default level. |
Every tool but target_list needs target. The tools that change the target
(serial_send, serial_transact, hid_send, action_run, power_on, power_off,
power_cycle) are
marked as such, so a harness that asks before side effects will ask.
video_shot needs the target's video daemon to be running already
(panio video watch <target>). To wait for the screen to change after an
action, pass the previous result's hash as changed_since, the same loop the
kvm-puppeting skill teaches with video shot --changed-since.
A tool that runs and fails returns a result with isError: true. Its text
gives panio's exit code, the error message, and the one-line JSON error
object described in Exit status and errors. When a tool that
changes the target fails with unreachable (4) or timeout (22), the text
also says the outcome is unknown: the action may have happened, so check the
target's state before repeating it. A malformed call (unknown tool, missing
target) is a JSON-RPC error instead.
How it behaves¶
- stdout is the protocol. Each tool runs the matching
paniocommand as a child process with stdin closed and stdout captured, so nothing a command prints can corrupt the stream. Diagnostics go to stderr, which harnesses usually log. - One call at a time.
serial_waitandserial_transactcan block other calls up to their finite deadline (default 30 seconds, maximum one hour); use a deadline appropriate for the task. Avideo_shotwaiting onchanged_sinceblocks other calls until it returns, for at mosttimeout_ms; so does a slowpower_cycle. - Dead links fail, not hang. A remote call over an SSH connection that has
gone silently dead fails as
unreachablewithin about 15 seconds; see Distributed control. - MCP protocol revisions 2024-11-05 through 2025-11-25 are accepted.