How to: Integrate MCP Servers

Using the standalone synwire-mcp-server

The easiest way to expose Synwire tools to an MCP host (Claude Code, GitHub Copilot, Cursor) is the standalone synwire-mcp-server binary.

Install

cargo install synwire-mcp-server

Configure Claude Code

Add to .claude/mcp.json in your project or home directory:

{
  "synwire": {
    "command": "synwire-mcp-server",
    "args": ["--project", "."]
  }
}

With LSP support:

{
  "synwire": {
    "command": "synwire-mcp-server",
    "args": ["--project", ".", "--lsp", "rust-analyzer"]
  }
}

Config file (alternative to CLI flags)

Create synwire.toml in your project root:

project = "."
product_name = "myapp"
lsp = "rust-analyzer"
embedding_model = "bge-small-en-v1.5"
log_level = "info"
{
  "synwire": {
    "command": "synwire-mcp-server",
    "args": ["--config", "synwire.toml"]
  }
}

CLI flags override config file values. See the synwire-mcp-server explanation for all available flags.


Goal: Connect to Model Context Protocol servers via stdio, HTTP, or in-process transports, and manage multiple servers through McpLifecycleManager.


Core trait: McpTransport

All transports implement:

#![allow(unused)]
fn main() {
pub trait McpTransport: Send + Sync {
    fn connect(&self)    -> BoxFuture<'_, Result<(), AgentError>>;
    fn reconnect(&self)  -> BoxFuture<'_, Result<(), AgentError>>;
    fn disconnect(&self) -> BoxFuture<'_, Result<(), AgentError>>;
    fn status(&self)     -> BoxFuture<'_, McpServerStatus>;
    fn list_tools(&self) -> BoxFuture<'_, Result<Vec<McpToolDescriptor>, AgentError>>;
    fn call_tool(&self, tool_name: &str, arguments: Value)
        -> BoxFuture<'_, Result<Value, AgentError>>;
}
}

McpServerStatus carries the server name, McpConnectionState, call success/failure counters, and an enabled flag.

McpConnectionState progression: Disconnected → Connecting → Connected. After a drop: Connected → Reconnecting → Connected (or back to Disconnected on failure). Shutdown is terminal.


StdioMcpTransport

Manages a subprocess and exchanges newline-delimited JSON-RPC over its stdin/stdout.

#![allow(unused)]
fn main() {
use synwire_agent::mcp::stdio::StdioMcpTransport;
use synwire_core::mcp::traits::McpTransport;
use std::collections::HashMap;

let transport = StdioMcpTransport::new(
    "my-mcp-server",
    "npx",
    vec!["-y".to_string(), "@modelcontextprotocol/server-filesystem".to_string()],
    HashMap::from([
        ("MCP_WORKSPACE".to_string(), "/home/user/project".to_string()),
    ]),
);

transport.connect().await?;

let tools = transport.list_tools().await?;
for t in &tools {
    println!("{}: {}", t.name, t.description);
}

let result = transport.call_tool("read_file", serde_json::json!({
    "path": "/home/user/project/README.md"
})).await?;

transport.disconnect().await?;
}

Reconnecting kills the existing subprocess and spawns a new one:

#![allow(unused)]
fn main() {
transport.reconnect().await?;
}

HttpMcpTransport

Connects to an HTTP-based MCP server. The connect call performs a health check by issuing POST /tools/list.

#![allow(unused)]
fn main() {
use synwire_agent::mcp::http::HttpMcpTransport;

let transport = HttpMcpTransport::new(
    "remote-mcp",
    "https://mcp.example.com",
    Some("Bearer sk-...".to_string()),
    Some(30),  // timeout_secs; None defaults to 30
);

transport.connect().await?;

let tools = transport.list_tools().await?;
let result = transport.call_tool("search", serde_json::json!({"query": "rust async"})).await?;
}

InProcessMcpTransport

Registers native Tool implementations and exposes them via the McpTransport interface without any subprocess or network hop. Useful for built-in toolsets that benefit from the MCP lifecycle API.

#![allow(unused)]
fn main() {
use synwire_agent::mcp::in_process::InProcessMcpTransport;
use std::sync::Arc;

let mut transport = InProcessMcpTransport::new("builtin-tools");

// Register any type that implements `synwire_core::tools::Tool`.
transport.register(Arc::new(MyCustomTool)).await;

transport.connect().await?;  // transitions to Connected immediately

let tools = transport.list_tools().await?;
let result = transport.call_tool("my_tool", serde_json::json!({"input": "value"})).await?;
}

reconnect on an in-process transport is equivalent to connect — it simply marks the state as Connected.


McpLifecycleManager

Manages multiple transports: connects all at start, monitors health, and reconnects dropped servers in the background.

#![allow(unused)]
fn main() {
use synwire_agent::mcp::lifecycle::McpLifecycleManager;
use std::sync::Arc;
use std::time::Duration;

let manager = Arc::new(McpLifecycleManager::new());

// Register servers with individual reconnect delays.
manager.register("filesystem", StdioMcpTransport::new(/* ... */), Duration::from_secs(5)).await;
manager.register("web-search",  HttpMcpTransport::new(/* ... */), Duration::from_secs(10)).await;

// Connect all enabled servers.
manager.start_all().await?;

// Spawn background health monitor (polls every 30 s, reconnects as needed).
Arc::clone(&manager).spawn_health_monitor(Duration::from_secs(30));

// Call a tool on a named server — auto-reconnects if needed.
let result = manager.call_tool("filesystem", "read_file", serde_json::json!({
    "path": "/project/src/main.rs"
})).await?;

// Inspect status of all servers.
let statuses = manager.all_status().await;
for s in &statuses {
    println!("{}: {:?} ok={} fail={}", s.name, s.state, s.calls_succeeded, s.calls_failed);
}

// Disable a server at runtime.
manager.disable("web-search").await?;

// Re-enable and reconnect.
manager.enable("web-search").await?;

// Shutdown.
manager.stop_all().await?;
}

Calling call_tool on a disabled server returns AgentError::Backend immediately without attempting to reconnect.


See also