bug(mcp): --mcp-config dynamic entries silently dropped when server name appears in disabledMcpServers
Summary
--mcp-config is documented as a way to manually configure MCP servers for a single Claude Code session, overriding ~/.claude.json's mcpServers registrations. But the current implementation silently drops --mcp-config entries when the same server name appears in the canonical project's disabledMcpServers array — making the override unable to override.
Repro
~/.claude.jsonhasprojects.<some-canonical-path>.disabledMcpServerscontaining"foo".- Start a Claude Code session in a directory whose canonical git root resolves to
<some-canonical-path>. - Invoke Claude with
--mcp-config '{"mcpServers":{"foo":{"type":"http","url":"http://127.0.0.1:NNNN/"}}}'and--allowedTools mcp__foo__some_tool. - Expected: the
fooMCP server is registered for THIS session because--mcp-configis a manual override.mcp__foo__some_toolis in the tool catalog. - Actual: the
fooserver is silently emitted astype: 'disabled'. No HTTP handshake. No tools registered. The system prompt's references tomcp__foo__*resolve to nothing. ToolSearch returns no matches.
Root cause (sourcedive)
Investigated against the leaked @anthropic-ai/claude-code source mirror (~v1.0.x; current runtime is 2.1.144 — same architecture in this area):
--mcp-configparses correctly intodynamicMcpConfigatentrypoints/cli/main.tsx:1415-1521. The parsed config is tagged withscope: 'dynamic'(lines 1500-1503) to distinguish it from~/.claude.jsonmcpServersentries.- Merged into
regularMcpConfigsatmain.tsx:2386-2400and passed toconnectMcpBatchat:2729. - Bug:
getMcpToolsCommandsAndResources(services/mcp/client.ts:2226) walks each entry. At:2245and:2288, it callsisMcpServerDisabled(name)BEFORE attempting the connection. If true: emitsonConnectionAttempt({client: {type:'disabled', ...}})and returns. Never attempts the HTTP handshake. isMcpServerDisabled(services/mcp/config.ts:1528) readsgetCurrentProjectConfig().disabledMcpServers.getCurrentProjectConfig(utils/config.ts:1602) usesfindCanonicalGitRoot(utils/git.ts:195), which resolves any worktree path back to its main checkout.- The disable filter at
client.ts:2245,:2288does NOT check thescopefield on the config. So a dynamic (--mcp-config) entry gets dropped if a static (~/.claude.json) entry with the same name is disabled at the project level.
pendingMcpServers (consumed by tools/ToolSearchTool/ToolSearchTool.ts:334) filters appState.mcp.clients by type === 'pending'. A 'disabled' entry is invisible there; its tools never appear in deferred_tools_delta.
Why this is the bug, not a feature
The conceptual model of --mcp-config is a session-scoped manual override. Users invoking it explicitly want to enable a server for THIS session regardless of broader configuration. The disabledMcpServers array's intent is to be a project-wide default — that an explicit session-scoped flag wouldn't honor it is the documented user expectation, but the current code path treats both as equal and filters dynamic on the static blacklist.
Suggested fix
Two-line guard at the disable check sites:
// services/mcp/client.ts:2245
- if (isMcpServerDisabled(name)) {
+ if (config.scope !== 'dynamic' && isMcpServerDisabled(name)) {
// services/mcp/client.ts:2288
- if (isMcpServerDisabled(name)) {
+ if (config.scope !== 'dynamic' && isMcpServerDisabled(name)) {
Same guard belongs in useManageMCPConnections.ts:891-893 (the filter step before getMcpToolsCommandsAndResources runs).
The scope: 'dynamic' tag is already set at main.tsx:1500-1503 and survives the merge, so the discriminator is available — no new state needed.
Workarounds for now
Two viable ones:
- Scrub the server name from
disabledMcpServersin~/.claude.json(across all project entries that contain it). This restores the dynamic override but defeats the intent ofdisabledMcpServers(which is to keep the server disabled in static config). - Use a server name that's never appeared in any project's
disabledMcpServers— i.e., rename the--mcp-configserver tofoo-sessionorfoo-dynamic. Requires coordinated change on three surfaces (the--mcp-configflag, the--allowedToolsflag, and any system-prompt-injectedmcp__foo__*references).
Real-world trigger
Discovered during a HAPImatic (https://github.com/MattStarfield/hapimatic-private — fork of tiann/hapi) worker session 2026-05-20. HAPImatic injects an HTTP MCP server via --mcp-config '{"mcpServers":{"hapi":{"type":"http","url":"http://127.0.0.1:<port>/"}}}' on every Claude session spawn, providing change_title and image_fetch tools. A user-installed enforce-default-disabled hook had added "hapi" to every project's disabledMcpServers. The --mcp-config override never engaged. Both tools silently absent in every session, but the system prompt continued to promise them.
Environment
- Claude Code version: 2.1.144
- Platform: Linux (Raspberry Pi 5, ARM64)
- Node: v24.11.1
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