MCP Server Discovery

Description

MCP server discovery (src/services/mcp/config.ts) loads server definitions from 7 configuration scopes, merges them with a defined precedence order, deduplicates across sources, enforces deny/allow policies, and provides the final server list for connection. It supports 8 server config types (stdio, SSE, SSE-IDE, HTTP, WebSocket, WS-IDE, SDK, and claude.ai proxy).

Configuration scopes

Servers are discovered from 7 scopes (later wins on name conflicts):

Priority Scope Source Notes
1 (lowest) claudeai fetchClaudeAIMcpConfigsIfEligible() Claude.ai hosted connectors
2 plugins getPluginMcpServers() From enabled plugins
3 user ~/.claude/settings.json User-level config
4 project .mcp.json in project root Only approved servers
5 local Local settings Per-machine overrides
6 dynamic Runtime arguments Passed programmatically
7 (highest) enterprise ${managedPath}/managed-mcp.json Takes exclusive control

When enterprise config exists, it takes exclusive control — all other scopes are ignored.

Config file format

.mcp.json is validated against McpJsonConfigSchema:

{
  "mcpServers": {
    "server-name": {
      "type": "stdio",
      "command": "node",
      "args": ["server.js"],
      "env": { "KEY": "value" }
    }
  }
}

Written atomically: writeMcpjsonFile() uses temp file, datasync(), then rename(), preserving original file permissions.

Server config types

Type Required fields Optional
stdio command args, env, type (defaults to stdio)
sse type: 'sse', url headers, headersHelper, oauth
sse-ide type: 'sse-ide', url, ideName ideRunningInWindows
http type: 'http', url headers, headersHelper, oauth
ws type: 'ws', url headers, headersHelper
ws-ide type: 'ws-ide', url, ideName authToken, ideRunningInWindows
sdk type: 'sdk', name
claudeai-proxy type: 'claudeai-proxy', url, id

Deduplication strategies

Three strategies prevent duplicate registrations:

  1. Plugin dedup (dedupPluginMcpServers) — matches by signature (stdio command array or URL). Manual config wins over plugin; first plugin wins ties.
  2. Claude.ai dedup (dedupClaudeAiMcpServers) — only enabled manual servers count as dedup targets.
  3. CCR proxy unwrap (unwrapCcrProxyUrl) — extracts the original vendor URL from CCR proxy path markers (/v2/session_ingress/shttp/mcp/, /v2/ccr-sessions/) so a proxied server and its direct URL are recognized as the same server.

Policy enforcement

Connection concurrency

getMcpToolsCommandsAndResources() splits servers into local (stdio/sdk) and remote groups, processing each with different concurrency limits via processBatched(). Local servers get lower concurrency (process spawning overhead); remote servers get higher concurrency (network I/O only).

Platform handling

Config validation detects Windows + npx command without cmd /c wrapper and emits a warning with a fix suggestion. This is a common gotcha on Windows where npx requires a shell wrapper.

Trade-offs

  1. 7 scopes — very flexible for different deployment scenarios (personal, team, enterprise) but complex precedence. Enterprise override simplifies managed environments at the cost of user freedom.
  2. Atomic writesdatasync() + rename() prevents corrupt .mcp.json but is slower than a direct write.
  3. Approval required for project servers.mcp.json servers only connect if marked approved, preventing untrusted repos from auto-connecting to arbitrary servers. Adds friction for legitimate use.
  4. CCR proxy unwrap — deduplicates proxied servers correctly but depends on specific URL path patterns that could break if the proxy format changes.

Depends on

Key claims

Relations

Sources

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