Skip to main content

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 pyserial supports via serial_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


Download files

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

Source Distribution

seriallm-0.1.0.tar.gz (22.2 kB view details)

Uploaded Source

Built Distribution

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

seriallm-0.1.0-py3-none-any.whl (22.8 kB view details)

Uploaded Python 3

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

Hashes for seriallm-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c4291fe007ef27f6905f85178bec64980176cb5f277fdc4159d07d7d0d0bcf1c
MD5 fbef5f39e8eb17f1cd6cb16beebc4ef3
BLAKE2b-256 89e2cf2051b7cbbb31b55055b44210dd1d08fcb83935887d9ef55257c95f4e40

See more details on using hashes here.

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

Hashes for seriallm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d8170c8570094c3f572d51a049a50bfc7d811d3a5f3b0641676d3c8d004d85f3
MD5 c6da6e8e0bdb9764813ac2d12aa0971b
BLAKE2b-256 2e8db054a4e58477b8675a34796160119a1025b436a52f40b0f121cec77e72d2

See more details on using hashes here.

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