AI Assistant

MCP Service

The MCP (Model Context Protocol) service (src/services/mcp/) manages connections to external MCP servers, exposing their tools, resources, and prompts to the Claude model. It handles multiple transport types, configuration scopes, authentication, and the full server connection lifecycle.

Transport Types

MCP servers communicate over different transport protocols, defined in types.ts:

stdioStdioClientTransport

Spawns a child process and communicates over stdin/stdout. The most common transport for local servers. Requires command and optional args/env.

sseSSEClientTransport

Server-Sent Events over HTTP. Used for remote servers. Supports custom headers and OAuth authentication.

httpStreamableHTTPClientTransport

Streamable HTTP transport. Similar to SSE but uses the newer MCP streamable HTTP protocol. Supports headers and OAuth.

wsWebSocket

WebSocket transport for persistent bidirectional connections. Supports custom headers.

sdkSdkControlTransport

In-process transport for SDK integrations. References a server by name rather than spawning a process.

Additional internal-only transports exist for IDE integration:

  • sse-ide: SSE transport with IDE metadata (name, platform detection)
  • ws-ide: WebSocket transport with IDE metadata and optional auth token

Configuration Examples

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "@my-org/mcp-server"],
      "env": { "API_KEY": "..." }
    }
  }
}

Config Scopes

Each MCP server configuration carries a ConfigScope indicating where it was defined:

ScopeSourcePriority
local.claude/settings.local.jsonProject-local, gitignored
user~/.claude/settings.jsonUser-global
project.claude/settings.jsonProject-level, committed
dynamicAdded at runtimeProgrammatic
enterpriseEnterprise policyOrganization-managed
claudeaiClaude.ai proxyCloud-provided
managedRemote managed settingsAdmin-controlled

Server configs are typed as ScopedMcpServerConfig which adds the scope field and an optional pluginSource for servers contributed by plugins.

Server Connection States

An MCP server transitions through several states during its lifecycle:

pending --> connected
pending --> failed
pending --> needs-auth
pending --> disabled
connectedConnectedMCPServer

Successfully connected. Exposes client, capabilities, serverInfo, and optional instructions.

failedFailedMCPServer

Connection attempt failed. Stores the error message for display.

needs-authNeedsAuthMCPServer

Server requires OAuth authentication before connecting.

pendingPendingMCPServer

Connection in progress. Tracks reconnectAttempt and maxReconnectAttempts.

disabledDisabledMCPServer

Explicitly disabled by the user.

MCP Client

The client module (client.ts) creates Client instances from the @modelcontextprotocol/sdk and manages the connection lifecycle:

  • Transport creation: Instantiates the appropriate transport based on server config type
  • Tool discovery: Fetches available tools via ListToolsResultSchema and wraps them as MCPTool instances
  • Resource access: Lists and reads server resources via ListResourcesResultSchema
  • Command registration: Fetches server-provided prompts as slash commands
  • Tool name normalization: MCP tool names are normalized to prevent collisions across servers
// Tools, commands, and resources from all connected servers
const { tools, commands, resources } = getMcpToolsCommandsAndResources()

Elicitation Handler

The elicitation handler (elicitationHandler.ts) manages interactive prompts from MCP servers. When a server sends an ElicitRequest, the handler queues it in app state for the UI to render:

formElicitationRequestEvent

Form-based elicitation where the user fills in structured fields.

urlElicitationRequestEvent

URL-based elicitation where the user opens a browser link. Shows a waiting state after the browser opens with retry/cancel options.

The handler also processes ElicitationComplete notifications from the server, marking queued elicitations as completed and triggering the waiting-state dismiss callback.

Channel Permissions

The channel permissions system (channelPermissions.ts) enables permission prompts to be relayed over communication channels (Telegram, iMessage, Discord):

  1. When Claude hits a permission dialog, it sends the prompt via active channel MCP servers
  2. The channel server parses the user's reply (e.g., "yes tbxkq") and emits a structured notifications/claude/channel/permission event
  3. The first resolver (local UI, bridge, hook, classifier, or channel) wins via a claim mechanism

Channel permission relay is gated behind the tengu_harbor_permissions feature flag and requires servers to declare capabilities.experimental['claude/channel/permission'].

Connection Lifecycle

The useManageMCPConnections hook manages the full MCP connection lifecycle within the React REPL:

  1. Config loading: Reads MCP server configs from all scopes via getClaudeCodeMcpConfigs()
  2. Initial connection: Connects to all configured servers in parallel at mount time
  3. Change notifications: Subscribes to ToolListChanged, ResourceListChanged, and PromptListChanged notifications from each server
  4. Reconnection: Automatically reconnects failed servers with configurable retry limits
  5. Server enable/disable: Toggling via setMcpServerEnabled() persists to settings and triggers reconnection
  6. Cleanup: Disconnects all servers on unmount

State Management

MCP state is serialized as MCPCliState in AppState.mcp:

interface MCPCliState {
  clients: SerializedClient[]           // Server connection status
  configs: Record<string, ScopedMcpServerConfig>  // Server configurations
  tools: SerializedTool[]               // Available tools from all servers
  resources: Record<string, ServerResource[]>     // Server resources
  normalizedNames?: Record<string, string>        // Name normalization map
}