Skip to main content
< All Topics
Print

MCP Client for Tauri

name: mcp-client-tauri

description: MCP (Model Context Protocol) client implementation for Tauri/Rust desktop applications. Protocol handshake, tool discovery, capability negotiation, and streaming response handling in a Rust + TypeScript hybrid environment. Use when integrating MCP servers into Tauri apps, implementing tool discovery UIs, or handling MCP streaming in a desktop context.

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:

  1. Initialize: Send initialize request with client capabilities
  2. Negotiate: Server responds with its capabilities (tools, resources, prompts)
  3. Discover: List available tools via tools/list, resources via resources/list
  4. Execute: Call tools via tools/call, read resources via resources/read
  5. Stream: Handle streaming responses for long-running operations
  6. Shutdown: Send notifications/cancelled or 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 reqwest with 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_json with 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/list response per server — re-fetch only when the server sends a notifications/tools/list_changed notification
  • 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 content blocks 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/cancelled to 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 isError on 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_changed notifications — 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 protocolVersion in the initialize response and handle version mismatches
Table of Contents