You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document provides a comprehensive technical explanation of how the Serial MCP Server operates, including architecture, data flows, state management, and internal mechanisms.
The Serial MCP Server is a Rust-based Model Context Protocol (MCP) server that bridges AI assistants (like Claude) with physical serial port devices. It enables AI systems to discover, connect to, and communicate with embedded hardware such as STM32 microcontrollers, Arduino boards, and other UART-capable devices.
graph TB
subgraph "AI Layer"
AI[AI Assistant<br/>Claude/Other]
end
subgraph "Transport Layer"
STDIO[stdio Transport<br/>stdin/stdout]
end
subgraph "MCP Server"
SH[SerialHandler<br/>Tool Router]
CM[ConnectionManager<br/>Connection Pool]
end
subgraph "Hardware Layer"
SC1[SerialConnection 1]
SC2[SerialConnection 2]
SCN[SerialConnection N]
end
subgraph "Physical Devices"
DEV1[STM32]
DEV2[Arduino]
DEVN[Other Device]
end
AI <-->|JSON-RPC| STDIO
STDIO <-->|MCP Protocol| SH
SH <--> CM
CM <--> SC1
CM <--> SC2
CM <--> SCN
SC1 <-->|UART| DEV1
SC2 <-->|UART| DEV2
SCN <-->|UART| DEVN
Loading
Architecture
Module Structure
graph TD
subgraph "Entry Point"
MAIN[main.rs<br/>CLI & Server Startup]
end
subgraph "Library Core"
LIB[lib.rs<br/>Public API Exports]
end
subgraph "Tools Module"
TM[tools/mod.rs]
TSH[tools/serial_handler.rs<br/>MCP Tool Implementations]
TT[tools/types.rs<br/>Request/Response Types]
end
subgraph "Serial Module"
SM[serial/mod.rs<br/>ConnectionManager]
SC[serial/connection.rs<br/>SerialConnection]
SP[serial/port.rs<br/>PortInfo & Discovery]
SE[serial/error.rs<br/>Serial Errors]
end
subgraph "Session Module"
SESS[session/mod.rs]
SESM[session/manager.rs<br/>SessionManager]
SESH[session/session.rs<br/>SerialSession]
end
subgraph "Support Modules"
CFG[config.rs<br/>Configuration]
ERR[error.rs<br/>Error Types]
UTL[utils.rs<br/>Utilities]
end
MAIN --> LIB
LIB --> TM
LIB --> SM
LIB --> SESS
LIB --> CFG
LIB --> ERR
LIB --> UTL
TM --> TSH
TM --> TT
TSH --> SM
SM --> SC
SM --> SP
SM --> SE
SESS --> SESM
SESS --> SESH
SESM --> SM
Loading
Key Components
Component
Location
Responsibility
SerialHandler
tools/serial_handler.rs
Implements MCP tools, routes requests
ConnectionManager
serial/mod.rs
Manages pool of active connections
SerialConnection
serial/connection.rs
Wraps individual serial port streams
PortInfo
serial/port.rs
Discovers and describes available ports
SessionManager
session/manager.rs
Higher-level session lifecycle management
Config
config.rs
Configuration loading and validation
MCP Protocol Integration
Server Initialization Sequence
sequenceDiagram
participant CLI as Command Line
participant Main as main()
participant Config as Config
participant Handler as SerialHandler
participant RMCP as rmcp SDK
participant Client as MCP Client
CLI->>Main: Start with args
Main->>Config: Load configuration
Config-->>Main: Config validated
Main->>Handler: new(config)
Handler->>Handler: Create ConnectionManager
Handler->>Handler: Build ToolRouter
Main->>RMCP: handler.serve(stdio())
RMCP->>RMCP: Start transport
Client->>RMCP: initialize request
RMCP->>Handler: initialize()
Handler-->>RMCP: ServerInfo + Capabilities
RMCP-->>Client: initialize response
Note over Client,Handler: Server ready for tool calls
Loading
Tool Registration
The server uses the rmcp SDK's macro system for tool registration:
graph LR
subgraph "Macro Expansion"
TR["tool_router macro"]
TM["tool macro"]
TH["tool_handler macro"]
end
subgraph "Generated Code"
ROUTER["ToolRouter: Maps tool names to methods"]
SCHEMA["JSON Schema: Parameter definitions"]
HANDLER["ServerHandler impl: MCP protocol handling"]
end
TR --> ROUTER
TM --> SCHEMA
TH --> HANDLER
Receive data from device or collect a bounded capture window
close
connection_id
Terminate connection
Connection Lifecycle
State Diagram
stateDiagram-v2
[*] --> Discovered: list_ports
Discovered --> Opening: open request
Opening --> Connected: Success
Opening --> Error: Failure
Connected --> Writing: write request
Connected --> Reading: read request
Writing --> Connected: Complete
Reading --> Connected: Data received
Reading --> Timeout: No data
Timeout --> Connected: Continue
Connected --> Closing: close request
Closing --> [*]: Closed
Error --> [*]: Cleanup
note right of Connected: Connection ID assigned<br/>UUID v4 format
note right of Timeout: Returns empty result<br/>Not an error
Loading
Connection Open Process
sequenceDiagram
participant Client as MCP Client
participant Handler as SerialHandler
participant CM as ConnectionManager
participant SC as SerialConnection
participant Port as Physical Port
Client->>Handler: open(port, baud_rate, ...)
Handler->>Handler: Convert OpenArgs to ConnectionConfig
Handler->>CM: open(config)
CM->>CM: Check for existing connection to port
alt Port already in use
CM-->>Handler: Error: ConnectionExists
Handler-->>Client: Error response
else Port available
CM->>SC: new(config)
SC->>SC: Validate baud rate 0 to 4M
SC->>Port: tokio_serial::new().open_native_async()
alt Port opens successfully
Port-->>SC: SerialStream
SC->>SC: Generate UUID connection_id
SC-->>CM: SerialConnection
CM->>CM: Store in HashMap
CM-->>Handler: connection_id
Handler-->>Client: Success with connection_id
else Port fails to open
Port-->>SC: Error
SC-->>CM: ConnectionFailed error
CM-->>Handler: Error
Handler-->>Client: Error response
end
end
Loading
Connection Configuration
graph TD
subgraph "Serial Parameters"
BR[Baud Rate<br/>300 - 921600]
DB[Data Bits<br/>5, 6, 7, 8]
SB[Stop Bits<br/>1, 2]
PA[Parity<br/>None, Odd, Even]
FC[Flow Control<br/>None, Software, Hardware]
end
subgraph "Defaults"
D1[115200 baud]
D2[8 data bits]
D3[1 stop bit]
D4[No parity]
D5[No flow control]
end
BR --- D1
DB --- D2
SB --- D3
PA --- D4
FC --- D5
Loading
Data Flow
Write Operation
sequenceDiagram
participant Client as MCP Client
participant Handler as SerialHandler
participant CM as ConnectionManager
participant SC as SerialConnection
participant Stream as SerialStream
participant Device as Serial Device
Client->>Handler: write(connection_id, data, encoding)
Handler->>CM: get(connection_id)
alt Connection found
CM-->>Handler: Arc SerialConnection
Handler->>Handler: decode_data(data, encoding)
alt Decoding successful
Handler->>SC: write(bytes)
SC->>SC: Lock stream mutex
SC->>Stream: AsyncWriteExt write(bytes)
Stream->>Device: TX bytes
Device-->>Stream: Bytes sent
Stream-->>SC: bytes_written count
SC->>SC: Update bytes_sent counter
SC->>Stream: flush()
SC-->>Handler: bytes_written
Handler-->>Client: Success response
else Decoding failed
Handler-->>Client: Encoding error
end
else Connection not found
CM-->>Handler: InvalidConnection error
Handler-->>Client: Error response
end
Loading
Read Operation
sequenceDiagram
participant Client as MCP Client
participant Handler as SerialHandler
participant CM as ConnectionManager
participant SC as SerialConnection
participant Stream as SerialStream
participant Device as Serial Device
Client->>Handler: read(connection_id, timeout_ms, max_bytes, encoding, duration_ms?)
Handler->>CM: get(connection_id)
alt Connection found
CM-->>Handler: Arc SerialConnection
alt duration_ms absent
Handler->>SC: read(buffer, timeout_ms)
else duration_ms present
Handler->>SC: capture(config)
end
SC->>SC: Lock stream mutex
alt Single read with timeout
SC->>SC: tokio time timeout(duration, read)
alt Data available before timeout
Stream->>Device: RX request
Device-->>Stream: bytes
Stream-->>SC: bytes_read
SC->>SC: Update bytes_received counter
SC-->>Handler: buffer slice
else Timeout elapsed
SC-->>Handler: ReadTimeout error
Handler-->>Client: Timeout response (not error)
end
else Single read without timeout
Stream->>Device: RX request (blocking)
Device-->>Stream: bytes
Stream-->>SC: bytes_read
SC-->>Handler: buffer slice
else Capture window
alt start_trigger is first_byte
SC->>SC: wait up to initial_timeout_ms for first bytes
else start_trigger is immediate
SC->>SC: start duration clock immediately
end
loop until duration, idle timeout, or max bytes
SC->>SC: timed serial read
Device-->>Stream: bytes or timeout
SC->>SC: append chunk metadata
end
SC-->>Handler: combined buffer and capture metadata
end
Handler->>Handler: encode_data(buffer, encoding)
Handler-->>Client: Success with encoded data
else Connection not found
CM-->>Handler: InvalidConnection error
Handler-->>Client: Error response
end
Loading
Data Encoding Pipeline
graph LR
subgraph "Input (Write)"
WI[String Data]
WE{Encoding?}
WU[UTF-8: as_bytes]
WH[Hex: decode pairs]
WB[Base64: decode]
WO[Byte Array]
end
subgraph "Output (Read)"
RI[Byte Array]
RE{Encoding?}
RU[UTF-8: from_utf8]
RH[Hex: spaced pairs]
RB[Base64: encode]
RO[String Data]
end
WI --> WE
WE -->|utf8| WU --> WO
WE -->|hex| WH --> WO
WE -->|base64| WB --> WO
RI --> RE
RE -->|utf8| RU --> RO
RE -->|hex| RH --> RO
RE -->|base64| RB --> RO
sequenceDiagram
participant C1 as Client 1
participant C2 as Client 2
participant CM as ConnectionManager
participant Conn as Connection
par Client 1 reads
C1->>CM: get(conn_id)
CM-->>C1: Arc Connection
C1->>Conn: read()
Note over Conn: Mutex locked
Conn-->>C1: data
and Client 2 writes
C2->>CM: get(conn_id)
CM-->>C2: Arc Connection
C2->>Conn: write()
Note over Conn: Waits for mutex
end
Note over Conn: C1 releases mutex
Conn->>Conn: C2 acquires mutex
Conn-->>C2: bytes_written
Loading
Session Management
Session State Machine
stateDiagram-v2
[*] --> Creating: new()
Creating --> Active: set_connection()
Active --> Suspended: remove_connection()
Active --> Error: set_error()
Suspended --> Active: set_connection()
Suspended --> Closing: close()
Error --> Closing: close()
Active --> Closing: close()
Closing --> Closed: cleanup complete
Closed --> [*]
note right of Creating: Session ID generated<br/>Config stored
note right of Active: Has SerialConnection<br/>Ready for I/O
note right of Suspended: Disconnected<br/>Can reconnect
note right of Error: Error recorded<br/>May auto-reconnect
graph LR
subgraph "Per-Session Stats"
BS[bytes_sent]
BR[bytes_received]
MS[messages_sent]
MR[messages_received]
EC[errors_count]
RC[reconnections]
LA[last_activity]
end
subgraph "Aggregate Stats"
TBS[total_bytes_sent]
TBR[total_bytes_received]
AS[active_sessions]
CS[connected_sessions]
ES[error_sessions]
end
BS --> TBS
BR --> TBR
MS --> AS
MR --> CS
EC --> ES
Loading
Error Handling
Error Hierarchy
graph TD
subgraph "Main Error Type"
SE[SerialError]
end
subgraph "Connection Errors"
PNF[PortNotFound]
CF[ConnectionFailed]
IC[InvalidConnection]
CE[ConnectionExists]
CLE[ConnectionLimitExceeded]
end
subgraph "Communication Errors"
OT[OperationTimeout]
RT[ReadTimeout]
WT[WriteTimeout]
COMM[CommunicationError]
PE[ProtocolError]
BO[BufferOverflow]
end
subgraph "Configuration Errors"
IVC[InvalidConfig]
IBR[InvalidBaudRate]
IDB[InvalidDataBits]
ISB[InvalidStopBits]
IP[InvalidParity]
IFC[InvalidFlowControl]
end
subgraph "Encoding Errors"
ENC[EncodingError]
UTF[Utf8Error]
HEX[HexError]
B64[Base64Error]
end
SE --> PNF
SE --> CF
SE --> IC
SE --> CE
SE --> CLE
SE --> OT
SE --> RT
SE --> WT
SE --> COMM
SE --> PE
SE --> BO
SE --> IVC
SE --> IBR
SE --> IDB
SE --> ISB
SE --> IP
SE --> IFC
SE --> ENC
SE --> UTF
SE --> HEX
SE --> B64
sequenceDiagram
participant Client as MCP Client
participant Handler as SerialHandler
participant PortInfo as PortInfo
participant SerialPort as serialport crate
participant OS as Operating System
Client->>Handler: list_ports()
Handler->>PortInfo: list_ports()
PortInfo->>SerialPort: available_ports()
SerialPort->>OS: Enumerate serial devices
OS-->>SerialPort: Device list
loop For each port
SerialPort-->>PortInfo: SerialPortInfo
PortInfo->>PortInfo: Extract hardware_id
alt USB Port
PortInfo->>PortInfo: Format VID/PID
else PCI Port
PortInfo->>PortInfo: Mark as PCI
else Bluetooth
PortInfo->>PortInfo: Mark as Bluetooth
else Unknown
PortInfo->>PortInfo: Mark as Unknown
end
PortInfo->>PortInfo: Get description
end
PortInfo-->>Handler: Vec of PortInfo
Handler->>Handler: Format port list string
Handler-->>Client: CallToolResult with port info
Loading
Connection ID Management
graph LR
subgraph "ID Generation"
UUID["UUID v4"]
GEN["Uuid::new_v4()"]
STR["to_string()"]
end
subgraph "Storage"
MAP["HashMap: String to Arc Connection"]
end
subgraph "Lookup"
GET["get(id)"]
FOUND{"Found?"}
RET["Return Arc clone"]
ERR["InvalidConnection"]
end
GEN --> STR
STR --> MAP
GET --> MAP
MAP --> FOUND
FOUND -->|Yes| RET
FOUND -->|No| ERR
Loading
Shutdown Sequence
sequenceDiagram
participant Signal as SIGINT/SIGTERM
participant Main as main()
participant Service as MCP Service
participant Handler as SerialHandler
participant CM as ConnectionManager
participant Conns as Connections
Signal->>Main: Ctrl+C or shutdown
Main->>Main: tokio select triggered
alt Service completed
Service-->>Main: waiting() returned
else Signal received
Signal-->>Main: ctrl_c() triggered
end
Main->>Main: Log Cleaning up resources
Main->>Handler: connection_manager()
Handler-->>Main: Arc ConnectionManager
Main->>CM: close_all()
CM->>CM: Write lock connections
loop For each connection
CM->>Conns: Remove from HashMap
Note over Conns: Connection dropped
end
CM-->>Main: closed_count
Main->>Main: Log shutdown complete
Main-->>Signal: Exit 0