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.

23
Files
~12.2 k
Lines
6
Transports
7
Config Scopes

📚 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 / FunctionFilePurpose
MCPToolclient.tsExtends Tool base class; wraps MCP tool calls with OAuth retry and result truncation
createMcpClientConnection()client.tsEstablishes transport connection, registers elicitation & permission handlers, discovers capabilities
fetchToolsForClient()client.tsQueries MCP server for available tools, returns Tool[]
fetchResourcesForClient()client.tsQueries MCP server for available resources
reconnectMcpServerImpl()client.tsReconnects a failed MCP server with fresh config and transport
MCPConnectionManagerMCPConnectionManager.tsxReact component managing all MCP server connections as state
getAllMcpConfigs()config.tsAggregates server configs from enterprise, user, project, plugin, and Claude.ai scopes
loadMcpConfigFile()config.tsParses .mcp.json with Zod validation
ClaudeAuthProviderauth.tsOAuth token management with XAA (Cross-App Access) token exchange flow
discoverAuthorizationServerMetadata()auth.tsRFC 8414 OAuth server metadata discovery with HTTPS validation
registerElicitationHandler()elicitationHandler.tsSets up form-based user prompts relayed from MCP servers
InProcessTransportInProcessTransport.tsLinked in-process transport pair for SDK MCP servers
SdkControlClientTransportSdkControlTransport.tsCLI-side transport bridge for SDK ↔ control message communication
expandEnvVarsInString()envExpansion.tsExpands ${VAR} and ${VAR:-default} syntax in config values
hashMcpConfig()utils.tsSHA256 fingerprint (16 chars) for detecting config changes and stale clients

Data Structures

TypeNameDescription
unionMCPServerConnectionDiscriminated union: ConnectedMCPServer | FailedMCPServer | NeedsAuthMCPServer | PendingMCPServer | DisabledMCPServer
typeConnectedMCPServer{ client: Client, name, type: 'connected', capabilities, cleanup() }
typeConfigScopes'local' | 'user' | 'project' | 'dynamic' | 'enterprise' | 'claudeai' | 'managed'
schemaMcpStdioServerConfigSchema{ type?: 'stdio', command, args?, env? } — Zod validated
schemaMcpSSEServerConfigSchema{ type: 'sse', url, headers?, headersHelper?, oauth? }
schemaMcpHTTPServerConfigSchema{ type: 'http', url, headers?, headersHelper?, oauth? }
schemaMcpWebSocketServerConfigSchema{ type: 'ws', url, headers?, headersHelper? }
schemaMcpSdkServerConfigSchema{ type: 'sdk', name } — in-process SDK server
typeSerializedTool{ name, description, inputJSONSchema?, isMcp?, originalToolName? }
typeMCPCliState{ clients, configs, tools, resources, normalizedNames? }
typeChannelPermissionResponse{ behavior: 'allow' | 'deny', fromServer }

Interactions

DirectionPartnerInterfaceDescription
→MCP ServersJSON-RPC over transportsTool calls, resource reads, capability discovery via MCP protocol
→OAuth ProvidersClaudeAuthProviderRFC 8693 token exchange, JWT Authorization Grants, XAA flow
←Core Agent LoopMCPTool.execute()Agent invokes MCP tools as part of the tool-use loop
←Plugin SystemgetPluginMcpServers()Plugins register dynamic-scope MCP servers
→Claude.ai API/v1/mcp_serversFetches organization-managed MCP server configs
→Enterprise Configmanaged-mcp.jsonReads enterprise-managed server definitions (highest precedence)
↔Channel ServersNotification handlersPermission relay over Telegram, Discord, iMessage channels
→GrowthBookFeature gatestengu_harbor for channels, tengu_harbor_ledger for allowlist

Configuration

Knob / ParameterDefaultDescription
.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_MS30000OAuth request timeout
MAX_LOCK_RETRIES5Token refresh lock retry attempts
FETCH_TIMEOUT_MS5000Claude.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_HEADERmcp-servers-2025-12-04Beta 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.

auth.ts + xaa.ts deep dive

Multi-Scope Configuration Focus

Enterprise → user → project → plugin → claudeai config cascade, .mcp.json parsing, and policy filtering.

config.ts deep dive

Transport Layer Focus

Six transport implementations: stdio, SSE, HTTP, WebSocket, in-process SDK, and SDK control bridge.

Transport deep dive

Transport Comparison

TransportConfig TypeUse CaseAuth Support
stdioMcpStdioServerConfigSchemaLocal CLI tools spawned as child processesEnv vars only
sseMcpSSEServerConfigSchemaRemote servers with Server-Sent Events streamingOAuth, headers, headersHelper
httpMcpHTTPServerConfigSchemaRemote REST-style MCP serversOAuth, headers, headersHelper
wsMcpWebSocketServerConfigSchemaReal-time bidirectional communicationHeaders, headersHelper
sdkMcpSdkServerConfigSchemaIn-process servers via InProcessTransport pairInherited from host
claudeai-proxyMcpClaudeAIProxyServerConfigSchemaOrganization-managed servers proxied through Claude.aiOAuth 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(', ')}`);
}