Skip to main content

MCP server for serial device communication

Project description

serial-mcp

MCP server for serial port communication. Lets LLMs talk to hardware — microcontrollers, routers, modems, embedded Linux, anything with a UART.

Why use this?

There are a handful of serial MCP servers out there. Most of them stop at open / read / write / close — fine for "send a string, get a string," but it falls apart the moment you need to drive a shell prompt, wait for a microcontroller to finish booting, or push firmware over XMODEM.

This one is built for the messy parts:

  1. Expect/wait, not just read. serial_command writes and waits for a regex (or 300 ms of silence). serial_wait_for blocks until a pattern appears. That's the difference between scripting an interactive shell and racing the device's output buffer.
  2. Nothing is lost between tool calls. A background reader thread captures everything into a timestamped ring buffer the moment a port opens. The LLM can replay history with serial_read_since instead of guessing when to call read.
  3. Real hardware control. DTR/RTS, break signals, and XMODEM file transfer — the primitives you actually need to reset Arduinos, enter ESP32 bootloader mode, interrupt U-Boot, or push firmware to a board with no network.
  4. Every tool is annotated. readOnlyHint, destructiveHint, idempotentHint on each registration — the host model knows which calls are safe to retry or parallelize, instead of treating every tool as opaque.
  5. Interactive elicitation. When the host supports it (Claude Desktop does), serial_open and serial_detect_baud prompt the user to pick a port or confirm a baud rate — no remembering /dev/ttyUSB0 or guessing rates. When the host doesn't, they degrade gracefully and hand the data back to the LLM to relay.
  6. Drop-in install, no toolchain required. uv tool install serial-mcp, pip install serial-mcp, or uvx serial-mcp from PyPI; there's also a packaged MCPB for one-click Claude Desktop install. The leading Rust alternative wants you to git clone && cargo build --release against a working Rust 1.70+ toolchain.
  7. 42 mocked unit tests, no hardware required for CI. A MockSerial fixture covers session buffering, pattern matching, history trimming, logging, XMODEM, output normalization, and auto-close. I also smoke-test against real devices before tagging a release.
  8. I use this day-to-day for actual hardware hacking. The 22 tools exist because I needed them, not because they round out a feature matrix.
image

What it does

Exposes serial ports as MCP tools so an AI assistant can:

  • Discover connected USB-serial adapters and identify them by VID/PID
  • Connect to devices with configurable baud rate, data bits, stop bits, parity
  • Send commands and wait for responses (with regex-based expect patterns)
  • Read/write raw hex for binary protocols (Modbus, bootloader commands, etc.)
  • Control hardware signals (DTR/RTS) — reset Arduinos, enter ESP32 bootloader mode
  • Auto-detect baud rate by trying common rates and scoring readability
  • Transfer files with XMODEM (checksum or CRC-16)
  • Log received data to a file for capture / postmortem analysis
  • Manage multiple sessions simultaneously across different ports

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 name collisions with other MCP servers. Each tool includes MCP annotations (readOnlyHint, destructiveHint, etc.).

Port discovery

Tool Description
list_serial_ports List available serial ports with USB metadata (VID/PID, manufacturer)
serial_detect_baud Auto-detect baud rate by trying common rates and scoring ASCII readability

Port control

Tool Description
serial_force_release Kill the process holding a serial port (SIGTERM → SIGKILL) so it can be opened

Connection management

Tool Description
serial_open Open a serial connection with configurable inactivity timeout (default 15 min auto-close)
serial_close Close a connection and release the port
serial_change_settings Change baud/parity/etc. on a live connection without closing it
serial_list_sessions List all open sessions
serial_status Detailed connection health, byte counts, uptime

Read / write (text)

Tool Description
serial_command Send a string, wait for response. Supports expect regex for prompt detection
serial_write Fire-and-forget text write
serial_read Read buffered data (advances cursor)
serial_read_since Read historical data since a timestamp (non-destructive)
serial_wait_for Block until a regex pattern appears in incoming data

Read / write (binary)

Tool Description
serial_write_hex Write raw bytes as hex ("AA 55 01 03")
serial_read_hex Read buffered data as hex string

