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.

4
Files
12
Commands
6
Transport Types
6
Config Scopes

📚 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 / FunctionFilePurpose
call()mcp.tsxEntry point for /mcp UI command; parses args and routes to subcommands
MCPTogglemcp.tsxReact component that enables/disables MCP servers via AppState
registerMcpAddCommand()addCommand.tsRegisters the claude mcp add CLI subcommand with Commander.js
registerMcpXaaIdpCommand()xaaIdpCommand.tsRegisters claude mcp xaa CLI subcommands (setup/login/show/clear)
addMcpConfig()services/mcp/config.tsWrites server config to the appropriate scope file
useMcpToggleEnabled()MCPConnectionManager.tsxReact hook returning a toggle function for server enabled/disabled state
performMCPOAuthFlow()services/mcp/auth.tsExecutes browser-based OAuth authorization code flow for MCP servers
saveMcpClientSecret()services/mcp/auth.tsPersists OAuth client secrets to the secure keychain
acquireIdpIdToken()services/mcp/xaaIdpLogin.tsRuns OIDC flow to obtain an id_token from the enterprise IdP
getProjectMcpServerStatus()services/mcp/config.tsReturns approval state (approved | rejected | pending) for project-scoped servers
ensureConfigScope()services/mcp/utils.tsValidates and normalizes the --scope CLI option
ensureTransport()services/mcp/utils.tsValidates and normalizes the --transport CLI option
MCPSettingscomponents/mcp/index.tsFull UI panel for browsing, enabling, and configuring MCP servers
MCPReconnectcomponents/mcp/MCPReconnect.tsComponent that reconnects a disconnected MCP server by name

Data Structures

TypeNameDescription
typeConfigScopeUnion: 'local' | 'user' | 'project' | 'dynamic' | 'enterprise' | 'claudeai' | 'managed'
typeTransportUnion: 'stdio' | 'sse' | 'sse-ide' | 'http' | 'ws' | 'sdk'
typeMcpStdioServerConfigConfig for subprocess-based servers: command, args, env
typeMcpSSEServerConfigConfig for SSE servers: url, headers, oauth
typeMcpHTTPServerConfigConfig for HTTP servers: url, headers, oauth
typeMcpOAuthConfigOAuth metadata: clientId, callbackPort, authServerMetadataUrl, xaa
typeScopedMcpServerConfigServer config extended with scope and optional pluginSource
typeConnectedMCPServerLive server with client, capabilities, serverInfo, cleanup()
typeFailedMCPServerServer that failed to connect, carrying error string
typeNeedsAuthMCPServerServer awaiting OAuth or XAA authentication
typePendingMCPServerServer in reconnection loop with reconnectAttempt counter
typeDisabledMCPServerServer toggled off by the user
unionMCPServerConnectionDiscriminated union of all five server states above
typeXaaIdpSettingsIdP connection: issuer, clientId, optional callbackPort

Interactions

DirectionPartnerInterfaceDescription
→services/mcp/configaddMcpConfig()Writes server configuration to scope-appropriate file
→services/mcp/authperformMCPOAuthFlow()Launches browser OAuth flow for HTTP/SSE servers
→services/mcp/authsaveMcpClientSecret()Stores OAuth secrets in secure keychain
→services/mcp/xaaIdpLoginacquireIdpIdToken()Runs OIDC authorization code flow for XAA enterprise login
→MCPConnectionManageruseMcpToggleEnabled()Hook to toggle server enabled state in the connection manager
→AppStateuseAppState(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 registrydefault exportCommand metadata registered in the global COMMANDS array
←main.tsx CLIregisterMcpAddCommand()Called at CLI bootstrap to register claude mcp add

Configuration

Knob / ParameterDefaultDescription
--scopelocalWhere to store the server config: local, user, or project
--transportstdioServer 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-portrandomFixed loopback port for OAuth redirect callback
--xaafalseEnable Cross-App Access (SEP-990) enterprise silent auth
CLAUDE_CODE_ENABLE_XAA0Environment 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.

addCommand.ts core flow

OAuth & Secret Management Focus

Browser-based authorization code flow, client secret keychain storage, token exchange, and per-server credential isolation.

services/mcp/auth.ts

XAA Enterprise Authentication Focus

OIDC discovery, IdP login, id_token caching, cross-app token exchange (SEP-990), and silent server authentication.

xaaIdpCommand.ts + xaaIdpLogin.ts

Scope & Approval Workflow Focus

Config scope hierarchy (local → user → project → enterprise), project server approval/rejection states, and policy denylist/allowlist checks.

services/mcp/config.ts

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}`)
}