claude-code - 💡(How to fix) Fix [BUG] Empty `projects[path].mcpServers: {}` shadows user-scope servers — should be passthrough, explicit deny required to override

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

An empty mcpServers: {} object in a project record inside ~/.claude.json silently shadows the top-level user-scope mcpServers registration for sessions launched in that directory. The user-scope servers appear in claude mcp list and claude mcp get <name> but do not get registered into the Claude Code session's tool catalog.

This reproduces cleanly in v2.1.114 — the most recent stable at time of filing — on Linux (WSL2). Same machine, same user, two sessions launched within minutes: one in a directory whose project record had a populated mcpServers, one in a directory whose project record had mcpServers: {}. Only the former got the MCP tools.

Our contention — the intended fix: a present but empty mcpServers object should be semantically equivalent to absent — pure passthrough. Only an explicit deny (e.g. disabledUserServers: [...] or inheritMcpServers: false) should suppress user-scope inheritance for a project. {} is not a deny; it is "I have nothing to add," and the CLI itself writes it automatically during project onboarding. Treating {} as shadow-override silently zeroes out user configuration through no action of the user.

Error Message

Every project record auto-written by the CLI with mcpServers: {} is a silent shadow of user-scope registrations. Users who rely on --scope user for convenience end up with MCP access that appears to work in some sessions and not others, with no error message and no warning in /mcp. Debugging it requires inspecting ~/.claude.json by hand and knowing to compare present-but-empty vs. absent — not obvious behavior for any user who hasn't already been burned by this.

Root Cause

  1. Violates least surprise. --scope user is documented as "available in all your projects." Current behavior makes that false in any project whose record exists and has mcpServers: {} — which is most of them, because Claude Code writes the empty object itself.

Fix Action

Workaround

Manually edit ~/.claude.json to delete the mcpServers key on every project record where it equals {}. Do not set it to {}; delete the key entirely. Sessions launched afterward in those directories inherit user scope as expected.

Caveat: if Claude Code re-writes mcpServers: {} during normal operation (e.g. on every session start or on trust-dialog acceptance), the workaround is unstable and users will be silently re-broken.

Code Example

# 1. Have a user-scope MCP server registered (top-level `mcpServers` key in ~/.claude.json).
claude mcp add -t http -s user my-server https://example.com/mcp -H "Authorization: Bearer ..."

# 2. Create two directories, open one in Claude Code to generate a project record.
mkdir -p ~/dev/proj-a ~/dev/proj-b
cd ~/dev/proj-a && claude   # accept trust dialog, exit

# 3. Inspect ~/.claude.json:
#    - top-level mcpServers: { "my-server": {...} }
#    - projects["/home/user/dev/proj-a"].mcpServers: {} (auto-written)
#    - projects["/home/user/dev/proj-b"]: does not exist yet

# 4. Launch a session in each:
cd ~/dev/proj-a && claude   # my-server tools are NOT in the catalog
cd ~/dev/proj-b && claude   # my-server tools ARE in the catalog (no project record yet)
RAW_BUFFERClick to expand / collapse

Summary

An empty mcpServers: {} object in a project record inside ~/.claude.json silently shadows the top-level user-scope mcpServers registration for sessions launched in that directory. The user-scope servers appear in claude mcp list and claude mcp get <name> but do not get registered into the Claude Code session's tool catalog.

This reproduces cleanly in v2.1.114 — the most recent stable at time of filing — on Linux (WSL2). Same machine, same user, two sessions launched within minutes: one in a directory whose project record had a populated mcpServers, one in a directory whose project record had mcpServers: {}. Only the former got the MCP tools.

Our contention — the intended fix: a present but empty mcpServers object should be semantically equivalent to absent — pure passthrough. Only an explicit deny (e.g. disabledUserServers: [...] or inheritMcpServers: false) should suppress user-scope inheritance for a project. {} is not a deny; it is "I have nothing to add," and the CLI itself writes it automatically during project onboarding. Treating {} as shadow-override silently zeroes out user configuration through no action of the user.

Reference

This is the same root issue as #16728 (auto-closed as stale by the bot, then locked — not fixed). Filing fresh per the bot's own instruction. Repro confirmed on a newer version (2.1.114 vs. 2.1.1 in the original).

Repro

# 1. Have a user-scope MCP server registered (top-level `mcpServers` key in ~/.claude.json).
claude mcp add -t http -s user my-server https://example.com/mcp -H "Authorization: Bearer ..."

# 2. Create two directories, open one in Claude Code to generate a project record.
mkdir -p ~/dev/proj-a ~/dev/proj-b
cd ~/dev/proj-a && claude   # accept trust dialog, exit

# 3. Inspect ~/.claude.json:
#    - top-level mcpServers: { "my-server": {...} }
#    - projects["/home/user/dev/proj-a"].mcpServers: {} (auto-written)
#    - projects["/home/user/dev/proj-b"]: does not exist yet

