services/mcp/ — Model Context Protocol Service
MCP protocol client for tool & resource integration
Manages MCP server connections across six transport types (stdio, SSE, HTTP, WebSocket, SDK, claudeai-proxy), discovers tools and resources, handles OAuth authentication with XAA token exchange, and enforces enterprise permission policies. The extensibility backbone of Claude Code.
📚 What is this & why does it exist?
The MCP service is Claude Code's plugin system. It lets external tools — file editors, databases, web scrapers, custom APIs — plug into Claude Code through a standardized protocol. This module handles everything from discovering what tools a server offers, to authenticating with OAuth, to actually calling those tools and returning results to the model.
💡 Real-world analogy
Imagine a universal remote control (Claude Code) that can operate any brand of TV, sound system, or smart home device. Each device speaks its own language, so the remote has a translation chip (MCP protocol). When you press a button, the remote first discovers what the device can do (tool discovery), checks if you're authorized to use it (OAuth + permissions), and then sends the right signal over the right channel — infrared for the TV (stdio), Wi-Fi for the speaker (HTTP/SSE), Bluetooth for the smart lock (WebSocket). The config file is like programming the remote's memory buttons for each room.
Contents
Architecture
flowchart TD CFG["config.ts\nMulti-scope Config Loader"] -->|server configs| CM["MCPConnectionManager.tsx"] CM -->|create connections| CL["client.ts\nMCP Client Factory"] CL -->|stdio| ST["Stdio Transport"] CL -->|sse/http| HT["SSE / HTTP Transport"] CL -->|websocket| WS["WebSocket Transport"] CL -->|sdk| SDK["InProcessTransport"] CL -->|claudeai-proxy| CAI["Claude.ai Proxy"] CL --> TD["Tool Discovery\nfetchToolsForClient()"] CL --> RD["Resource Discovery\nfetchResourcesForClient()"] CL --> CMD["Command Discovery\nfetchCommandsForClient()"] TD --> MCPTool["MCPTool extends Tool"] AUTH["auth.ts\nOAuth + XAA"] -->|tokens| CL EH["elicitationHandler.ts"] -->|user prompts| CL CP["channelPermissions.ts"] -->|permission relay| CL UTILS["utils.ts\nFiltering & Hashing"] --> CM
Connection Lifecycle
stateDiagram-v2 [*] --> Pending : Config loaded Pending --> Connected : Transport established Pending --> NeedsAuth : OAuth required Pending --> Failed : Connection error NeedsAuth --> Connected : Token acquired NeedsAuth --> Failed : Auth rejected Connected --> Failed : Runtime crash Connected --> Disabled : User disabled Failed --> Pending : Reconnect attempt Disabled --> Pending : User re-enabled Connected --> [*] : Shutdown
Key Classes & Functions
| Class / Function | File | Purpose |
|---|---|---|
MCPTool | client.ts | Extends Tool base class; wraps MCP tool calls with OAuth retry and result truncation |
createMcpClientConnection() | client.ts | Establishes transport connection, registers elicitation & permission handlers, discovers capabilities |
fetchToolsForClient() | client.ts | Queries MCP server for available tools, returns Tool[] |
fetchResourcesForClient() | client.ts | Queries MCP server for available resources |
reconnectMcpServerImpl() | client.ts | Reconnects a failed MCP server with fresh config and transport |
MCPConnectionManager | MCPConnectionManager.tsx | React component managing all MCP server connections as state |
getAllMcpConfigs() | config.ts | Aggregates server configs from enterprise, user, project, plugin, and Claude.ai scopes |
loadMcpConfigFile() | config.ts | Parses .mcp.json with Zod validation |
ClaudeAuthProvider | auth.ts | OAuth token management with XAA (Cross-App Access) token exchange flow |
discoverAuthorizationServerMetadata() | auth.ts | RFC 8414 OAuth server metadata discovery with HTTPS validation |
registerElicitationHandler() | elicitationHandler.ts | Sets up form-based user prompts relayed from MCP servers |
InProcessTransport | InProcessTransport.ts | Linked in-process transport pair for SDK MCP servers |
SdkControlClientTransport | SdkControlTransport.ts | CLI-side transport bridge for SDK ↔ control message communication |
expandEnvVarsInString() | envExpansion.ts | Expands ${VAR} and ${VAR:-default} syntax in config values |
hashMcpConfig() | utils.ts | SHA256 fingerprint (16 chars) for detecting config changes and stale clients |
Data Structures
| Type | Name | Description |
|---|---|---|
| union | MCPServerConnection | Discriminated union: ConnectedMCPServer | FailedMCPServer | NeedsAuthMCPServer | PendingMCPServer | DisabledMCPServer |
| type | ConnectedMCPServer | { client: Client, name, type: 'connected', capabilities, cleanup() } |
| type | ConfigScopes | 'local' | 'user' | 'project' | 'dynamic' | 'enterprise' | 'claudeai' | 'managed' |
| schema | McpStdioServerConfigSchema | { type?: 'stdio', command, args?, env? } — Zod validated |
| schema | McpSSEServerConfigSchema | { type: 'sse', url, headers?, headersHelper?, oauth? } |
| schema | McpHTTPServerConfigSchema | { type: 'http', url, headers?, headersHelper?, oauth? } |
| schema | McpWebSocketServerConfigSchema | { type: 'ws', url, headers?, headersHelper? } |
| schema | McpSdkServerConfigSchema | { type: 'sdk', name } — in-process SDK server |
| type | SerializedTool | { name, description, inputJSONSchema?, isMcp?, originalToolName? } |
| type | MCPCliState | { clients, configs, tools, resources, normalizedNames? } |
| type | ChannelPermissionResponse | { behavior: 'allow' | 'deny', fromServer } |
Interactions
| Direction | Partner | Interface | Description |
|---|---|---|---|
| → | MCP Servers | JSON-RPC over transports | Tool calls, resource reads, capability discovery via MCP protocol |
| → | OAuth Providers | ClaudeAuthProvider | RFC 8693 token exchange, JWT Authorization Grants, XAA flow |
| ← | Core Agent Loop | MCPTool.execute() | Agent invokes MCP tools as part of the tool-use loop |
| ← | Plugin System | getPluginMcpServers() | Plugins register dynamic-scope MCP servers |
| → | Claude.ai API | /v1/mcp_servers | Fetches organization-managed MCP server configs |
| → | Enterprise Config | managed-mcp.json | Reads enterprise-managed server definitions (highest precedence) |
| ↔ | Channel Servers | Notification handlers | Permission relay over Telegram, Discord, iMessage channels |
| → | GrowthBook | Feature gates | tengu_harbor for channels, tengu_harbor_ledger for allowlist |
Configuration
| Knob / Parameter | Default | Description |
|---|---|---|
.mcp.json (project) | — | Project-level MCP server definitions in mcpServers key |
~/.claude/settings.json | — | User-level MCP server configs |
managed-mcp.json | — | Enterprise-managed servers (highest precedence) |
AUTH_REQUEST_TIMEOUT_MS | 30000 | OAuth request timeout |
MAX_LOCK_RETRIES | 5 | Token refresh lock retry attempts |
FETCH_TIMEOUT_MS | 5000 | Claude.ai MCP config fetch timeout |
TOOL_CALL_TIMEOUT_MS | — | Per-tool-call execution timeout |
headersHelper | — | Executable script path for dynamic header generation (10 s timeout) |
MCP_SERVERS_BETA_HEADER | mcp-servers-2025-12-04 | Beta header for Claude.ai MCP server endpoint |
${VAR} / ${VAR:-default} | — | Environment variable expansion in config string values |
Directory Map
Source tree for mcp
mcp/
├── auth.ts ← OAuth + XAA authentication (2,139 lines)
├── channelAllowlist.ts ← GrowthBook-backed channel allowlist
├── channelNotification.ts ← Channel notification gating & wrapping
├── channelPermissions.ts ← Permission relay over messaging channels
├── claudeai.ts ← Claude.ai org MCP server fetching
├── client.ts ← Primary MCP client & tool execution (2,750 lines)
├── config.ts ← Multi-scope config aggregation (1,700 lines)
├── elicitationHandler.ts ← Form-based user prompt handling
├── envExpansion.ts ← ${VAR} expansion in config values
├── headersHelper.ts ← Dynamic header generation via scripts
├── InProcessTransport.ts ← Linked in-process transport pair
├── MCPConnectionManager.tsx ← React connection state manager
├── mcpStringUtils.ts ← Tool name parsing (mcp__server__tool)
├── normalization.ts ← Server name normalization
├── oauthPort.ts ← OAuth callback port management
├── officialRegistry.ts ← Official MCP server registry
├── SdkControlTransport.ts ← SDK ↔ CLI transport bridge
├── types.ts ← Core types & Zod schemas
├── useManageMCPConnections.ts ← React hook for connection management
├── utils.ts ← Filtering, hashing, stale detection
├── vscodeSdkMcp.ts ← VS Code SDK MCP integration
├── xaa.ts ← Cross-App Access token exchange
└── xaaIdpLogin.ts ← XAA identity provider login flow
Deep-Dive Highlights
Core mechanisms from this sub-module have been extracted into dedicated focus areas. Each topic covers one protocol, subsystem, or security mechanism in detail.
Deep-Dive Pages
OAuth & XAA Authentication Focus
RFC 8693 token exchange, JWT Authorization Grants, cross-app access flow, and the ClaudeAuthProvider token lifecycle.
Multi-Scope Configuration Focus
Enterprise → user → project → plugin → claudeai config cascade, .mcp.json parsing, and policy filtering.
Transport Layer Focus
Six transport implementations: stdio, SSE, HTTP, WebSocket, in-process SDK, and SDK control bridge.
Transport Comparison
| Transport | Config Type | Use Case | Auth Support |
|---|---|---|---|
stdio | McpStdioServerConfigSchema | Local CLI tools spawned as child processes | Env vars only |
sse | McpSSEServerConfigSchema | Remote servers with Server-Sent Events streaming | OAuth, headers, headersHelper |
http | McpHTTPServerConfigSchema | Remote REST-style MCP servers | OAuth, headers, headersHelper |
ws | McpWebSocketServerConfigSchema | Real-time bidirectional communication | Headers, headersHelper |
sdk | McpSdkServerConfigSchema | In-process servers via InProcessTransport pair | Inherited from host |
claudeai-proxy | McpClaudeAIProxyServerConfigSchema | Organization-managed servers proxied through Claude.ai | OAuth via Claude.ai |
Tool Naming Convention
MCP tools follow the triple-underscore naming pattern: mcp__<serverName>__<toolName>. The mcpStringUtils.ts module provides parsing and generation utilities:
import { mcpInfoFromString, buildMcpToolName, getMcpDisplayName } from './mcpStringUtils';
// Parse: "mcp__github__search_repos" → { server: "github", tool: "search_repos" }
const info = mcpInfoFromString('mcp__github__search_repos');
// Build: ("github", "search_repos") → "mcp__github__search_repos"
const fullName = buildMcpToolName('github', 'search_repos');
// Display: strip prefix for user-facing name
const display = getMcpDisplayName('mcp__github__search_repos', 'github');
// → "search_repos"
Code Examples
Example 1: Multi-Scope Config Aggregation
import { getAllMcpConfigs } from './config';
// Aggregates from all sources with precedence:
// 1. Enterprise managed-mcp.json (highest)
// 2. User ~/.claude/settings.json
// 3. Project .mcp.json
// 4. Plugin-provided servers (dynamic scope)
// 5. Claude.ai organization servers
const configs = await getAllMcpConfigs();
for (const [name, config] of Object.entries(configs)) {
console.log(`${name} [${config.scope}]:`);
if (config.type === 'stdio') {
console.log(` command: ${config.command} ${config.args?.join(' ')}`);
} else if (config.type === 'sse' || config.type === 'http') {
console.log(` url: ${config.url}`);
if (config.headersHelper) {
console.log(` headers via: ${config.headersHelper}`);
}
}
}
Example 2: MCP Tool Execution with OAuth Retry
import { MCPTool, createMcpClientConnection } from './client';
import { getMcpServerHeaders } from './headersHelper';
// Establish connection with transport auto-detection
const connection = await createMcpClientConnection(serverName, config);
if (connection.type !== 'connected') {
throw new Error(`Connection failed: ${connection.type}`);
}
// Discover available tools
const tools = await fetchToolsForClient(connection.client);
const searchTool = tools.find(t => t.name === 'mcp__myserver__search');
// Execute with automatic OAuth 401 retry (max 2 attempts)
if (searchTool instanceof MCPTool) {
const result = await searchTool.call(
{ query: 'find all TypeScript files' },
{ timeout: 30000 }
);
// Results are auto-truncated if too large
console.log(result.content);
}
Example 3: Environment Variable Expansion in Config
import { expandEnvVarsInString } from './envExpansion';
// Supports ${VAR} and ${VAR:-fallback} syntax
const { expanded, missingVars } = expandEnvVarsInString(
'https://${API_HOST:-localhost}:${API_PORT}/mcp'
);
// If API_HOST="prod.example.com" and API_PORT is unset:
// expanded = "https://prod.example.com:${API_PORT}/mcp"
// missingVars = ["API_PORT"]
if (missingVars.length > 0) {
console.warn(`Missing env vars: ${missingVars.join(', ')}`);
}