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:
Spawns a child process and communicates over stdin/stdout. The most common transport for local servers. Requires command and optional args/env.
Server-Sent Events over HTTP. Used for remote servers. Supports custom headers and OAuth authentication.
Streamable HTTP transport. Similar to SSE but uses the newer MCP streamable HTTP protocol. Supports headers and OAuth.
WebSocket transport for persistent bidirectional connections. Supports custom headers.
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:
| Scope | Source | Priority |
|---|---|---|
local | .claude/settings.local.json | Project-local, gitignored |
user | ~/.claude/settings.json | User-global |
project | .claude/settings.json | Project-level, committed |
dynamic | Added at runtime | Programmatic |
enterprise | Enterprise policy | Organization-managed |
claudeai | Claude.ai proxy | Cloud-provided |
managed | Remote managed settings | Admin-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 --> disabledSuccessfully connected. Exposes client, capabilities, serverInfo, and optional instructions.
Connection attempt failed. Stores the error message for display.
Server requires OAuth authentication before connecting.
Connection in progress. Tracks reconnectAttempt and maxReconnectAttempts.
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
ListToolsResultSchemaand wraps them asMCPToolinstances - 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:
Form-based elicitation where the user fills in structured fields.
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):
- When Claude hits a permission dialog, it sends the prompt via active channel MCP servers
- The channel server parses the user's reply (e.g., "yes tbxkq") and emits a structured
notifications/claude/channel/permissionevent - 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:
- Config loading: Reads MCP server configs from all scopes via
getClaudeCodeMcpConfigs() - Initial connection: Connects to all configured servers in parallel at mount time
- Change notifications: Subscribes to
ToolListChanged,ResourceListChanged, andPromptListChangednotifications from each server - Reconnection: Automatically reconnects failed servers with configurable retry limits
- Server enable/disable: Toggling via
setMcpServerEnabled()persists to settings and triggers reconnection - 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
}