mcp-serial
# mcp-serial
**Let AI agents talk to your hardware over a serial port.**
*(Versión en español: [README.es.md](README.es.md))*
`mcp-serial` is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives
Claude Code, Claude Desktop, Cursor, or any MCP client a set of tools to find serial ports, open
them, send commands and read what comes back: Arduino, ESP32/ESP8266, STM32, USB-UART adapters,
RS-232 lab equipment, AT modems, anything that speaks UART.
[](https://github.com/HumbertoBernal/mcp-serial/actions/workflows/ci.yml)
[](LICENSE)
[](pyproject.toml)
## Why
Debugging firmware means alt-tabbing between your editor and a serial monitor, copying stack
traces around by hand. With `mcp-serial` the agent that is editing your code can also reset the
board, read the boot log, spot the `Guru Meditation Error`, and go fix the line it points at, all
in one conversation.
> **You:** find my ESP32, reset it and tell me why it keeps rebooting
>
> **Agent:** `list_ports` → `/dev/cu.usbserial-0001` (CP210x, 0x10C4:0xEA60) → `open_port` at 115200
> → `reset_device` → boot log shows `Guru Meditation Error: Core 1 panic'ed (LoadProhibited)` at
> `PC: 0x400d1a2f` … *"That address is inside `parse_packet()` in `src/protocol.cpp`; you dereference
> `buf` before checking the length. Here's the fix."*
Things it is good at:
- **Interactive firmware debugging**: reset, capture boot logs, send CLI commands, watch output.
- **Bring-up of new boards**: identify the port by VID/PID, try baud rates, check that the
firmware answers.
- **Talking to instruments and modems**: `AT` command sets, SCPI over RS-232, custom ASCII
protocols, raw hex frames.
- **Sensor experiments**: read a stream for a while, then let the agent summarize or plot it.
## Install
Requires Python 3.10+ and [`uv`](https://docs.astral.sh/uv/) (or `pipx`).
```bash
# straight from GitHub (PyPI release coming)
uvx --from git+https://github.com/HumbertoBernal/mcp-serial mcp-serial --help
```
### Claude Code
```bash
claude mcp add serial -- uvx --from git+https://github.com/HumbertoBernal/mcp-serial mcp-serial
```
### Claude Desktop, Cursor, Windsurf, and other JSON configs
```json
{
"mcpServers": {
"serial": {
"command": "uvx",
"args": ["--from", "git+https://github.com/HumbertoBernal/mcp-serial", "mcp-serial"]
}
}
}
```
To restrict which ports the agent may touch, add an environment variable (see [Safety](#safety)):
```json
"env": { "MCP_SERIAL_ALLOWED_PORTS": "/dev/cu.usb*,/dev/ttyUSB*" }
```
## Tools
| Tool | What it does |
| --- | --- |
| `list_ports` | Enumerate serial ports with USB VID/PID, manufacturer, product and serial number. |
| `open_port` | Open a port (baud rate, data bits, parity, stop bits, encoding) and start buffering everything it sends. |
| `close_port` | Release the port for other programs. |
| `port_status` | Byte counters, unread bytes, uptime, and the error if the device disconnected. |
| `query` | Send a command and return the reply in one call: wait for a pattern (`expect="OK"`) or until the line goes quiet. |
| `write` | Send text (with configurable line ending) or raw hex bytes (`"01 A0 FF"`). |
| `read` | Consume buffered data. Waits for the first byte up to `timeout_s`; `settle_ms` keeps collecting until the device is silent. |
| `read_until` | Block until a substring or regex appears, return everything up to and including it. |
| `tail` | Peek at the last N lines received **without** consuming them. |
| `clear_buffer` | Drop stale buffered data. |
| `reset_device` | Pulse DTR/RTS to reboot Arduino / ESP32 style boards and capture the boot output. |
| `set_control_lines` | Drive DTR and RTS manually (bootloader entry, custom reset circuits). |
There is also a `serial://ports` resource with the same information as `list_ports`.
Every port is read continuously by a background thread into a bounded buffer (1 MB per port) plus
a 2000-line history for `tail`, so nothing the device prints between two tool calls is lost.
```
device ──UART──▶ pyserial ──reader thread──▶ [ unread buffer ] ──▶ read / read_until / query
└──▶ [ line history ] ──▶ tail
```
## Try it without hardware
pyserial URL handlers work everywhere a port name is accepted:
- `loop://` echoes back whatever you write: `open_port("loop://")`, then `query("loop://", "PING")` → `PING`.
- `socket://192.168.1.50:23` for network serial bridges (ESP-Link, ser2net, Moxa NPort).
- `rfc2217://host:port` for RFC 2217 servers.
## Try it with an Arduino
Flash [`examples/arduino_echo/arduino_echo.ino`](examples/arduino_echo/arduino_echo.ino) (any board,
115200 baud), then ask your agent:
> open my Arduino and send it PING, TEMP? and HELP
The sketch answers `PONG`, a fake temperature, and a list of its commands, enough to check that
the whole chain works before pointing the agent at your real firmware.
## Safety
The server only moves bytes between the agent and the port. It does not flash firmware, run
programs, or touch files. Still, an agent that can write arbitrary bytes to a device can trigger
whatever that device's protocol allows, so:
- **Allowlist ports** with `MCP_SERIAL_ALLOWED_PORTS` (comma-separated globs, e.g.
`/dev/cu.usb*,COM3`) or `--allow GLOB` (repeatable). Anything else is refused.
- **Timeouts are capped** at `MCP_SERIAL_MAX_TIMEOUT` seconds (default 120) so a tool call can never
hang the agent forever; reads are capped at 1 MB.
- **At most 8 ports** are open at a time, and everything is closed when the server shuts down.
- Nothing is written to stdout except MCP traffic, so it is safe under stdio transport.
Flashing (`esptool`, `avrdude`) is on the [roadmap](ROADMAP.md) as a separate, opt-in tool.
## Running the server manually
```bash
mcp-serial # stdio (what MCP clients spawn)
mcp-serial --allow "/dev/cu.usb*" # restrict ports
mcp-serial --transport streamable-http --http-port 8000 # HTTP, for remote clients
```
## Development
```bash
git clone https://github.com/HumbertoBernal/mcp-serial
cd mcp-serial
uv sync --group dev
uv run pytest # tests use pyserial's loop:// device, no hardware needed
uv run ruff check .
```
Test the server interactively with the MCP inspector:
```bash
npx @modelcontextprotocol/inspector uv run mcp-serial
```
See [ROADMAP.md](ROADMAP.md) for what is planned and [CONTRIBUTING.md](CONTRIBUTING.md) if you want
to help. Issues with a `hardware:` label are great first contributions: test the server with a
board we have not tried and report what works.
## License
[MIT](LICENSE)
TDQS
Scored across 12 tools
Each tool targets a distinct serial-port operation: open/close/status/list are lifecycle, write/read/read_until/query/tail/clear_buffer handle data flow in clearly separated ways, and reset_device/set_control_lines cover control-line actions. The descriptions explicitly call out the consumption-vs-peek and reply-vs-no-reply distinctions, so an agent should not misselect.
Most tools follow verb_noun naming (open_port, list_ports, clear_buffer), but several are bare verbs (write, read, query, tail) and port_status uses a noun phrase instead of a verb. The mix is readable but not a consistent pattern.
Twelve tools is a well-scoped size for a serial-port server. Each operation has a distinct role and the count is neither bloated nor too thin.
The surface covers the full serial workflow: enumeration, open/close/status, raw write, buffered read, pattern waits, request/response queries, monitoring without consuming, buffer clearing, device reset, and manual control-line control. No major missing operation is apparent.