MCP Auth

Description

The MCP auth module (src/services/mcp/auth.ts, 88KB) handles OAuth 2.0 authentication for MCP server connections. It supports two flows: standard OAuth authorization code with PKCE, and XAA (Cross-App Access) for enterprise SSO. Tokens are stored in the platform keychain, and the system handles refresh, revocation, step-up auth, and vendor-specific error normalization.

How it works

Standard OAuth flow

performMCPOAuthFlow() orchestrates the browser-based PKCE flow:

  1. Discover auth server metadata via RFC 9728 + RFC 8414 fallback chain (fetchAuthServerMetadata())
  2. Start a local HTTP server on 127.0.0.1 for the callback
  3. Open the user's browser to the authorization endpoint
  4. Wait for callback (5-minute timeout)
  5. Exchange authorization code for tokens
  6. Store tokens in platform keychain

Claude Code registers as a public client (token_endpoint_auth_method: 'none'). It supports CIMD (URL-based client_id) via a clientMetadataUrl getter pointing to MCP_CLIENT_METADATA_URL.

For remote/browser-based environments where localhost is unreachable, users can paste the callback URL manually.

XAA (Cross-App Access) flow

performMCPXaaAuth() enables enterprise SSO — one IdP login is reused across all XAA-configured MCP servers:

  1. Acquire id_token from IdP (cached in keychain by issuer; if missing, runs OIDC authorization_code+PKCE)
  2. RFC 8693 token exchange + RFC 7523 jwt-bearer grant (no browser needed)
  3. Store tokens in the same keychain slot as normal OAuth

ClaudeAuthProvider

ClaudeAuthProvider (line 1376) implements OAuthClientProvider from @modelcontextprotocol/sdk. It manages: - Token storage and retrieval from secure keychain - Proactive token refresh (5 minutes before expiry) - Server key generation: {serverName}|{sha256(configJson).slice(0,16)}

Step-up authentication

wrapFetchWithStepUpDetection() handles 403 insufficient_scope responses. When detected, it marks step-up pending and forces PKCE re-authorization instead of useless token refresh (RFC 6749 section 6 forbids scope elevation via refresh).

Vendor normalization

normalizeOAuthErrorBody() fixes non-standard error codes from specific vendors. For example, Slack returns invalid_refresh_token, expired_refresh_token, and token_expired — all normalized to the standard invalid_grant.

Token revocation

revokeServerTokens() implements RFC 7009 — revokes refresh token first, then access token.

Constants

Constant Value Purpose
AUTH_REQUEST_TIMEOUT_MS 30,000 (30s) Per-request timeout
MAX_LOCK_RETRIES 5 Concurrent auth lock attempts
OAuth callback timeout 5 minutes Browser auth flow timeout
Proactive refresh 5 min before expiry Token refresh trigger
MCP_AUTH_CACHE_TTL_MS 15 min Skip re-probing servers that recently returned 401

Trade-offs

  1. Public client — no client secret, simpler setup, but relies entirely on PKCE for security. Standard for CLI tools but less secure than confidential clients.
  2. Platform keychain storage — secure but platform-specific. Requires different implementations per OS.
  3. Vendor normalization — fixes real interop issues but creates ongoing maintenance as new vendors ship non-standard responses.
  4. Step-up auth — correct per RFC but adds complexity. Without it, expired scope tokens would loop endlessly on refresh.
  5. XAA SSO — single login for all enterprise MCP servers, but adds an entire second auth flow to maintain.

Depends on

Key claims

Relations

Sources

src-20260409-a5fc157bc756, source code analysis of src/services/mcp/auth.ts