What can we help you with?
MCP Client for Tauri
MCP Client for Tauri
Instructions
Implement a Model Context Protocol client in a Tauri desktop application, bridging the Rust backend (protocol handling) with the React/TypeScript frontend (UI rendering).
MCP Protocol Overview
MCP uses JSON-RPC 2.0 over stdio or SSE transport. The client lifecycle:
- Initialize: Send
initializerequest with client capabilities - Negotiate: Server responds with its capabilities (tools, resources, prompts)
- Discover: List available tools via
tools/list, resources viaresources/list - Execute: Call tools via
tools/call, read resources viaresources/read - Stream: Handle streaming responses for long-running operations
- Shutdown: Send
notifications/cancelledor close the transport
Rust Backend Implementation
The Rust side manages transport, protocol state, and process lifecycle:
Transport layer:
- For stdio-based MCP servers: spawn the server process with
tokio::process::Command, pipe stdin/stdout - For SSE-based servers: use
reqwestwith SSE streaming to the server endpoint - Wrap the transport in a trait so stdio and SSE share the same interface
Protocol handler:
- Parse JSON-RPC messages using
serde_jsonwith typed request/response structs - Maintain a pending request map (
HashMap<RequestId, oneshot::Sender>) for correlating responses - Handle notifications (server-initiated messages with no ID) separately from responses
- Implement automatic reconnection with exponential backoff for SSE transport
Process management:
- Track spawned MCP server processes by PID
- Implement graceful shutdown: send
notifications/cancelled, wait 5s, then SIGTERM, then SIGKILL - Clean up child processes on Tauri app exit via a shutdown hook
- Handle server crashes: detect broken pipe on stdio, trigger reconnect
#[tauri::command]
async fn mcp_initialize(
state: tauri::State<'_, McpManager>,
server_id: String,
config: McpServerConfig,
) -> Result<ServerCapabilities, McpError> {
state.connect_and_initialize(&server_id, &config).await
}
#[tauri::command]
async fn mcp_list_tools(
state: tauri::State<'_, McpManager>,
server_id: String,
) -> Result<Vec<ToolDefinition>, McpError> {
state.list_tools(&server_id).await
}
#[tauri::command]
async fn mcp_call_tool(
state: tauri::State<'_, McpManager>,
server_id: String,
tool_name: String,
arguments: serde_json::Value,
) -> Result<ToolResult, McpError> {
state.call_tool(&server_id, &tool_name, arguments).await
}
Capability Negotiation
During initialization, declare client capabilities:
{
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": {
"name": "personal-assistant",
"version": "1.0.0"
}
}
Parse the server’s capability response to determine:
- Which tools are available and their input schemas
- Which resources can be read and their URI templates
- Whether the server supports prompts, logging, or completion
Tool Discovery and Schema Handling
- Cache the
tools/listresponse per server — re-fetch only when the server sends anotifications/tools/list_changednotification - Parse each tool’s
inputSchema(JSON Schema) to dynamically generate UI forms - Validate tool call arguments against the schema before sending to the server
- Display tool descriptions to help users understand what each tool does
Streaming Response Handling
For tools that produce streaming output:
- Use Tauri’s event system (
app_handle.emit()) to push partial results to the frontend - Frontend listens with
listen('mcp-stream-chunk', callback)and appends to a buffer - Handle
contentblocks progressively: text blocks can be displayed immediately, image blocks need base64 decoding - Implement cancellation: user can cancel a long-running tool call, which sends
notifications/cancelledto the server
Frontend Integration (React/TypeScript)
interface McpTool {
name: string;
description: string;
inputSchema: JSONSchema;
}
interface McpToolResult {
content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>;
isError?: boolean;
}
- Build a tool browser UI: list available tools grouped by server, show descriptions and input schemas
- Generate dynamic forms from JSON Schema input definitions
- Display tool results with appropriate rendering (text as markdown, images inline)
- Show server connection status indicators (connected, disconnected, reconnecting)
Multi-Server Management
- Support multiple MCP servers running concurrently, each with its own transport and state
- Store server configurations in the app’s SQLite database or a JSON config file
- Provide a UI for adding, removing, and configuring MCP servers
- Namespace tool names to avoid collisions:
{server_id}.{tool_name}
Error Handling
| Error | Handling |
|---|---|
| Server process crashes | Detect broken pipe, auto-reconnect, notify user |
| Tool call timeout (>30s) | Cancel the request, return timeout error to UI |
| Invalid tool arguments | Validate against schema before sending, show validation errors in UI |
| Server returns error response | Display the error message and code, do not retry automatically |
| Transport disconnection | Reconnect with backoff, queue pending requests for retry |
Inputs Required
- MCP server configuration: command to spawn (stdio) or URL (SSE), environment variables, working directory
- Client identity: application name and version for the initialize handshake
- Server capability requirements: which features the app needs from the server
Output Format
- Initialized MCP client connection with negotiated capabilities
- Tool catalog with names, descriptions, and input schemas per server
- Tool execution results as structured content blocks (text, image, resource)
- Connection status events for UI display
- Error reports with context for debugging
Anti-Patterns
- Blocking the main thread on MCP calls: All MCP communication must be async — blocking the Tauri main thread freezes the UI
- Not cleaning up server processes: Spawned MCP server processes must be killed on app exit — leaked processes consume resources indefinitely
- Trusting tool results without error checking: Always check
isErroron tool results — a successful HTTP response can still contain a tool-level error - Rebuilding tool list on every call: Cache tool definitions and only refresh on
tools/list_changednotifications — listing tools is a round-trip that adds latency - Hardcoding server configurations: Server configs should be user-editable — hardcoding prevents users from adding their own MCP servers
- Ignoring protocol version negotiation: The server may support a different protocol version — always check
protocolVersionin the initialize response and handle version mismatches
