How MCP Plugins Are Integrated with Stdio and HTTP Transports in DeepSeek-Reasonix
Reasonix selects between stdio subprocesses and HTTP clients based on the Spec.Type field, then unifies both transports under a common interface that exposes tools, prompts, and resources to the host application.
The DeepSeek-Reasonix repository implements a modular plugin system that consumes external MCP (Model Context Protocol) servers as native extensions. Whether connecting to a local analysis tool via standard input/output or a remote service over HTTP, the integration follows a consistent pattern defined in the internal/plugin package. This architecture allows Reasonix to discover and execute tools from diverse sources without modifying core application logic.
The Spec Struct and Transport Selection
The integration entry point is the Spec struct defined in internal/plugin/plugin.go. This configuration object determines which transport implementation to instantiate based on the Type field.
When Host.StartSingle initializes a plugin, it evaluates the spec type through a switch statement:
switch strings.ToLower(spec.Type) {
case "", "stdio":
tr, err = newStdioTransport(ctx, spec)
case "http", "streamable-http", "sse":
tr, err = newHTTPTransport(spec)
default:
return fmt.Errorf("unknown MCP transport %q", spec.Type)
}
- stdio: Spawns a subprocess communicating via JSON-RPC over newline-delimited
stdin/stdout. - http/streamable-http/sse: Constructs an HTTP client for POST-based JSON-RPC or Server-Sent Events streaming.
Both implementations satisfy the internal transport interface, exposing call, notify, and close methods that the higher-level client uses to perform MCP operations.
Stdio Transport Implementation
The stdio transport, implemented in internal/plugin/transport_stdio.go, manages subprocess lifecycle and message framing for local MCP servers.
Process Launch and Environment
The resolveStdioExecutable function locates the target binary by enriching the child process PATH through the user's login shell. The transport then constructs the command using exec.CommandContext with configurable Command, Args, Env, and Dir fields from the spec:
cmd := exec.CommandContext(ctx, spec.Command, spec.Args...)
cmd.Dir = spec.Dir
cmd.Env = os.Environ()
for k, v := range spec.Env {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
}
Setting spec.LowPriority = true reduces the subprocess OS scheduling priority, suitable for background indexing tasks.
JSON-RPC Framing and Cancellation
Message exchange follows newline-delimited JSON-RPC:
- Writing: Requests are serialized and written with a newline terminator to
stdin. - Reading: A dedicated goroutine scans
stdoutline-by-line, routing responses to pending call channels based on JSON-RPCid. - Timeouts: The transport applies
defaultCallTimeoutwhen the caller's context lacks a deadline, preventing hung processes from blocking the host.
Errors from missing executables, startup failures, or premature EOF propagate as formatted errors with captured stderr diagnostics.
HTTP Transport Implementation
The HTTP transport in internal/plugin/transport_http.go handles remote MCP servers using standard HTTP POST requests with optional SSE streaming support.
Request Construction and Session Management
The do method builds requests with specific headers:
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json, text/event-stream")
// Static headers from spec
for k, v := range spec.Headers {
req.Header.Set(k, v)
}
// Session tracking
if t.sessionID != "" {
req.Header.Set("Mcp-Session-Id", t.sessionID)
}
The captureSession function extracts the Mcp-Session-Id header from responses, maintaining stateful connections across subsequent calls. Session expiration (HTTP 404 with RPC error code -32001) triggers httpSessionExpiredError.
Response Handling Strategies
The transport automatically detects response types:
- JSON Responses: When
Content-Typeisapplication/json,decodeRPCResultparses the RPC result directly. - SSE Streams: For
text/event-stream,readSSEResponsescans Server-Sent Events, extracting the JSON-RPC response matching the requestidwhile discarding interleaved notifications.
Non-2xx status codes wrap into descriptive errors including the server name and invoked method.
Client Integration and Host Aggregation
Once instantiated, the transport feeds into an MCP client that performs the protocol handshake (initialize) and capability discovery (tools/list, prompts/list, resources/list).
The client stores discovered capabilities in the Host manager:
- Tool Registration: Tools are namespaced with the
mcp__prefix and registered ininternal/tool/registry.go, appearing alongside built-in Reasonix tools. - Prompt/Resource Exposure:
Host.Prompts()andHost.Resources()aggregate items from all active clients, making them available to the UI as slash commands and@references. - Failure Tracking: Startup errors populate
Host.failures, allowing graceful degradation when individual plugins fail to initialize.
All tool invocations route through transport.call for requests or transport.notify for one-way messages, abstracting the underlying communication mechanism from the business logic.
Practical Configuration Examples
These snippets demonstrate real-world plugin configurations.
Stdio plugin with Node.js server:
spec := plugin.Spec{
Name: "mcp__mycodegraph",
Type: "stdio",
Command: "node",
Args: []string{"./codegraph.js"},
Env: map[string]string{"NODE_ENV": "production"},
Dir: "/path/to/project",
}
if err := host.StartSingle(context.Background(), spec); err != nil {
log.Fatalf("failed to start stdio MCP: %v", err)
}
result, err := host.CallTool(ctx, "mycodegraph__search", map[string]any{
"query": "authentication",
})
HTTP plugin with authentication:
spec := plugin.Spec{
Name: "mcp__searchsvc",
Type: "http",
URL: "https://search.example.com/mcp",
Headers: map[string]string{
"Authorization": "Bearer <token>",
},
}
if err := host.StartSingle(context.Background(), spec); err != nil {
log.Fatalf("failed to start HTTP MCP: %v", err)
}
out, err := host.CallTool(ctx, "searchsvc__lookup", map[string]any{
"term": "golang",
})
Low-priority background indexer:
spec.LowPriority = true // Reduces OS scheduling priority
Streamable HTTP with SSE:
spec.Type = "streamable-http"
spec.URL = "https://graph.example.com/mcp"
// transport_http.go automatically handles SSE parsing
Summary
- Transport Selection: Reasonix uses the
Spec.Typefield to instantiate eithernewStdioTransportornewHTTPTransport, with stdio serving as the default. - Stdio Mechanics: The stdio transport manages subprocess lifecycle through
exec.CommandContext, implements newline-delimited JSON-RPC framing, and supports cancellation via context deadlines. - HTTP Mechanics: The HTTP transport handles POST-based JSON-RPC, maintains sessions via the
Mcp-Session-Idheader, and transparently parses both single JSON responses and SSE streams. - Unified Interface: Both transports implement the
transportinterface (call,notify,close), allowing theHostmanager to aggregate tools, prompts, and resources without transport-specific logic. - Key Files:
internal/plugin/plugin.godefines the spec and host;internal/plugin/transport_stdio.goandinternal/plugin/transport_http.gocontain transport implementations;internal/tool/registry.goregisters discovered capabilities.
Frequently Asked Questions
What happens if the transport type is unspecified in the plugin spec?
When spec.Type is an empty string, Reasonix defaults to the stdio transport. The switch statement in the host initialization code treats empty strings and explicit "stdio" values identically, launching a subprocess using the command-line configuration provided in the spec.
How does Reasonix authenticate with HTTP-based MCP servers?
Authentication credentials pass through the Headers field of the Spec struct. The HTTP transport copies these static headers into every request via the do method in transport_http.go, allowing Bearer tokens, API keys, or custom authentication schemes to reach the remote endpoint.
Can stdio MCP plugins run with reduced system priority?
Yes. Setting spec.LowPriority = true configures the subprocess to run with reduced OS scheduling priority. This is implemented in the stdio transport startup logic and is particularly useful for background tasks like code indexing that should not interfere with interactive performance.
How does the HTTP transport distinguish between standard JSON responses and SSE streams?
The transport inspects the Content-Type response header. If it matches application/json, the transport calls decodeRPCResult to parse the body directly. If it is text/event-stream, the transport invokes readSSEResponse to scan the event stream and extract the JSON-RPC response matching the request ID, discarding interleaved notification events.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →