A · The tools at a glance
One server provides thirty tools and can hold open as many ports as the shack has cables. You say what you want in plain language, and the agent picks the tool. None of this is tied to telecom. If a device has a serial port and speaks raw bytes, ANSI or VT100, the same tools drive it.
| Tools | What they do | When the agent reaches for them |
|---|---|---|
| list_serial_ports · list_presets | Every port the computer sees, marked if it is open here. The usual settings for each device family. | First, whenever a port or its settings are unknown. |
| connect · reconnect_last · disconnect | Open a port by name with baud, framing, flow control, line ending and prompt, or from a preset. Several at once. Remembered across sessions. | "Connect the Kenwood as rig and the rotator on the other port." |
| send_text · send_hex | Write an ASCII line or raw bytes. These only write, and never read. | Any command, login, bare return, CI-V frame. |
| read_until_prompt · read_available · query_text | Return buffered output up to a prompt (leaving the rest), or whatever has arrived. query_text clears, sends and reads in one step. | Every reply. |
| expect | A scripted list of send-and-wait steps in one call, with automatic replies to pagers. | Logins, multi-step command sequences. |
| send_keys · screen | Press Ctrl-C, Esc, Tab, arrows and F-keys by name. View the VT100/xterm screen of a full-screen console. | "Interrupt that ping" · BIOS, BMC and menu consoles. |
| set_lines · pulse_line · send_break | Drive DTR and RTS, and send BREAK. status shows CTS, DSR, CD and RI. | Key a rig's PTT, reset an Arduino, interrupt a boot. |
| capture_start · capture_stop · get_transcript | Log a session to a file, raw or timestamped. Re-read the rolling transcript. | "Record this console session." |
| port_in_use_by · detect_baud | Show which program holds a port, and which baud rate produces readable text. | "It says in use" · "I do not know the rate." |
| cat_build · cat_parse | Kenwood, Elecraft and Yaesu ; commands: build a frequency set, decode ID, FA, MD, IF and error replies. | Any text-CAT rig. |
| civ_build · civ_parse · civ_freq | Icom CI-V frames: build, decode (echo versus reply, BCD frequency, mode, PTT), convert. | Any Icom. |
| rotator_build · rotator_parse | GS-232 commands (read, move, stop, speed) and position replies. | Any Yaesu-protocol rotator. |
| clear_buffer · status | Housekeeping. status shows every open port, its lines, buffer and capture. | Pre-flight; after anything odd. |
The agent reads the tool descriptions. This guide is for the human at the bench. It explains what the agent will do for you, which words get it to do the right thing, and how to tell a wrong baud rate from a dead device when a port is silent.
B · Installing
The server runs on your own computer, started by Claude Desktop, so it can see /dev/cu.*, COM4 or /dev/ttyUSB0.
As a desktop extension (recommended)
- Download the bundle. Get
serial-console-mcp.mcpbfrom Downloads. The same file works on Mac, Windows and Linux. - Install it. In Claude Desktop, go to Settings → Extensions → Advanced settings → Install Extension and choose the file. You do not need a terminal or a Python install.
- Settings, if you want them. The form offers read-only mode and a timeout that closes a forgotten port. Both are off unless you turn them on.
From PyPI (uv or pipx)
uvx serial-console-mcp configure --command uvx --arg serial-console-mcp
# or
pipx install serial-console-mcp
serial-console-mcp configure --command "$(which serial-console-mcp)"
This needs Python 3.10 or newer. configure writes the Claude Desktop entry. It keeps the servers already listed there and backs up the old file. serial-console-mcp configure --remove undoes it. Any MCP client can launch the same command over stdio. Developers: git clone, uv sync, uv run pytest.
Check that it works (thirty seconds)
- Ask for the ports. "What serial ports do you see?" A list with USB descriptions means the server is running and the OS sees your adapter. "No serial ports found" with the device plugged in almost always means a charge-only USB cable or a missing driver (FTDI, CP210x, CH340).
- Open and close a port. "Connect to the first one, show me the status, then disconnect." Status should say the reader is running with zero bytes buffered.
A serial port can be open in only one program at a time. If a terminal, WSJT-X, a contest logger, the Arduino Serial Monitor or the device's own software holds it, the connect fails with "already in use". Say "disconnect" before you hand the port to something else.
C · Your first session
You do not need to know a terminal program. If you can plug in a cable and describe your gear, you can operate it.
What you are installing
Claude is an assistant made by Anthropic. It runs in the Claude Desktop app. Think of serial-console-mcp as a rig-interface cable with a patient operator on the other end. Claude types what you ask into the port, watches what comes back, and tells you. Nothing about your device changes. It sees an ordinary terminal. The cable does not care what is on the other end. A switch, a radio, an alarm panel, an IoT gateway and a lab instrument all look the same to it.
Operating is a conversation
| You type | What happens |
|---|---|
| What serial ports do you see? | Every port with its USB description, so you can pick the CP210x or FTDI that is your device. |
| Connect to /dev/cu.usbserial-10 at 9600. | Opens the port 9600 8N1 with no flow control and starts the background reader. |
| Connect to /dev/cu.BLTH at 38400 with XON/XOFF. | Same, with software flow control on. Every field of a terminal's Quick Connect dialog can be said this way (chapter 01). |
| Send a return and read until the login prompt. | Writes a bare CR, then reads until login: appears. |
| Log in as admin and run show version. Show me all of it. | Sends the username, waits for the password prompt, sends the password, waits for the shell prompt, runs the command, reads until the prompt again. Long output comes back whole. |
| Reconnect to the same port as last time. | The last port and settings are remembered across sessions. |
| Disconnect. | Closes the port so a terminal program or WSJT-X can have it. |
These tools send what you ask, unchanged, to whatever is on the cable. Claude Desktop asks you to approve each tool call, so you see every command before it runs. Read it. A console session on a router or a rig can reconfigure, reboot, or transmit, and this server does not try to guess which commands are dangerous. If something looks wrong, decline it. Keep a real terminal handy for anything you would not want an assistant to type.
Start with a read-only command, such as show version on a router, ID; on a Kenwood, or a frequency read on an Icom. Watch one full cycle of send, prompt and output before you ask for anything that changes state.
D · How the system works
A serial console does not work in neat question-and-answer pairs. It echoes what you type, prints things on its own, and can dump pages of output. So the server never guesses when a reply is done. It keeps reading, and you tell it what the end of a reply looks like.
- The reader owns the port. One thread empties the OS buffer continuously. Echo, unsolicited syslog and a three-page
show runall arrive intact, with no ad-hoc polling racing to catch them. minicom and expect work the same way. - Reads end at the prompt, not on a timer.
read_until_promptreturns as soon as the prompt shows up. It hands back everything up to and including the prompt. A fixed delay would cut off long output or waste seconds on short output. Waiting for the prompt avoids both. - Leftover bytes are kept. Bytes after the matched prompt go back to the front of the buffer, so an unsolicited line that arrived mid-read is the first thing the next read sees.
- Timeouts return what they have. If the prompt never shows, you still get the partial output with a note to try again or change the prompt.
- A dead port is reported. If you unplug the adapter, the reader stops. Every tool then says so and asks you to reconnect, instead of timing out with no message.
- Memory use is limited. The buffer holds four megabytes. If a device streams for hours and nobody reads it, the oldest bytes are dropped, and
statusreports how many.
Tell the agent the prompt. If you say "The prompt is user@r1> with a trailing space", every read comes back clean and complete. Without it, the agent falls back to reading until the line goes quiet. That works for one-line CAT answers but fails for anything paged.
01 · Connection settings
You can say everything in a terminal program's Quick Connect dialog in one sentence. The default is 9600-8-N-1 with no flow control. Most consoles and much radio gear expect that, so name only what differs.
| Dialog field | Parameter | Default | Values | How to say it |
|---|---|---|---|---|
| Port | port | required | /dev/cu.usbserial-10 · COM4 · /dev/ttyUSB0 | "the CP210x one" works if you listed ports first |
| Baud rate | baud | 9600 | 1200 … 115200 | "at 38400" |
| Data bits | bytesize | 8 | 5 · 6 · 7 · 8 | "7 data bits" |
| Parity | parity | N | N · E · O | "even parity" |
| Stop bits | stopbits | 1 | 1 · 1.5 · 2 | "two stop bits" |
| RTS/CTS | rtscts | off | on · off | "with hardware flow control" |
| XON/XOFF | xonxoff | off | on · off | "with XON/XOFF" |
Shorthand works too. "38400 8N1", "9600 7E1" and "the usual Cisco console settings" all resolve to the right parameters. The agent repeats the settings back in the connect reply, so a misheard baud rate is visible before the first command goes out.
Presets
connect(preset="kenwood-cat") loads a family's usual settings. That includes two settings the dialog never asks for: the line ending the device expects, and the prompt that ends its replies. Anything you say explicitly overrides the preset. The presets are cisco-console, juniper-craft, linux-console, kenwood-cat, elecraft-cat, yaesu-cat, icom-civ, xiegu-civ, yaesu-rotator, arduino, nmea-gps, screen-console. Presets also choose the terminal mode (chapter 05). The console presets strip colors and escape sequences, and the radio presets pass raw bytes. Ask "list the presets" to see each one's values and caveats. The presets hold factory defaults. If the device's menu says something different, go with the menu.
If you do not know the rate at all, ask "What baud is this thing?". That runs detect_baud. It tries the common rates, sends a return (or a command you name, such as ID; for a Kenwood), and ranks the rates by how readable the reply is.
Flow control settings
- Leave both off unless the manual says otherwise. Almost all consoles, CAT ports and microcontrollers run without flow control.
- RTS/CTS needs the wires. Many console cables and three-wire adapters do not carry RTS and CTS. If you turn it on with such a cable, every write waits for a CTS that never comes. The server gives up after two seconds and tells you that is what happened.
- XON/XOFF is for text only. It treats the bytes 0x11 and 0x13 as flow-control characters and swallows them. Never enable it for Icom CI-V or any other binary protocol. A frequency that contains those bytes would be corrupted with no warning.
Remembered across sessions
Every successful connect saves the connection to a small file in your user profile. The file keeps the connection's name, all its settings, the preset and the prompt. "Reconnect to the rig" or "reconnect to the same port as last time" reads it back. status shows what is remembered when nothing is open.
The dialog does not ask for it, because a terminal sends whatever you type. Here it matters. send_text appends CR (most radios, rotators), LF (Unix-style consoles, Arduino), CRLF, or nothing (Kenwood-style CAT ends in ;). Say "this device wants LF" once. The line ending is stored on the connection, as the prompt is, so every later send and read uses it without being told.
02 · The console standard
The tools are built around six rules. Each rule avoids a trap that a naive send-and-wait approach falls into.
- Sending never reads.
send_textandsend_hexput bytes on the wire and return. Reading the reply is a separate step. That is how the agent can log in (send, wait forPassword:, send, wait for the shell) without guessing at delays. - Name the prompt with its trailing space. The match runs over everything received, including the echo of your own command.
"# "is safe. A bare"#"matches the first#in a banner or inshow run | include #. A regex such as[\w.-]+[#>] ?$handles hostnames that change afterconfigure. Set the prompt once onconnect(or let the preset set it), and every read uses it. If no prompt is set anywhere, the read ends when the received text ends in #, >, $ or %. - Clear before a command whose reply must be clean. Syslog, interface flaps and a previous timed-out read leave bytes in the buffer.
send_text(…, clear_buffer_first=true)orquery_textdiscard them so the next read starts at your echo. - Use text prompts for CLIs and idle reads for everything else. For routers, switches and shells, use
read_until_prompt. Binary protocols and one-line CAT answers have no prompt. For those, useread_available, orquery_textwith an empty prompt, which returns once the line has been quiet for a moment. - A wrong baud rate looks like garbage. A wrong line ending looks like silence. A stream of replacement and box characters means the baud rate is wrong. An echo with no reply means the device is waiting for the other line ending. Send a bare return first to get a fresh prompt. If that also produces nothing, check the cable.
- Disconnect before handing the port to anything else. The OS gives a port to one process. A forgotten open port is the most common reason WSJT-X or a terminal program cannot find a device that is clearly plugged in.
minicom and expect were written to deal with these same problems. The server builds the rules in, so the agent does not have to learn them again at your bench.
03 · The device playbook
Four device families have their own playbook. The last row covers anything else on a serial port. The settings below are the common factory defaults. When the device's manual or menu says otherwise, go with the device.
| Family | Typical settings | Line ending | Read with | Notes |
|---|---|---|---|---|
| Network craft and console ports (Juniper, Cisco, Arista, switches, firewalls) | 9600 8N1 | LF or CR | read_until_prompt: "login: ", "> ", "# " | Send a bare return to draw a prompt. Turn off paging once (set cli screen-length 0, terminal length 0) or --More-- becomes your prompt. |
| Text CAT radios (Kenwood, Elecraft, Yaesu FT-991 and FTdx, Flex) | 4800 to 115200 8N1, per the rig's menu | NONE | query_text (empty prompt, idle read) | Commands end in ; with no CR: ID; FA; MD;. Replies are one line ending in ;. cat_build formats a frequency set, cat_parse names the rig, mode and frequency. Elecraft echoes nothing. Kenwood may need AI0; to stop auto-info. |
| Icom CI-V (IC-7300, 7610, 705, 9700) | 19200 8N1 (USB), 9600 via the CI-V jack | n/a (binary) | send_hex → read_available | Frames are FE FE <rig> E0 <cmd> … FD. The reply shows as hex. Frequency is BCD, low byte first. Never enable XON/XOFF. If the rig echoes, the first frame back is your own. Xiegu G90 and X6100 speak CI-V too (address 0x70, the xiegu-civ preset). Baofeng-class handhelds have no CAT. Their cable carries a memory-programming protocol, which is a job for CHIRP, not a console. |
| Rotators, switches, MCUs (Yaesu GS-232, Green Heron, Arduino, ESP32) | 9600 or 115200 8N1 | CR (rotators), LF (Arduino) | read_available or read_until_prompt | GS-232 C returns +0xxx azimuth; rotator_build and rotator_parse cover read, move, stop and speed. Opening a port resets many Arduinos via DTR. Wait a second and expect a boot banner. Sketches that print continuously are a read_available job. |
| Everything else on a serial port (IoT gateways, alarm and access-control panels, UPS and PDU menus, lab instruments, industrial controllers) | 9600 8N1 to start; detect_baud if silent | CR or CRLF (try both) | read_until_prompt, read_available, screen | Choose the terminal mode by what the device prints. Use dumb for plain text or binary, ansi for colors and cursor codes, and xterm with screen for a full-screen menu that repaints. Binary protocols such as Modbus RTU go through send_hex and read_available. The server does not decode them. For panels that can arm, disarm, unlock or switch power, start in read-only mode and confirm every command. |
A CAT command can key the radio (TX;) and a console command can reboot a router. The server does not filter commands. The approval prompt is your filter. On a rig, start with reads (ID; FA; MD;) and put the radio on a dummy load before any command that could transmit.
04 · The shack: several ports, control lines, scripts, capture
This chapter covers what release 0.2 added on top of the console model. You can name ports, drive the lines that key and reset, script a login in one call, and keep a record of a session.
Several ports at once
Every connection has a name, as in "connect the Kenwood as rig, then the rotator on the other port as rotator". Tools act on the most recently used connection unless you say otherwise, as in "ask the rig for its frequency, then turn the rotator to 180". status lists all of them and marks the current one. "Disconnect everything" closes them all. Connecting to a port that is already open replaces that connection. Reusing a name for a different port is refused, so rig can never silently become the amplifier.
Control lines
| Tool | What it does | Typical use |
|---|---|---|
| set_lines | Set DTR and/or RTS and hold them. | Hold an Arduino in reset; assert PTT for as long as the user says. |
| pulse_line | Drive DTR or RTS to a level for N ms, then always restore it. | Reset a board (DTR low, 100 ms); key a rig for a timed transmission (RTS, 2000 ms). |
| send_break | Hold TX low for N ms. | Enter ROMMON, interrupt a boot. |
| status | Shows CTS, DSR, CD, RI, DTR, RTS. | "Is the cable wired for flow control?" |
Many rig interfaces key PTT from RTS or DTR. Asking the agent to pulse a line on such a port makes the radio transmit. The skill tells the agent to confirm first, keep pulses short, and never leave PTT asserted with set_lines unless you said so. Read-only mode refuses these tools completely.
Scripting with expect
A login takes four round trips: return, username, password, first command. expect runs them in one call. Each step is a send and the prompt to wait for, and the call returns every step's output. A step can also only wait, or only send. auto_reply answers pagers along the way. With {"--More--": " "}, a paged show run comes back as one result. If a step's prompt never shows up, the sequence stops there, says so, and returns whatever arrived.
expect(steps=[
{"send": "", "expect": "login: "},
{"send": "admin", "expect": "Password:"},
{"send": "<password>", "expect": "> "},
{"send": "show version", "expect": "> ", "clear_first": true}
])
Capture and transcript
"Record this session" starts a capture. The raw format writes received bytes unchanged as they arrive, like a terminal log. The annotated format writes timestamped lines marked TX or RX, which is what you want when debugging a protocol. capture_stop reports the file. Separately from any capture, the last 256 KB received on every connection is kept as a transcript. So "show me what the router printed earlier" works even after a read has used up those bytes. MCP clients that browse resources see the same transcript as serial://transcript/<name>.
Read-only mode and idle auto-close
Two environment variables in the server's config entry control these. SERIAL_CONSOLE_READ_ONLY=1 makes the server refuse anything but read-style commands (show, display, two-letter CAT reads such as ID;, CI-V read frames) and refuse control-line changes. This is how a cautious admin can try the tool on a production console. SERIAL_CONSOLE_IDLE_MINUTES=15 closes a port that nobody has used for that long, so a forgotten connection stops blocking WSJT-X. Both are off by default.
05 · Terminals: clean text, keys, and a real screen
A terminal program offers a menu of emulations: VT100, VT220, Xterm, ANSI, Linux, Wyse, TN3270. An agent needs three of them, and this chapter covers those three. The rest of the menu is either the same family under another name, or a 1980s terminal nobody has plugged in for twenty years.
Three terminal modes
| Mode | What the agent sees | Use it for |
|---|---|---|
| dumb | The bytes as received, unchanged. This is the default. | CAT, CI-V, rotators, GPS, Arduino, IoT gateways and sensors, binary protocols, and most CLIs. |
| ansi | Color and escape sequences are stripped as they arrive. A bare carriage return overwrites the line, backspace moves left, and erase-line and cursor moves are honored. A progress bar reads as its final state, a colored prompt as plain text, and read_until_prompt matches through the color codes. | Linux shells, colored router prompts, embedded and IoT consoles, anything that prints ESC[32m noise. The console presets set it. |
| xterm · vt100 | All of the above plus a real character grid, 80×24 unless you say otherwise, updated continuously. The screen tool returns the grid as rows of text with the cursor position, and the terminal answers "who are you" queries as a VT100 so full-screen programs behave. | BIOS setup over a serial console, RAID and BMC consoles, menu-driven switches (older ProCurve, Netgear), alarm and control panel menus, UPS and PDU setup screens, vi, top. The screen-console preset. |
Choose the mode with connect(terminal=…), or let the preset choose it. The capture file and the screen model always keep the raw bytes. Only what the read tools return is cleaned. The screen model needs the [screen] extra (pip install 'serial-console-mcp[screen]'). The desktop extension includes it. Without it, xterm falls back to ansi and tells you so.
Prompts on a redrawn line
If you ask Junos for help with show system ?, or clear the line with Ctrl-U, it does not print a fresh prompt. Instead it sends a carriage return, retypes the line, pads it with spaces and backs up over them. So the bytes end in show system \b\b\b rather than in > . In ansi, vt100 and xterm mode, read_until_prompt and expect also try the pattern on the last line as the screen shows it, up to the cursor. After Ctrl-U, the preset's [#>%] ?$ finds root@sw> . After ?, the pattern > show system $ finds the retyped words. A line that has ended, or that a carriage return has just rewound, is never taken for a prompt. dumb mode matches the bytes as they are, backspaces and all. Tab completion does not cause this problem. On Junos 15.1 it only adds the rest of the word in place, so it needs no special handling.
Keys by name
send_text types a line. send_keys presses keys, as in "hit Ctrl-C, that ping is still running", "press Tab to complete it", or "arrow down twice and Enter". The key names are the obvious ones: ctrl-c through ctrl-z, esc, tab, enter, backspace, delete, up, down, left, right, home, end, pgup, pgdn, f1 to f12, a single character, or text:… for a literal run. They are encoded the way an xterm sends them, which is what every current device expects. send_keys does not read anything. Look at the reply with read_until_prompt, read_available or screen.
Connect with the screen-console preset. Then repeat: press a key, read the screen, decide, press the next key. Read the screen after every keypress. Missing that a menu did not move is the most common way an agent gets lost in a BIOS. The skill gives the agent the same instruction.
TN3270 is IBM mainframe over telnet, not serial. SCOANSI, Wyse 50/60 and Televideo are proprietary terminals for old SCO and Unix hosts, with no maintained emulator. The rare host that still wants one usually accepts VT100. If you meet such a host, fall back to dumb mode with a capture file.
Read-only mode and keys
In read-only mode, named keys are allowed, because interrupting and navigating never change configuration by themselves. Single characters and text: runs are refused, since those count as typing.
06 · Junos and IOS, the basics
The server deals with bytes and prompts. Two skills teach the agent what the prompts mean on the two console families it will meet most. They cover how to tell which mode it is in, how to move from one mode to another, how to change something without cutting off its own connection, and which show commands answer the everyday questions. Both live in skills/ and load the way the fldigi skills do.
junos-operating
| Prompt ends in | Mode | Move |
|---|---|---|
| % (newer releases: ~ # with no [edit] above) | FreeBSD shell | cli to reach the CLI. cli -c "show … | no-more" runs one command without leaving the shell. exit here logs out. root always lands here. Put the console back here if that is how you found it. |
| > | Operational | show …; configure exclusive to edit; exit back to the shell (or to login:); start shell opens a shell whose exit returns here. |
| # with [edit] above | Configuration | show | compare, commit check, commit confirmed 5, rollback 0 to discard, exit at the top to leave. |
The skill enforces these rules. Read the prompt before every action. Use configure exclusive, not bare configure. Show the user show | compare before any commit. Use commit confirmed for anything that can cut the path you are on, then a plain commit once you have verified the device is still reachable. To undo, use rollback 1, then commit.
| Question | Junos command |
|---|---|
| What is it, which version | show version, show chassis hardware |
| Is it healthy (there is no show system status) | show system alarms, show chassis alarms, show chassis routing-engine (CPU, memory, last reboot), show chassis environment (power, temperature), show system storage |
| Uptime, last config change | show system uptime |
| Interfaces | show interfaces terse (one line each), show interfaces brief (a short block each), show interfaces descriptions |
| VLANs, neighbors, routes | show ethernet-switching interfaces, show vlans, show lldp neighbors, show route |
| Config and log | show configuration | display set, show log messages | last 50 |
Before anything long, send set cli screen-length 0 once, or add | no-more. At a ---(more pager, every key is a pager command (q quits, s saves the output to a file). So press q before typing the next command. If you are not sure a command exists, type ? after the words so far, clear the line with Ctrl-U, and read the prompt again. The ansi terminal matches the prompt on the redrawn line, so no extra return is needed.
ios-operating
| Prompt | Mode | Move |
|---|---|---|
| Switch> | User EXEC | enable (may ask for a password: ask the user). |
| Switch# | Privileged EXEC | All show; configure terminal to edit; disable or exit to leave. |
| Switch(config)# | Configuration | Every line is live as typed. end back to #, then write memory to keep it. |
| rommon 1 > | Boot monitor | The OS is not running. Stop and tell the user. |
IOS has no commit, so the procedure is different. Send reload in 10 before any change that could cut the path. Then verify, send reload cancel, then write memory. Undo a line by putting no in front of it. IOS-XE with archive has configure replace. IOS has no shell underneath to get out of. The only prompts that are not the CLI are the boot monitor and the setup dialog.
| Question | IOS command |
|---|---|
| What is it, version, uptime, last reload reason | show version, show inventory |
| Is it healthy (no show system status here either) | show environment all, show processes cpu sorted, show processes memory sorted |
| Interfaces (show interfaces brief is NX-OS and IOS-XR, not IOS) | show ip interface brief, show interfaces status, show interfaces description |
| VLANs, neighbors, routes | show vlan brief, show mac address-table, show cdp neighbors, show ip route |
| Config and log | show running-config, show logging | include % |
Send terminal length 0 once per session. IOS pipes are include, exclude, begin, section and count. The pipes last and no-more are Junos only.
The skills will not commit, write, reload, erase or zeroize on their own. Each of these needs the user to ask for it in the conversation. For anything that can cut the console's own path, the confirmed-commit or reload-timer safety net is the default. In read-only mode the skills use only show and the commands that change mode.
The Junos parts of this chapter were run live on 15 September 2026, with nothing committed. The run covered shell to CLI and back with cli, exit, start shell and cli -c; every show command in the Junos table (including the proof that show system status is a syntax error), the pager and the ? help, and configuration mode through configure exclusive, commit check, edit, up, top, run show, rollback 0 and exit. One test went wrong. Before the pager string was right, a command typed at the pager was read as pager keys, and it saved the output to a file on the switch. The skill now says to press q first. The IOS skill is written from the Cisco command references. It has not yet been tried on a Cisco on this bench.
E · Worked sessions
There are five sessions, one per family. Each shows what you say, what the agent sends, and what comes back. The device replies are representative, not recordings.
1 · Juniper craft port: log in and read a version
Why it worked. The preset supplied CR and the prompt regex. Each step named the next prompt to wait for. clear_first made the last reply begin at the echo. It took one approval and one round trip.
2 · Kenwood-style CAT: identify the rig and read the VFO
Why it worked. The preset supplied both things the dialog never asks for. It set no line ending (Kenwood parsers reject a CR) and ; as the prompt, so each reply returned as soon as it was complete. cat_parse turned the codes into a model and a frequency.
3 · Icom CI-V: read the operating frequency in hex
Why it worked. CI-V is a binary protocol, so the hex view is the answer, and civ_parse does the BCD arithmetic. The rig's USB interface echoes, and the parser labels the echo so it is not mistaken for a reply. XON/XOFF was off, so no byte of the binary frame could be mistaken for flow control.
4 · Arduino sensor: watch a stream, then stop it
Why it worked. A device that talks on its own has no prompt and needs no query. read_available with a longer timeout captures a window of output (capture_start would keep all of it in a file). disconnect frees the port for the Arduino IDE.
5 · Rotator: read the heading, turn to Europe, confirm
Why it worked. Two ports were open at once, each with its own preset. The rotator's CR line ending and "\r" reply terminator came from its preset. A move is confirmed by reading the position back, never assumed.
F · When it does not answer
Most silent ports come down to one of the causes below. The server's own messages point to many of them.
| Symptom | Most likely cause | What to say |
|---|---|---|
| No serial ports found | Charge-only USB cable, missing driver, device off. Windows: nothing under Ports (COM & LPT). | Swap the cable, then "list the ports again". |
| Could not open, already in use | Another program holds the port. On a Mac or Linux the message names it (port_in_use_by). | Close it, then "try connecting again". |
| Port name was not found | Names change when you replug, so the remembered port is out of date. | "List the ports and connect to the right one." |
| Garbage characters, box drawing | Wrong baud rate. Occasionally wrong data bits or parity. | "Detect the baud rate" · "reconnect at 115200". |
| Echo comes back, no reply | Wrong line ending for this device, or a CLI that needs a return to wake. | "Send a return first" · "use LF instead of CR". |
| Prompt not seen, partial output returned | The prompt string does not match: missing trailing space, hostname changed, or --More-- paging. | "The prompt is admin@r1> with a space" · "answer --More-- with a space". |
| Output full of ESC[0m and [32m noise | Color and cursor escape sequences are shown raw, because the connection is in dumb mode. | "Reconnect with terminal ansi" (or a console preset). |
| A command will not stop; the prompt never returns | ping, monitor, a pager or a hung process is still running. | "Press Ctrl-C" · "press q". |
| Menu screen looks like scrambled fragments | A full-screen program is repainting with cursor addressing. Line tools cannot show it. | "Reconnect as xterm and show me the screen." |
| Read-only mode refused to send | SERIAL_CONSOLE_READ_ONLY is set and the command is not a read. | Unset it in the config entry, or widen SERIAL_CONSOLE_ALLOW. |
| Wrong device answered | Several ports are open, and the command went to the current one. | "Send that to the rotator" · "what is the status?" |
| Write failed: write timeout | RTS/CTS is on and the cable does not carry CTS. | "Reconnect without hardware flow control." |
| Background reader has stopped | Adapter unplugged or port taken by another program mid-session. | "Disconnect and reconnect." |
| Claude says it has no serial tools | Claude Desktop was not fully quit after install, or the config entry is missing. | Quit with ⌘Q or the tray Quit, reopen, new chat. |
"What is the status?" tells you whether a port is open, at what settings, whether the reader is alive, and how much is buffered. "Read whatever is waiting" shows raw bytes as text and hex, which is how you tell a baud-rate problem (garbage) from a line-ending problem (silence).
G · Tool reference
These are the parameters as the agent sees them, with defaults in brackets. Every tool that acts on a port takes connection [current].
| Tool | Parameters | Returns |
|---|---|---|
| list_serial_ports | none | Ports with description, hardware id, open-here marker. |
| list_presets | none | Each preset's settings and notes. |
| connect | port · preset · name · baud [9600] · bytesize [8] · parity N|E|O [N] · stopbits [1] · rtscts [off] · xonxoff [off] · line_ending [CR] · prompt · prompt_regex · terminal dumb|ansi|vt100|xterm [dumb] · cols [80] · rows [24] | Settings echoed back, preset notes, other open ports. |
| reconnect_last | name [most recent] | Same as connect, from the remembered settings. |
| disconnect | connection · all_connections [false] | Confirmation; always releases the port. |
| send_text | data · line_ending [connection's] · clear_buffer_first [false] | Byte count sent. |
| send_hex | hex_bytes · clear_buffer_first [false] | Bytes sent, as hex. |
| read_until_prompt | prompt [connection's, else ends-with # > $ %] · timeout [10 s] · regex · auto_reply | Output through the prompt; on timeout, whatever arrived. |
| read_available | read_timeout [1 s] | Bytes as text and hex, after the line goes quiet. |
| query_text | data · line_ending · prompt [connection's; "" = idle] · read_timeout [5 s] | The reply. |
| expect | steps [{send, send_hex, line_ending, expect, regex, timeout, clear_first}] · auto_reply · stop_on_timeout [true] | Each step's result. |
| clear_buffer | none | Bytes discarded. |
| send_keys | keys [ctrl-c, esc, tab, enter, up, f1, "x", text:…] | Confirmation of what was pressed. |
| screen | reset [false] | The xterm/vt100 grid as text, with cursor position. |
| status | none | Every open port: settings, reader, buffer, lines, capture; or what is remembered. |
| set_lines | dtr · rts | Resulting DTR and RTS. |
| pulse_line | line DTR|RTS · ms [100] · level [true] | Confirmation, line restored. |
| send_break | ms [250] | Confirmation. |
| capture_start | path [auto] · format raw|annotated [raw] | The file being written. |
| capture_stop | none | File and bytes written. |
| get_transcript | last_bytes [4000] | Tail of everything received (rolling 256 KB); also the resource serial://transcript/{name}. |
| port_in_use_by | port | Process holding it (macOS, Linux). |
| detect_baud | port · probe [""] · probe_line_ending [CR] · candidates · settle [0.5 s] | Rates ranked by readable reply, best guess. |
| cat_build | command · value · frequency_mhz · flavor kenwood|elecraft|yaesu [kenwood] | The ; string, with its meaning. |
| cat_parse | reply · flavor [kenwood] | Each reply decoded: rig model, MHz, mode, IF fields, ?/E/O errors. |
| rotator_build | action azimuth|position|elevation|move|move_azel|stop|…|speed · azimuth · elevation · speed | The GS-232 command. |
| rotator_parse | reply | Azimuth and elevation in degrees, or the rejection. |
| civ_build | command · data · rig [94] · controller [E0] | Frame as hex, with its meaning. |
| civ_parse | hex_bytes · controller [E0] | Each frame decoded: echo or reply, frequency, mode, OK/NG, PTT. |
| civ_freq | mhz | bcd_hex | The other representation. |
Other details
- Non-ASCII text in
send_textis sent as?. Usesend_hexfor raw bytes. - Display decoding is UTF-8 with replacement characters. Prompt matching works on the exact bytes, so binary noise before a prompt does not shift the match.
- The write timeout is two seconds. The reader polls every tenth of a second. A read with no prompt returns after 150 ms of quiet. The receive buffer holds 4 MB per port, and the transcript holds 256 KB.
- Remembered connections live in
last_connection.jsonunder~/Library/Application Support/serial-console-mcp/(Mac),%APPDATA%\serial-console-mcp\(Windows),~/.config/serial-console-mcp/(Linux). Captures that are named automatically go in acapturesfolder beside it. - Terminal modes change only what the read tools return. The capture file and the screen model always get the raw bytes. If an escape sequence is split across two reads, the stripper holds it until it is complete.
- The operating skill in
skills/serial-console/SKILL.mdcarries this guide's rules for the agent.
H · About and provenance
Release 0.3.2 was run against real hardware on 15 September 2026. This guide claims only what has been verified.
serial-console-mcp began as a generic console tool for network craft ports and radio gear. For this release it was rewritten around the background-reader model described in chapter D. The 0.3.2 code is verified three ways. An 83-case test suite drives a simulated serial port. Continuous integration runs on Linux, macOS and Windows. And a live session ran on 15 September 2026. In that session the PyPI package, launched by uvx the same way Claude Desktop launches it, drove a Juniper EX2200-C (Junos 15.1R6.7) over a Prolific USB-serial adapter at 9600 8N1 with the juniper-craft preset. One expect call entered the CLI from the shell, turned off paging, and ran show version, show chassis hardware and show system uptime. Every prompt matched on the regex, and the call returned to the shell. An annotated capture recorded the session. The worked sessions in chapter E are still representative walk-throughs. The Juniper one now matches what the real switch printed.
| Resource | Where |
|---|---|
| serial-console-mcp | github.com/sbrunner-atx/serial-console-mcp |
| Desktop extension | serial-console-mcp.mcpb on GitHub Releases, listed on Downloads |
| PyPI | pypi.org/project/serial-console-mcp · uvx serial-console-mcp |
| Server and tests | src/serial_console_mcp/ (server, presets, terminal, cat, civ, rotator) · tests/test_serial_console.py |
| Operating skills | skills/serial-console · skills/junos-operating · skills/ios-operating (SKILL.md each) |
| Live-port harness | _hosttest/run_live.py <steps.json> |
| This guide's sources | docs/brand/ (HTML and CSS, WeasyPrint) |
| Sibling servers | fldigi-mcp · wsjtx-mcp · n3fjp-mcp · mcp-host-bridge |
Corrections and additions are welcome. Open an issue on the repository.
This is private, personal work by Stefan Brunner, AE5VG, built for amateur radio operators and anyone else with a console cable. It is not affiliated with any equipment vendor or any employer. MIT licensed. GL es 73 de AE5VG sk.