Hardware signals

Tool Description
serial_set_signals Control DTR/RTS (reset micros, enter bootloader, etc.)
serial_get_signals Read DTR, RTS, CTS, DSR, RI, CD
serial_send_break Send a serial break (interrupt U-Boot, Cisco ROMMON, etc.)

Session utilities

Tool Description
serial_clear_history Flush the receive buffer and free memory

Logging

Tool Description
serial_log_start Capture all received data to a file (like minicom's capture)
serial_log_stop Stop logging and return file path, byte count, and duration

File transfer

Tool Description
serial_xmodem_send Send a file via XMODEM (checksum or CRC-16 mode)
serial_xmodem_receive Receive a file via XMODEM (checksum or CRC-16 mode)

The reader thread is paused for the duration of an XMODEM transfer so the protocol has exclusive port access — serial_read and logging won't capture anything during the transfer.

Prompts

Four prompts are registered to guide common workflows:

Prompt Description
scan_devices Walk through identifying all connected serial devices by VID/PID
detect_baud_rate Run baud detection on a port and interpret the results
interactive_shell Open a connection and probe for the device 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)

How it works

Each serial_open() call creates a SerialSession with a background thread that continuously reads from the port into a timestamped ring buffer (default 10MB cap). This means:

  • No data loss — bytes are captured even between tool calls
  • Non-destructive readsserial_read_since() can replay history without advancing the cursor
  • Pattern matchingserial_command() and serial_wait_for() scan the buffer for regex matches in real-time
  • Multiple sessions — each port gets its own thread and buffer

All tools are async. Blocking serial I/O runs in asyncio.to_thread() so the event loop stays free.

Session lifecycle

Sessions auto-close after a configurable inactivity timeout (default 15 minutes). The AI can set a custom timeout via serial_open(inactivity_timeout=...). A background reaper task checks every 30 seconds and closes stale sessions, surfacing a clear message when the AI next tries to use them. Server-level instructions, tool docstrings, and prompts all reinforce that ports must be closed when done.

Output normalization

Serial output is normalized at the tool layer: \r\n\n, trailing whitespace stripped per line. Raw bytes are preserved in session.py for binary/hex tools — normalization only affects text-returning tools (serial_command, serial_read, serial_wait_for, serial_read_since).

Port conflict handling

When a port is held by another process, serial_open identifies the blocker via lsof and returns an actionable error with PID and command name. serial_force_release can kill the holder (with user approval) so the port can be reclaimed without leaving the AI session.

Testing

Run the unit tests (no hardware required — they 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

Set command to python3 and args to -m serial_mcp.server in the inspector UI, then connect.

Requirements

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyserial_mcp-0.5.0.tar.gz (33.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyserial_mcp-0.5.0-py3-none-any.whl (23.9 kB view details)

Uploaded Python 3

File details

Details for the file pyserial_mcp-0.5.0.tar.gz.

File metadata

  • Download URL: pyserial_mcp-0.5.0.tar.gz
  • Upload date:
  • Size: 33.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyserial_mcp-0.5.0.tar.gz
Algorithm Hash digest
SHA256 f7340abd30b9b505ea6d8ab67e43d5a7cf738e99e4e587df5106178dc7a37105
MD5 d9d5c866dd26b5081be5b60b4ff7080e
BLAKE2b-256 86a01b44d3d80ab056b2484c0d5e1e29546d7499021624b81617c980459cc31e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyserial_mcp-0.5.0.tar.gz:

Publisher: release.yml on alxgmpr/serial-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyserial_mcp-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: pyserial_mcp-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 23.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyserial_mcp-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1b1a841fe192f668a144030cdbc8db3f3aadcec159e54efccca860d0056d4a98
MD5 07a29e87d71d7d69fa85fbdf9e6fd05c
BLAKE2b-256 5de506eabb1bc08ff1db965ad9ca2a22e488d6c0003b446d2f3053aef0a1a9c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyserial_mcp-0.5.0-py3-none-any.whl:

Publisher: release.yml on alxgmpr/serial-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page