Serial terminal emulator with MCP server for LLM-driven serial port interaction
Project description
SeriaLLM
Serial terminal emulator with MCP (Model Context Protocol) integration.
Combines miniterm/tio-like interactive terminal access with
programmatic control via MCP, so an LLM agent can interact with serial
devices while the user sees everything live.
Features
- Interactive terminal on stdin/stdout (raw mode, Ctrl+] to quit)
- MCP server over stdio for LLM integration
- Supports local serial ports, RFC 2217, and TCP sockets (anything
pyserialsupports viaserial_for_url) - Auto-reconnect when the port disappears (USB serial going to bootloader, etc.)
- Ring buffer with absolute monotonic byte offsets — each MCP client tracks its own read position, no data is lost between calls
- Server/client architecture: multiple terminals and MCP clients share one server, server auto-spawns on first use and exits when idle
- Config file with port aliases and serial profiles
Installation
pip install seriallm
Quick start
Open a terminal to a serial port:
seriallm /dev/ttyUSB0 115200
A background server is automatically started. Ctrl+] to quit.
Configuration
Config file: ~/.config/seriallm/config.yaml
server:
# Unix domain socket (default)
socket: ~/.config/seriallm/server.sock
# Or TCP socket (for remote access)
# address: "127.0.0.1:8808"
# Seconds before idle server exits after last client disconnects
grace_period: 5
# Port aliases — shortcuts for serial port URLs
alias:
target:
url: /dev/ttyUSB0
profile: embedded
debug:
url: rfc2217://192.168.1.10:2217
profile: fast
nucleo:
url: /dev/ttyACM0
# Serial profiles — reusable baud rate / settings
profile:
default:
baudrate: 115200
embedded:
baudrate: 115200
fast:
baudrate: 921600
With this config:
seriallm target # opens /dev/ttyUSB0 at 115200
seriallm debug # opens rfc2217://192.168.1.10:2217 at 921600
seriallm nucleo # opens /dev/ttyACM0 at 115200 (default profile)
seriallm /dev/ttyS0 9600 # raw URL with explicit baud rate
Commands
seriallm [attach] <target> [baudrate]
Open a terminal to a serial port. <target> is an alias name or a
serial port URL. The baud rate is optional (defaults to the profile's
value, or 115200).
The server is auto-spawned if not already running.
seriallm /dev/ttyUSB0 115200
seriallm target
seriallm attach target --name my-port --raw
seriallm attach /dev/ttyACM0 9600 --server http://remote-host:8808
| Option | Description |
|---|---|
--name NAME |
Port name visible in MCP tools (default: alias name or URL) |
--raw |
Raw terminal mode (no output filtering) |
--server URL |
Connect to a specific server instead of config/auto-spawn |
--config PATH |
Use a custom config file |
Terminal
When stdin is a TTY, the tool enters raw terminal mode:
- Everything you type is sent to the serial port
- Everything received is printed to stdout
- Ctrl+] quits
In default mode, output is filtered: CR is mapped to CRLF, bare LF is
mapped to CRLF, non-printable control codes (except tab and ESC) are
stripped, NUL bytes are removed. Use --raw to disable filtering.
When stdin is not a TTY (piped), the terminal runs in headless mode (serial output only, no keyboard input).
seriallm serve
Start the server explicitly. Normally not needed — the server auto-spawns when a client connects.
seriallm serve
seriallm serve --background
seriallm serve --buffer-size 10000000
| Option | Description |
|---|---|
--background |
Suppress output (used by auto-spawn) |
--buffer-size N |
Max ring buffer per port in bytes (default: 1MB) |
--config PATH |
Use a custom config file |
The server listens on a Unix domain socket (default:
~/.config/seriallm/server.sock) or a TCP address if configured.
It starts with no serial ports — ports are created when terminal
clients attach and removed when they disconnect.
The server exits automatically after a grace period (default: 5s) when all clients disconnect.
seriallm mcp
Run as an MCP server over stdio. This is what you configure Claude Code to launch.
seriallm mcp
seriallm mcp --config /path/to/config.yaml
The MCP client connects to the shared server (auto-spawning it if needed) and exposes all serial port tools over stdio. It stays alive until the parent process (Claude Code) terminates.
MCP tools
All tools accept a port_id parameter to select which serial port to
operate on. Use list_ports to see available ports.
read_serial
Read data from the ring buffer.
| Parameter | Type | Default | Description |
|---|---|---|---|
since |
int | 0 | Absolute byte offset to read from |
up_to |
int | null | null | Absolute byte offset to stop at (exclusive) |
port_id |
string | "default" | Port to read from |
Returns { data, start, end }. The end value is the offset to pass
as since on the next call to get only new data. If start > since,
older data was evicted from the buffer.
send
Send a UTF-8 string to the serial port.
| Parameter | Type | Description |
|---|---|---|
data |
string | The string to send |
port_id |
string | Port to send to |
send_bytes
Send raw bytes (hex-encoded) to the serial port.
| Parameter | Type | Description |
|---|---|---|
hex_data |
string | Hex string, e.g. "0d0a" for CR LF |
port_id |
string | Port to send to |
wait_for
Wait for a regex pattern to appear in serial output.
| Parameter | Type | Default | Description |
|---|---|---|---|
pattern |
string | Regex pattern to search for | |
since |
int | 0 | Search from this absolute byte offset |
timeout |
float | 10.0 | Timeout in seconds |
port_id |
string | "default" | Port to watch |
Returns { offset, end, match } — the byte offset range and matched
text. Use read_serial(since=..., up_to=...) to get surrounding
context.
set_control_lines
Set DTR and/or RTS control lines.
| Parameter | Type | Default | Description |
|---|---|---|---|
dtr |
bool | null | null | Set DTR (null = don't change) |
rts |
bool | null | null | Set RTS (null = don't change) |
port_id |
string | "default" | Port to control |
send_break
Send a serial break signal.
| Parameter | Type | Default | Description |
|---|---|---|---|
duration |
float | 0.25 | Break duration in seconds |
port_id |
string | "default" | Port to send break on |
get_port_info
Returns baud rate, connection status, buffer offsets, and control line
states (CTS, DSR, RI, CD, DTR, RTS). The buffer_end value is the
current absolute byte offset — use it as since in read_serial to
start reading from "now".
get_port_events
Returns connection/disconnection events with their buffer offsets. Use to detect reconnection boundaries in the data stream.
| Parameter | Type | Default | Description |
|---|---|---|---|
since |
int | 0 | Only return events at or after this offset |
port_id |
string | "default" | Port to query |
set_baudrate
Change the baud rate at runtime.
| Parameter | Type | Description |
|---|---|---|
baudrate |
int | New baud rate |
port_id |
string | Port to change |
list_ports
List all currently attached serial ports with their connection status and baud rate.
Typical MCP usage patterns
Following serial output
The ring buffer uses absolute byte offsets that only increase. Track your read position to avoid re-reading data:
cursor = 0
# Send a command
send(data="version\r\n")
# Wait for the response
result = wait_for(pattern="v\\d+\\.\\d+", since=cursor, timeout=5.0)
# Read everything from cursor to just after the match
response = read_serial(since=cursor, up_to=result["end"])
cursor = response["end"]
Device reset flow
For devices like ESP32 that use DTR/RTS for bootloader entry:
set_control_lines(dtr=False, rts=True) # assert RESET
set_control_lines(dtr=True, rts=False) # assert BOOT, release RESET
set_control_lines(dtr=False, rts=False) # release both
wait_for(pattern="waiting for download", timeout=5.0)
Detecting reconnections
When a USB serial device disappears and reappears (e.g. bootloader
reset), use get_port_events to find the boundary:
events = get_port_events(since=cursor)
# [{"offset": 1523, "event": "disconnected"}, {"offset": 1523, "event": "connected"}]
Configuring with Claude Code
Add seriallm as an MCP server:
claude mcp add seriallm seriallm mcp
Or add to .mcp.json:
{
"mcpServers": {
"seriallm": {
"command": "seriallm",
"args": ["mcp"]
}
}
}
Then open a terminal to your device in a separate shell:
seriallm /dev/ttyUSB0 115200
Claude can now use the serial port tools. You see the serial I/O live in your terminal, and Claude interacts with the same port programmatically.
If you only need MCP access (no terminal), just configure Claude Code
and start using the tools — the server and MCP client handle everything
automatically. Attach a terminal later with seriallm <port> to see
live output.
Architecture
seriallm serve (background server, auto-spawned)
├── /ws WebSocket for terminal clients
└── /ws/mcp WebSocket for MCP tool clients (JSON-RPC)
seriallm <target> (terminal client, connects via /ws)
seriallm mcp (MCP stdio client, connects via /ws/mcp)
The server manages serial ports and ring buffers. Terminal clients attach via WebSocket for real-time I/O. The MCP stdio client proxies tool calls to the server via JSON-RPC over WebSocket.
Port lifecycle is tied to terminal clients: when a terminal client connects, the server opens the serial port; when it disconnects, the port is closed. MCP clients can access any port that has an active terminal client.
The server auto-spawns on first client connection and exits after a configurable grace period when all clients disconnect.
License
MIT
Attribution
Claude Code has been used to create this project. Human did the whole specification, and offloaded all the boring stuff to LLM.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file seriallm-0.1.0.tar.gz.
File metadata
- Download URL: seriallm-0.1.0.tar.gz
- Upload date:
- Size: 22.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4291fe007ef27f6905f85178bec64980176cb5f277fdc4159d07d7d0d0bcf1c
|
|
| MD5 |
fbef5f39e8eb17f1cd6cb16beebc4ef3
|
|
| BLAKE2b-256 |
89e2cf2051b7cbbb31b55055b44210dd1d08fcb83935887d9ef55257c95f4e40
|
File details
Details for the file seriallm-0.1.0-py3-none-any.whl.
File metadata
- Download URL: seriallm-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8170c8570094c3f572d51a049a50bfc7d811d3a5f3b0641676d3c8d004d85f3
|
|
| MD5 |
c6da6e8e0bdb9764813ac2d12aa0971b
|
|
| BLAKE2b-256 |
2e8db054a4e58477b8675a34796160119a1025b436a52f40b0f121cec77e72d2
|