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.2.0.tar.gz (22.6 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.2.0-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

Details for the file seriallm-0.2.0.tar.gz.

File metadata

  • Download URL: seriallm-0.2.0.tar.gz
  • Upload date:
  • Size: 22.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for seriallm-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7d1e7f93343806216d6cf0ff0973775446e072271d495a7e47e2fb0617cf918d
MD5 25992c3d95cfd24bfd58e5f229b2a74b
BLAKE2b-256 0d798dbeb19d7d123b1ee4cb10ca2c25f6a4f8b47a51bf8c120052bad79e645b

See more details on using hashes here.

File details

Details for the file seriallm-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: seriallm-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 23.2 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 66d37fb2f33152eca478d928c15f3d58693724e59307bcf592d387a4409d9505
MD5 3fbc6e1333bdc1e114d78dbab039394a
BLAKE2b-256 c4ad6a005fa76b485c0858b9b9e42617a5978651df2a8b8093d55dbfef10c2a9

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