MCP server config: project-level should merge with user-level (env, etc.), not replace the whole entry
Summary
When the same MCP server name appears in both user-level (~/.claude.json) and project-level (<repo>/.mcp.json) config, Claude Code replaces the entire server entry rather than merging. The project entry has to spell out the full server definition (transport, command, args, env, etc.) just to override one field.
This forces hardcoded platform paths into checked-in config, which breaks any project deployed across multiple machines/OSes.
The pain, concretely
I'm running an MCP server (https://github.com/klercker/human-bridge) where the same project repo is checked out on macOS and on Linux LXCs. The natural shape would be:
- User-level (per machine): MCP server's
command+args(paths differ by OS —/opt/homebrew/bin/bunvs/usr/local/bin/bun,~/Projects/...vs/opt/...). - Project-level (committed to git): just the env vars that scope this clone (
HB_AGENT_ID,HB_CHANNEL, etc.).
That would commit cleanly across machines because the project file never names a path. But CC's replace-not-merge semantics mean project-level has to spell out the full server definition with paths — so checking it in either breaks the Mac clone (if Linux paths win) or the Linux clone (if Mac paths win).
Workarounds today
All ugly:
- Dispatch shim — project
.mcp.jsoninvokes./tools/mcp-spawn.sh <name>, shim doesunamebranching. Works but every spawn pays a shell hop. - Per-host files + gitignored symlink — commit
.mcp.json.darwin+.mcp.json.linux, each clone symlinks one to.mcp.jsonat setup. Works but needs a per-clone setup step. - Don't commit
.mcp.json— defeats the purpose of project-level config.
What I'd want
Either:
a) Merge semantics — project-level partial entries merge into the user-level definition. Project can override single fields (env, args additions) without restating transport/command/args. Closest to how most config systems compose (Docker compose overlays, Kubernetes patches, npm package.json overrides, etc.).
b) Explicit extends — project entry says extends: \"<server-name>\", inherits everything from user-level, applies its own diff. More explicit, harder to surprise users with field collisions.
Either would let dual-deployed projects commit a single platform-independent .mcp.json with just the diff that's actually project-specific.
Why it matters
The dual-deploy pattern isn't exotic — anyone running personas/agents on multiple machines, or shipping plugins consumed across host OSes, hits this the moment they try to commit project-level MCP config. Today they reach for shims or symlink dances; ideally the harness handles composition for them.
Related
- #17017 (project-level permissions replace global permissions instead of merging — same pattern, different config surface)
- #51274 (agent-scoped MCP override — adjacent, but agent scope rather than project/user composition)
Repro
- User-level
~/.claude.jsondeclares MCP server \foo\with full transport/command/args. - Project-level
<repo>/.mcp.jsondeclares MCP server \foo\with only\"env\": { \"X\": \"y\" }. - Open the project. Observe \
foo\fails to spawn because command/args from user-level were not inherited.
Workaround confirms behavior: copy the user-level command/args into the project entry, server spawns successfully.
Environment: Claude Code (current as of May 2026), macOS Darwin 25.5.0.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