MCP Transports¶
Communication methods for MCP client-server connections.
Overview¶
Transports handle the low-level communication between MCP::Client and external MCP servers. All transports implement the same interface defined by Transports::Base, using JSON-RPC 2.0 for message exchange and the MCP protocol version 2024-11-05 for initialization.
RobotLab provides four built-in transport types:
| Transport | Class | Use Case |
|---|---|---|
| Stdio | Transports::Stdio |
Local subprocess servers |
| WebSocket | Transports::WebSocket |
Real-time bidirectional |
| SSE | Transports::SSE |
Server-sent events |
| Streamable HTTP | Transports::StreamableHTTP |
HTTP with session support |
Base Interface¶
All transports inherit from RobotLab::MCP::Transports::Base and implement:
class RobotLab::MCP::Transports::Base
attr_reader :config # => Hash (symbolized keys)
def connect # Establish connection, returns self
def send_request(message) # Send JSON-RPC message, returns Hash response
def close # Close connection, returns self
def connected? # Returns Boolean
end
Stdio Transport¶
Class: RobotLab::MCP::Transports::Stdio
Spawns a subprocess and communicates via stdin/stdout using JSON-RPC messages (one per line). Automatically sends MCP initialize and notifications/initialized on connect.
Configuration¶
{
type: "stdio",
command: "mcp-server-filesystem", # Required: executable command
args: ["--root", "/data"], # Optional: command arguments
env: { "DEBUG" => "true" } # Optional: environment variables
}
| Key | Type | Required | Description |
|---|---|---|---|
command |
String |
Yes | Executable command to spawn |
args |
Array<String> |
No | Command arguments |
env |
Hash |
No | Environment variables (merged with current env) |
Behavior¶
- Uses
Open3.popen3to spawn the subprocess - Writes JSON-RPC messages to stdin (one per line)
- Reads responses from stdout, skipping notifications (messages without
id) connected?returnstruewhen the subprocess is alivecloseterminates stdin, stdout, stderr, and kills the subprocess
Example¶
transport = RobotLab::MCP::Transports::Stdio.new(
command: "mcp-server-filesystem",
args: ["--root", "/data"],
env: { "DEBUG" => "true" }
)
transport.connect
response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
transport.close
WebSocket Transport¶
Class: RobotLab::MCP::Transports::WebSocket
Uses async-websocket for non-blocking bidirectional communication. Requires the async-websocket gem.
Configuration¶
| Key | Type | Required | Description |
|---|---|---|---|
url |
String |
Yes | WebSocket endpoint URL |
Behavior¶
- Uses
Async::WebSocket::Client.connectwithin anAsyncblock - Sends JSON-RPC messages as JSON strings
- Reads responses synchronously within the async context
- Raises
MCPErrorif theasync-websocketgem is not installed
Example¶
transport = RobotLab::MCP::Transports::WebSocket.new(
url: "ws://localhost:8080"
)
transport.connect
response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
transport.close
SSE Transport¶
Class: RobotLab::MCP::Transports::SSE
Uses async-http for HTTP-based communication. Sends requests via HTTP POST and reads responses. Requires the async-http gem.
Configuration¶
| Key | Type | Required | Description |
|---|---|---|---|
url |
String |
Yes | SSE/HTTP endpoint URL |
Behavior¶
- Creates an
Async::HTTP::Clienton connect - Sends JSON-RPC messages via HTTP POST with
Content-Type: application/json - Reads and parses JSON response body
- Raises
MCPErrorif theasync-httpgem is not installed
Example¶
transport = RobotLab::MCP::Transports::SSE.new(
url: "http://localhost:8080/sse"
)
transport.connect
response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
transport.close
Streamable HTTP Transport¶
Class: RobotLab::MCP::Transports::StreamableHTTP
HTTP-based transport with session management and optional authentication. Supports session IDs for maintaining server-side state across requests. Requires the async-http gem.
Configuration¶
{
type: "streamable-http", # or "http"
url: "https://server.smithery.ai/neon/mcp", # Required: HTTP endpoint
session_id: "abc123", # Optional: session identifier
auth_provider: -> { "Bearer #{token}" } # Optional: auth callback
}
| Key | Type | Required | Description |
|---|---|---|---|
url |
String |
Yes | HTTP endpoint URL |
session_id |
String |
No | Pre-existing session identifier |
auth_provider |
Proc |
No | Callback returning Authorization header value |
Behavior¶
- Creates an
Async::HTTP::Clienton connect - Sends MCP
initializeon connect; if no session ID was provided, extracts it from the server response (serverInfo.sessionId) - Sends
X-Session-IDheader with each request when a session ID is available - Calls
auth_providerfor each request to populate theAuthorizationheader - Exposes
session_idreader for accessing the current session ID - Raises
MCPErrorif theasync-httpgem is not installed
Example¶
transport = RobotLab::MCP::Transports::StreamableHTTP.new(
url: "https://server.smithery.ai/neon/mcp",
auth_provider: -> { "Bearer #{ENV['MCP_TOKEN']}" }
)
transport.connect
puts transport.session_id # => assigned by server or pre-configured
response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
transport.close
Connection Lifecycle¶
All transports follow the same lifecycle:
- Create -- instantiate with configuration hash
- Connect -- establish connection and perform MCP protocol initialization
- Request/Response -- send JSON-RPC requests, receive responses
- Close -- tear down connection and release resources
Each transport sends the MCP initialize message during connect:
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "RobotLab",
"version": "<current version>"
}
}
}
Error Handling¶
All transports raise RobotLab::MCPError for connection and communication failures:
begin
transport.connect
transport.send_request(message)
rescue RobotLab::MCPError => e
puts "Transport error: #{e.message}"
ensure
transport.close
end
Specific error cases:
- Not connected -- calling send_request before connect raises MCPError
- Missing gem -- WebSocket, SSE, and HTTP transports raise MCPError with a LoadError message if required gems are not installed
- No response -- Stdio transport raises MCPError if the subprocess produces no output