# 4. Launch a session in each:
cd ~/dev/proj-a && claude   # my-server tools are NOT in the catalog
cd ~/dev/proj-b && claude   # my-server tools ARE in the catalog (no project record yet)

claude mcp list reports the server as registered in both cases. /mcp inside the broken session doesn't list the mcp__my-server__* tools. The handshake completes — initialize and resources/list succeed against the server, confirming it's reachable — but tools/list output never installs into the session's deferred-tool registry.

Workaround

Manually edit ~/.claude.json to delete the mcpServers key on every project record where it equals {}. Do not set it to {}; delete the key entirely. Sessions launched afterward in those directories inherit user scope as expected.

Caveat: if Claude Code re-writes mcpServers: {} during normal operation (e.g. on every session start or on trust-dialog acceptance), the workaround is unstable and users will be silently re-broken.

Why current behavior is wrong

  1. Violates least surprise. --scope user is documented as "available in all your projects." Current behavior makes that false in any project whose record exists and has mcpServers: {} — which is most of them, because Claude Code writes the empty object itself.

  2. Inconsistent with every other config system Claude Code users touch. git, npm, shell env, and CLAUDE.md itself all merge hierarchically. Empty collections at lower scopes never shadow higher ones; explicit unset/deny mechanisms handle removal. Claude Code's MCP resolution is the only place this invariant breaks.

  3. Inconsistent with Claude Code's own CLAUDE.md behavior. User-level ~/.claude/CLAUDE.md composes with project-level CLAUDE.md. Users reasonably expect mcpServers to follow the same mental model.

  4. No coherent trust story. If the concern is "projects should opt in to MCP execution," the top-level mcpServers key already bypasses that gate for projects without a record, so the shadowing behavior isn't providing a real security boundary — just an inconsistent one. Trust should be wired to hasTrustDialogAccepted explicitly, not to the incidental presence of an empty object.

  5. Empty vs. deny are semantically different. Users should be able to express both:

    • "This project has nothing to add beyond user scope"mcpServers: {} or absent.
    • "This project explicitly opts out of specific user-scope servers" → explicit deny list or inheritMcpServers: false.

    The current design conflates them into one representation, erasing the second intent.

Proposed behavior

  • Absent mcpServers: inherit user-scope. (Already works.)
  • Empty mcpServers: {}: inherit user-scope. (Change.)
  • Populated mcpServers: { ... }: merge with user-scope; same-named keys in project scope override user scope. Project scope cannot remove a user-scope server by being silent about it.
  • New explicit mechanism for opting out of user scope: disabledUserServers: ["server-name"] or inheritMcpServers: false at the project record level. This is where deliberate project-level sandboxing lives.

Environment

  • Claude Code version: 2.1.114
  • Platform: Linux (WSL2 on Windows)
  • Installation: native binary
  • MCP server type reproduced against: HTTP (remote MCP over Cloudflare + ALB + ECS)

Impact

Every project record auto-written by the CLI with mcpServers: {} is a silent shadow of user-scope registrations. Users who rely on --scope user for convenience end up with MCP access that appears to work in some sessions and not others, with no error message and no warning in /mcp. Debugging it requires inspecting ~/.claude.json by hand and knowing to compare present-but-empty vs. absent — not obvious behavior for any user who hasn't already been burned by this.

extent analysis

TL;DR

The issue can be fixed by treating an empty mcpServers object in a project record as semantically equivalent to an absent mcpServers object, allowing user-scope MCP servers to be inherited.

Guidance

  • Manually editing ~/.claude.json to delete the mcpServers key on every project record where it equals {} can serve as a temporary workaround.
  • To implement the proposed behavior, update the code to merge user-scope and project-scope mcpServers configurations, allowing project scope to override user scope for same-named keys.
  • Introduce an explicit mechanism for opting out of user-scope MCP servers, such as disabledUserServers or inheritMcpServers: false, to provide a clear way for projects to deliberately sandbox their MCP access.
  • Verify the fix by checking that user-scope MCP servers are correctly inherited by projects with an empty or absent mcpServers object, and that explicit opt-out mechanisms work as expected.

Example

No code snippet is provided as the issue description does not include specific code that needs to be modified. However, the proposed behavior can be implemented by updating the configuration merging logic to handle empty mcpServers objects as described.

Notes

The current behavior may be unstable if Claude Code re-writes mcpServers: {} during normal operation, which could cause the workaround to be silently reverted. A permanent fix should address this by ensuring that the mcpServers object is handled consistently across all project records.

Recommendation

Apply the workaround of manually editing ~/.claude.json to delete empty mcpServers keys, and wait for an official update that implements the proposed behavior, as this approach ensures that user-scope MCP servers are correctly inherited by projects without introducing potential security risks or inconsistencies.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Empty `projects[path].mcpServers: {}` shadows user-scope servers — should be passthrough, explicit deny required to override