Skip to content

alxgmpr/serial-mcp

Repository files navigation

serial-mcp

MCP server that lets LLMs talk to serial devices: microcontrollers, routers, modems, embedded Linux, anything with a UART.

Why this exists

LLMs are surprisingly good at interacting with hardware over serial, but without proper tooling they resort to hacking together Python scripts or asking you to copy-paste between terminals. This MCP server gives them a real serial interface instead.

What makes this different from the other serial MCP servers? I actually use this. Every tool exists because I hit a wall without it, not because it sounded good on a feature list. It handles things the others don't: XMODEM file transfers, hardware signal control for reset/bootloader sequences, baud rate detection, triggered responses for catching time-sensitive boot prompts, and a ring buffer that doesn't lose data between tool calls.

image

Install

With uv (recommended)

Install globally so the serial-mcp command is available everywhere:

uv tool install serial-mcp

Or from a local clone:

uv tool install /path/to/serial-mcp

With pip

pip install serial-mcp

From source (editable)

git clone https://github.com/alxgmpr/serial-mcp.git
cd serial-mcp
uv pip install -e .

Configure

Claude Code

claude mcp add serial-mcp -- serial-mcp

That's it. Verify with claude mcp list.

If you installed from source instead of globally, use the full path:

claude mcp add serial-mcp -- python3 -m serial_mcp.server

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "serial": {
      "command": "serial-mcp"
    }
  }
}

With uvx (no install)

{
  "mcpServers": {
    "serial": {
      "command": "uvx",
      "args": ["serial-mcp"]
    }
  }
}

Tools

All tools are prefixed with serial_ to avoid collisions with other MCP servers.

Tool What it does
list_serial_ports List available ports with USB metadata (VID/PID, manufacturer)
serial_detect_baud Try common baud rates and score readability to find the right one
serial_force_release Kill the process holding a port (SIGTERM, then SIGKILL) so you can open it
serial_open Open a connection (configurable baud, data bits, stop bits, parity, inactivity timeout)
serial_close Close a connection and release the port
serial_change_settings Change baud/parity/etc. on a live connection without closing
serial_list_sessions List all open sessions
serial_status Connection health, byte counts, uptime
serial_command Send a string and wait for a response, with optional regex expect pattern
serial_write Fire-and-forget text write
serial_read Read buffered text data (advances the cursor)
serial_read_since Read historical data since a timestamp (non-destructive, doesn't advance cursor)
serial_wait_for Block until a regex pattern appears in incoming data
serial_write_hex Write raw bytes as hex ("AA 55 01 03")
serial_read_hex Read buffered data as a hex string
serial_set_signals Control DTR/RTS for reset sequences, bootloader entry, etc.
serial_get_signals Read CTS, DSR, RI, CD signal state
serial_send_break Send a serial break (used by U-Boot, Cisco ROMMON, etc.)
serial_clear_history Flush the receive buffer
serial_log_start Start capturing all received data to a file
serial_log_stop Stop logging, return file path and stats
serial_xmodem_send Send a file via XMODEM (checksum or CRC-16)
serial_xmodem_receive Receive a file via XMODEM (checksum or CRC-16)

serial_wait_for and serial_command both support triggered responses: you can set respond or respond_hex so the server automatically transmits a reply the instant a pattern matches. This is useful for catching time-sensitive prompts like U-Boot's "Hit any key to stop autoboot" where the MCP round-trip would be too slow.

The reader thread pauses during XMODEM transfers so the protocol has exclusive port access.

Prompts

Four prompts guide common workflows:

Prompt Description
scan_devices Walk through identifying all connected serial devices
detect_baud_rate Run baud detection on a port and interpret the results
interactive_shell Open a connection and probe for the device's shell prompt
safe_session Open/use/close lifecycle with mandatory port release reminder

Usage examples

Interactive shell on a Linux device

1. list_serial_ports()                        → find /dev/ttyUSB0
2. serial_open(port="/dev/ttyUSB0")           → connect at 115200 8N1
3. serial_command(data="", expect="[$#]")     → get the shell prompt
4. serial_command(data="uname -a", expect="\\$")

Arduino / microcontroller

1. list_serial_ports()                        → find /dev/ttyACM0
2. serial_open(port="/dev/ttyACM0", baud_rate=9600)
3. serial_command(data="STATUS", timeout=2)
4. serial_set_signals(dtr=False)              → reset the board
5. serial_set_signals(dtr=True)
6. serial_wait_for(pattern="Ready", timeout=5)

Unknown baud rate

1. serial_detect_baud(port="/dev/ttyUSB0")    → recommends 9600
2. serial_open(port="/dev/ttyUSB0", baud_rate=9600)

Binary protocol (Modbus, etc.)

1. serial_open(port="/dev/ttyUSB0", baud_rate=9600)
2. serial_write_hex(hex_string="01 03 00 00 00 0A C5 CD")
3. serial_read_hex(timeout=2)

ESP32 bootloader entry

1. serial_open(port="/dev/ttyUSB0", baud_rate=115200)
2. serial_set_signals(dtr=False, rts=True)
3. serial_set_signals(dtr=True, rts=False)
4. serial_set_signals(dtr=False)
5. serial_wait_for(pattern="waiting for download", timeout=3)

Catching a bootloader prompt

1. serial_open(port="/dev/ttyUSB0", baud_rate=115200)
2. serial_wait_for(pattern="Hit any key to stop autoboot", respond=" ", timeout=60)

How it works

Each serial_open() creates a SerialSession with a background thread that reads from the port into a timestamped ring buffer (10MB default cap). Data is captured continuously, even between tool calls, so nothing gets lost. serial_read_since() can replay history without advancing the read cursor, and serial_command()/serial_wait_for() scan the buffer for regex matches as data arrives.

Sessions auto-close after a configurable inactivity timeout (default 15 minutes). A background reaper checks every 30 seconds and closes stale sessions. When the AI next tries to use a closed session, it gets a clear error explaining what happened. All tools are async, with blocking serial I/O wrapped in asyncio.to_thread().

Serial output from text tools is normalized (\r\n\n, trailing whitespace stripped). Binary/hex tools return raw data.

When a port is held by another process, serial_open identifies the blocker via lsof and returns the PID and command name so the AI can offer to force-release it.

Testing

No hardware required. Tests use a MockSerial fixture:

uv pip install -e ".[dev]"
pytest -v

Smoke-test the live server with the MCP Inspector:

DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector -- python3 -m serial_mcp.server

Requirements

License

MIT

About

Let agents talk to serial devices like UART adapters

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors