david-t-martel/serial-mcp-server
If you are the rightful owner of serial-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
This application provides a production-grade, highly reliable Machine Control Protocol (MCP) server for interacting with system serial ports, engineered for stability, configurability, diagnosability, and deep observability.
Rust Serial Port MCP Server (v3.1 – Observability Edition)
This application provides a production-grade, highly reliable Machine Control Protocol (MCP) server for interacting with system serial ports. It is engineered for stability, explicit configurability, diagnosability, and deep observability—ideal for mission‑critical automation and integration with LLM / autonomous agents.
Core Capabilities (v3.1)
- Panic-Free Runtime: All
unwrap()calls removed in favor of structured, recoverable error flows. - Structured MCP Tooling: Exposes a first‑class MCP tool set (
list_ports,open_port,write,read,close,status,metrics). - Rich Serial Configuration: Full control of baud rate, timeout, data bits, parity, stop bits, flow control, optional write terminator, and idle auto‑disconnect.
- Deterministic & Idempotent: Repeated
closeon an already closed port returns success; safe to retry after transient failures. - Cross‑Platform: Works on Windows (
COM3etc.) and Unix‑like systems (/dev/ttyUSB0,/dev/ttyS0,/dev/ttyACM0, etc.). - Modular Architecture: Clear separation of concerns (
state,mcp,session,error, optional legacystdio). SeeARCHITECTURE.mdfor details. - Session Analytics: Persistent session logging with feature tagging, directional metadata, latency capture, filtering, and feature index aggregation.
- Metrics & Health: Real‑time cumulative counters (bytes read / written, open duration, idle auto‑close count) via the
metricstool.
Feature Flags & Interfaces
The project supports multiple interfaces for different use cases. All interfaces are equally valid and fully supported:
- MCP (default): LLM agent integration via Model Context Protocol
- REST API (opt-in): Web clients, HTTP-based automation, test frameworks
- stdio (opt-in): Simple scripting, legacy integrations, command-line tools
- WebSocket (opt-in): Real-time streaming for monitoring applications
Build examples:
# MCP-only (default, minimal binary)
cargo build --release
# With REST API for web clients/testing
cargo build --release --features rest-api
# With stdio for scripting
cargo build --release --features legacy-stdio
# Full feature set
cargo build --release --all-features
Build & Workflow
Prerequisites: Latest stable Rust (install via https://rustup.rs/).
Use the provided Makefile for consistent developer flows (it encodes features & quality gates):
make help # list targets
make build # debug build
make release # optimized build
make test # run all tests (unit + integration)
make clippy # lint (denies warnings)
make precommit # fmt-check + clippy + tests + deny (if installed)
make db-init # create / migrate session DB (optional, auto on first use)
You can still invoke cargo directly, but Make targets are the authoritative workflows.
Binary output: target/release/serial_mcp_agent.
Run (MCP / Stdio Transport)
When built with the mcp feature (default) the process speaks MCP over stdio:
./target/release/serial_mcp_agent
An MCP client (LLM host / orchestrator) should then perform a standard MCP initialize + list_tools flow.
Available MCP Tools
Serial / Port Control:
list_ports→ Enumerate available system serial ports.open_port→ Open a port with full configuration.write→ Write UTF‑8 text to the open port (auto‑appends configured terminator if missing).read→ Read up to 1024 bytes (non‑blocking beyond configured timeout; trims configured terminator if present).close→ Close the port (idempotent).status→ Return structured state, including current configuration if open.metrics→ Return cumulative IO counters & timing.
Session Persistence & Analytics:
create_session→ Create a persistent session log (returns session id).append_message→ Append a message with extended metadata.list_sessions→ List all sessions with filtering (open/closed) and optional limit.close_session→ Close a session by marking it as closed.list_messages→ List messages (ascending; optional limit).list_messages_range→ List messages with cursor-based pagination (after_message_id).export_session→ Export full session JSON (metadata + ordered messages).filter_messages→ Filter messages by role / feature substring / direction.feature_index→ Aggregate feature tag counts.session_stats→ Session statistics (message count, timestamps).
Serial Configuration (open_port)
| Field | Type | Default | Allowed / Notes |
|---|---|---|---|
port_name | string | (required) | System device identifier (e.g. COM4, /dev/ttyUSB0). |
baud_rate | u32 | (required) | Common values: 9600, 19200, 38400, 57600, 115200, etc. |
timeout_ms | u64 | 1000 | Read timeout in milliseconds. |
data_bits | enum | eight | One of: five, six, seven, eight (numeric aliases 5..8). |
parity | enum | none | One of: none, odd, even. |
stop_bits | enum | one | One of: one, two. |
flow_control | enum | none | One of: none, hardware (RTS/CTS), software (XON/XOFF). |
terminator | string | (none) | Optional line terminator appended on write (if absent) and trimmed on read (e.g. "\n", "\r", "\r\n"). |
idle_disconnect_ms | u64 | (none) | Milliseconds of inactivity (no successful read/write) after which the port is auto‑closed. |
Example MCP Call (open_port)
Pseudo JSON-RPC payload (client perspective):
{
"method": "tools/call",
"params": {
"name": "open_port",
"arguments": {
"port_name": "COM3",
"baud_rate": 115200,
"timeout_ms": 500,
"data_bits": "eight",
"parity": "none",
"stop_bits": "one",
"flow_control": "none",
"terminator": "\n",
"idle_disconnect_ms": 60000
}
}
}
Successful Response (abridged):
{
"content": [{"type": "text", "text": "opened"}],
"structured_content": {}
}
Status Example
{
"method": "tools/call",
"params": { "name": "status" }
}
Response (if open):
{
"content": [{"type": "text", "text": "status"}],
"structured_content": {
Status Example
--------------
"details": {
"config": {
"port_name": "COM3",
"baud_rate": 115200,
"timeout_ms": 500,
"data_bits": "eight",
"parity": "none",
"stop_bits": "one",
"flow_control": "none"
}
}
}
}
}
Error Semantics
Representative MCP tool errors use CallToolError forms (invalid_arguments, unknown_tool, or message). Agents should:
- Retry after transient I/O errors (e.g., permission denied due to another process locking the port—wait then retry
open_port). - On
invalid_arguments, correct the offending field(s) before retrying. - If
Port already openappears when callingopen_port, eitherclosefirst or proceed with operations.
Reading Behavior
read returns up to 1024 bytes. A timeout with no data yields 0 bytes and "read 0 bytes" (not an error). If a terminator is configured it is trimmed from the right edge of the returned text (single instance). Partial frames are expected—agents should internally buffer until a complete semantic unit (e.g. line) is assembled.
Idle Auto‑Disconnect
If idle_disconnect_ms is set the port is automatically closed on a read call once the elapsed wall time since the last successful read or write exceeds that threshold. The read response will indicate closure (text content includes closed (idle timeout)). An agent may immediately reopen using the previous configuration if further communication is required.
Structured auto‑close event payload (in read structured_content):
{
"event": "auto_close",
"reason": "idle_timeout",
"idle_ms": 60000,
"idle_close_count": 1
}
Metrics & Observability
The metrics tool returns cumulative counters & timing for the current port state.
Example structured_content fields:
state–OpenorClosedbytes_read_total– total bytes read since last openbytes_written_total– total bytes written since last openidle_close_count– number of idle auto‑closures in current open sessionopen_duration_ms– milliseconds since port openedlast_activity_ms– milliseconds since last successful read/write
Usage Tips:
- Poll before/after operation bursts to compute per‑burst deltas.
- Rising
last_activity_mscombined with stablebytes_read_totalindicates device silence. - Increase polling cadence or raise
idle_disconnect_msifidle_close_countincrements unexpectedly.
Session Persistence & Analytics Tools
Extended message schema (v3.1 additions):
direction(optional) – e.g.tx,rx,agent.features(optional) – space or comma separated feature tokens.latency_ms(optional) – associated latency measurement.
Common Pattern:
create_sessionat start of workflow.- After each read →
append_message(role=device, direction=rx, content=...). - After each write → optionally
append_message(role=tool, direction=tx, content=command). - Use
featuresto tag semantic meaning (e.g.temp voltage ack). - Query
feature_index+filter_messagesfor targeted analysis.
Closing & Reconfiguration
Reconfiguring currently requires close then a new open_port with updated parameters (a dedicated reconfigure tool may be added later).
Legacy Interfaces
The legacy (non-MCP) stdio command surface is deprecated and only built when the mcp feature is disabled. It intentionally returns deprecation errors for prior commands. Prefer MCP for all integrations.
Database Initialization
The session database schema is applied automatically on first use. To pre-create (e.g., packaging/CI):
make db-init
SESSION_DB_URL=sqlite://data/sessions.db make db-init
If the configured on-disk database cannot be opened (e.g. read-only filesystem), the server logs a warning and falls back to an in-memory SQLite instance so functionality remains available (persistence disabled for that run).
Reliability & Error Semantics
Production code avoids unwrap() / expect() so recoverable failures never abort the process. Error handling strategy:
- MCP tool failures return structured
CallToolErrorvariants (invalid arguments / unknown tool / message). - Serial port conflicts (already open / not open) are surfaced as user-correctable tool errors.
- Session DB initialization failure triggers a logged warning and in-memory fallback.
- Idle timeouts emit a structured auto-close event instead of a bare error.
- Integration tests may still use
expect()for clarity; runtime paths do not.
Development & Contribution
- Run debug build:
cargo build - Format / lint (optional):
cargo fmt/cargo clippy - Bench scaffolding:
cargo bench(basic Criterion harness included) - Custom session DB path: set
SESSION_DB_URL=sqlite://sessions.db(defaults to in-projectsqlite://sessions.dbif unspecified) - Generate docs:
cargo doc --no-deps --open
Changelog (v3.1)
- Added tools:
metrics,filter_messages,feature_index. - Extended
append_messagewithdirection,features,latency_ms. - Added cumulative IO counters and timing to
PortState. - Structured idle auto‑close event output in
read. - Documentation updated for observability & analytics features.
See ARCHITECTURE.md for an internal systems overview and llms.txt for succinct agent guidance.
License
Dual-licensed under MIT or Apache-2.0.