commands/mcp/ — MCP Server Management Commands
Commands for managing Model Context Protocol servers
Provides /mcp UI and claude mcp add CLI for registering, enabling, disabling, and authenticating MCP servers. Handles scope management (local/user/project), OAuth flows, XAA enterprise auth, and server approval workflows.
📚 What is this & why does it exist?
The MCP commands module is the user-facing gateway for managing external tool servers that extend Claude Code's capabilities. It lets developers register new MCP servers (via stdio, HTTP, or SSE transports), toggle them on and off, configure OAuth credentials, and set up enterprise-grade Cross-App Access (XAA) authentication. Without it, Claude Code would have no way to discover or connect to external tools.
💡 Real-world analogy
Think of MCP server management like a smart-home hub. Each MCP server is a smart device (a thermostat, a door lock, a camera). The /mcp add command is pairing a new device—you tell the hub how to talk to it (WiFi, Bluetooth, Zigbee = stdio, HTTP, SSE). The approval flow is like confirming "yes, I trust this device." Scope management (local vs project) decides whether only you see the device or everyone in the household does. XAA is like enterprise SSO for your entire smart building—one badge gets you into every room.
Contents
Architecture
flowchart TD USER["User CLI / UI"] -->|"/mcp add"| ADD["addCommand.ts"] USER -->|"/mcp enable/disable"| MCP["mcp.tsx"] USER -->|"/mcp xaa setup"| XAA["xaaIdpCommand.ts"] ADD -->|"addMcpConfig()"| CFG["services/mcp/config.ts"] ADD -->|"saveMcpClientSecret()"| AUTH["services/mcp/auth.ts"] MCP -->|"useMcpToggleEnabled()"| MGR["MCPConnectionManager.tsx"] MGR -->|"useManageMCPConnections()"| STATE["AppState.mcp.clients"] XAA -->|"acquireIdpIdToken()"| IDP["services/mcp/xaaIdpLogin.ts"] IDP -->|"OIDC flow"| BROWSER["Browser Auth"] CFG -->|"read/write"| FILES["Config Files"] AUTH -->|"store secrets"| KEYCHAIN["Secure Keychain"]
Command Routing
flowchart LR
CMD["/mcp args"] --> PARSE{"Parse args[0]"}
PARSE -->|"no args"| UI["MCPSettings Panel"]
PARSE -->|"enable"| TOG["MCPToggle Component"]
PARSE -->|"disable"| TOG
PARSE -->|"reconnect"| REC["MCPReconnect Component"]
PARSE -->|"no-redirect"| PLG["PluginSettings Panel"]
TOG -->|"target=all"| ALL["Toggle All Servers"]
TOG -->|"target=name"| ONE["Toggle Single Server"]
ALL --> HOOK["useMcpToggleEnabled()"]
ONE --> HOOK
Key Classes & Functions
| Class / Function | File | Purpose |
|---|---|---|
call() | mcp.tsx | Entry point for /mcp UI command; parses args and routes to subcommands |
MCPToggle | mcp.tsx | React component that enables/disables MCP servers via AppState |
registerMcpAddCommand() | addCommand.ts | Registers the claude mcp add CLI subcommand with Commander.js |
registerMcpXaaIdpCommand() | xaaIdpCommand.ts | Registers claude mcp xaa CLI subcommands (setup/login/show/clear) |
addMcpConfig() | services/mcp/config.ts | Writes server config to the appropriate scope file |
useMcpToggleEnabled() | MCPConnectionManager.tsx | React hook returning a toggle function for server enabled/disabled state |
performMCPOAuthFlow() | services/mcp/auth.ts | Executes browser-based OAuth authorization code flow for MCP servers |
saveMcpClientSecret() | services/mcp/auth.ts | Persists OAuth client secrets to the secure keychain |
acquireIdpIdToken() | services/mcp/xaaIdpLogin.ts | Runs OIDC flow to obtain an id_token from the enterprise IdP |
getProjectMcpServerStatus() | services/mcp/config.ts | Returns approval state (approved | rejected | pending) for project-scoped servers |
ensureConfigScope() | services/mcp/utils.ts | Validates and normalizes the --scope CLI option |
ensureTransport() | services/mcp/utils.ts | Validates and normalizes the --transport CLI option |
MCPSettings | components/mcp/index.ts | Full UI panel for browsing, enabling, and configuring MCP servers |
MCPReconnect | components/mcp/MCPReconnect.ts | Component that reconnects a disconnected MCP server by name |
Data Structures
| Type | Name | Description |
|---|---|---|
| type | ConfigScope | Union: 'local' | 'user' | 'project' | 'dynamic' | 'enterprise' | 'claudeai' | 'managed' |
| type | Transport | Union: 'stdio' | 'sse' | 'sse-ide' | 'http' | 'ws' | 'sdk' |
| type | McpStdioServerConfig | Config for subprocess-based servers: command, args, env |
| type | McpSSEServerConfig | Config for SSE servers: url, headers, oauth |
| type | McpHTTPServerConfig | Config for HTTP servers: url, headers, oauth |
| type | McpOAuthConfig | OAuth metadata: clientId, callbackPort, authServerMetadataUrl, xaa |
| type | ScopedMcpServerConfig | Server config extended with scope and optional pluginSource |
| type | ConnectedMCPServer | Live server with client, capabilities, serverInfo, cleanup() |
| type | FailedMCPServer | Server that failed to connect, carrying error string |
| type | NeedsAuthMCPServer | Server awaiting OAuth or XAA authentication |
| type | PendingMCPServer | Server in reconnection loop with reconnectAttempt counter |
| type | DisabledMCPServer | Server toggled off by the user |
| union | MCPServerConnection | Discriminated union of all five server states above |
| type | XaaIdpSettings | IdP connection: issuer, clientId, optional callbackPort |
Interactions
| Direction | Partner | Interface | Description |
|---|---|---|---|
| → | services/mcp/config | addMcpConfig() | Writes server configuration to scope-appropriate file |
| → | services/mcp/auth | performMCPOAuthFlow() | Launches browser OAuth flow for HTTP/SSE servers |
| → | services/mcp/auth | saveMcpClientSecret() | Stores OAuth secrets in secure keychain |
| → | services/mcp/xaaIdpLogin | acquireIdpIdToken() | Runs OIDC authorization code flow for XAA enterprise login |
| → | MCPConnectionManager | useMcpToggleEnabled() | Hook to toggle server enabled state in the connection manager |
| → | AppState | useAppState(s => s.mcp.clients) | Reads current server connection states for UI rendering |
| → | components/mcp | <MCPSettings /> | Renders full MCP management panel |
| → | components/mcp | <MCPReconnect /> | Reconnects a single server by name |
| ← | commands.ts registry | default export | Command metadata registered in the global COMMANDS array |
| ← | main.tsx CLI | registerMcpAddCommand() | Called at CLI bootstrap to register claude mcp add |
Configuration
| Knob / Parameter | Default | Description |
|---|---|---|
--scope | local | Where to store the server config: local, user, or project |
--transport | stdio | Server transport protocol: stdio, sse, or http |
--env KEY=val | — | Environment variables passed to stdio server subprocess |
--header "H: V" | — | HTTP headers for SSE/HTTP server connections |
--client-id | — | OAuth client_id for HTTP/SSE servers requiring authentication |
--client-secret | — | Prompts for OAuth client_secret (or reads MCP_CLIENT_SECRET env var) |
--callback-port | random | Fixed loopback port for OAuth redirect callback |
--xaa | false | Enable Cross-App Access (SEP-990) enterprise silent auth |
CLAUDE_CODE_ENABLE_XAA | 0 | Environment variable gate for XAA feature availability |
settings.xaaIdp | — | IdP connection config: issuer, clientId, callbackPort |
Directory Map
Source tree for mcp
mcp/
├── index.ts ← Command registration metadata (type, name, aliases, lazy load)
├── mcp.tsx ← UI /mcp command: enable/disable/reconnect/settings routing
├── addCommand.ts ← CLI "claude mcp add" subcommand (server registration)
└── xaaIdpCommand.ts ← CLI "claude mcp xaa" subcommands (setup/login/show/clear)
Deep-Dive Highlights
Core mechanisms from this sub-module have been extracted into dedicated focus areas for in-depth exploration. Each section covers a key protocol, flow, or subsystem.
Server Registration Pipeline Focus
Name validation, transport detection, Zod schema checking, enterprise policy enforcement, and scope-based file persistence.
OAuth & Secret Management Focus
Browser-based authorization code flow, client secret keychain storage, token exchange, and per-server credential isolation.
XAA Enterprise Authentication Focus
OIDC discovery, IdP login, id_token caching, cross-app token exchange (SEP-990), and silent server authentication.
Scope & Approval Workflow Focus
Config scope hierarchy (local → user → project → enterprise), project server approval/rejection states, and policy denylist/allowlist checks.
Code Examples
Example 1: Registering an MCP Server via CLI
// addCommand.ts — action handler (simplified)
async function action(
name: string,
commandOrUrl: string,
extraArgs: string[],
options: AddOptions
): Promise<void> {
const scope = ensureConfigScope(options.scope)
const transport = ensureTransport(options.transport)
// Validate server name: alphanumeric, hyphens, underscores only
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
cliError('Server name can only contain letters, numbers, hyphens, and underscores')
process.exit(1)
}
if (transport === 'http' || transport === 'sse') {
const headers = options.header ? parseHeaders(options.header) : undefined
const oauth: McpOAuthConfig | undefined = options.clientId
? { clientId: options.clientId, callbackPort: options.callbackPort, xaa: options.xaa }
: undefined
await addMcpConfig(name, { type: transport, url: commandOrUrl, headers, oauth }, scope)
if (options.clientSecret) {
const secret = await readClientSecret()
saveMcpClientSecret(name, { type: transport, url: commandOrUrl, oauth }, secret)
}
} else {
const env = options.env ? parseEnvVars(options.env) : undefined
await addMcpConfig(name, { type: 'stdio', command: commandOrUrl, args: extraArgs, env }, scope)
}
logEvent('tengu_mcp_add', { type: transport, scope, source: 'command' })
cliOk(`Added ${name} to ${describeMcpConfigFilePath(scope)}`)
}
Example 2: Toggling MCP Servers in the UI
// mcp.tsx — MCPToggle component
function MCPToggle({
action,
target,
onComplete,
}: {
action: 'enable' | 'disable'
target: string
onComplete: (result: string) => void
}): null {
const clients = useAppState(s => s.mcp.clients)
const toggleMcpServer = useMcpToggleEnabled()
const hasRun = useRef(false)
useEffect(() => {
if (hasRun.current) return
hasRun.current = true
const enable = action === 'enable'
const filtered = clients.filter(c => c.name !== 'ide')
if (target === 'all') {
const toToggle = filtered.filter(c =>
enable ? c.type === 'disabled' : c.type !== 'disabled'
)
toToggle.forEach(c => toggleMcpServer(c.name, enable))
onComplete(`${enable ? 'Enabled' : 'Disabled'} ${toToggle.length} servers`)
} else {
const server = filtered.find(c => c.name === target)
if (!server) { onComplete(`Server "${target}" not found`); return }
toggleMcpServer(server.name, enable)
onComplete(`${enable ? 'Enabled' : 'Disabled'} ${server.name}`)
}
}, [])
return null
}
Example 3: XAA IdP Setup
// xaaIdpCommand.ts — setup action (simplified)
async function setupAction(options: {
issuer: string
clientId: string
clientSecret?: boolean
callbackPort?: number
}): Promise<void> {
// Validate issuer URL (must be https, except loopback)
const url = new URL(options.issuer)
const isLoopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)
if (url.protocol !== 'https:' && !isLoopback) {
cliError('Issuer must use https:// (except loopback addresses)')
process.exit(1)
}
// Save non-secret config to settings
const settings: XaaIdpSettings = {
issuer: options.issuer,
clientId: options.clientId,
callbackPort: options.callbackPort,
}
updateSettingsForSource('userSettings', { xaaIdp: settings })
// Store client secret in keychain if provided
if (options.clientSecret) {
const secret = process.env.MCP_XAA_IDP_CLIENT_SECRET
if (!secret) {
cliError('Set MCP_XAA_IDP_CLIENT_SECRET env var when using --client-secret')
process.exit(1)
}
saveIdpClientSecret(issuerKey(options.issuer), secret)
}
cliOk(`XAA IdP connection configured for ${options.issuer}`)
}