VSCode mcp.json gallery and settings.json mcpServers silently launch duplicate MCP server processes for the same name

Open 💬 0 comments Opened Jun 9, 2026 by louzt

Summary

In the Claude Code VSCode extension, MCP server configuration can be loaded
from two sources: the VSCode-side mcp.json gallery and the user-level
~/.claude/settings.json mcpServers block. The merge priority and
deduplication behavior between these two sources is not documented
, and
the result is silent duplication: a server declared in both sources is
launched twice with whichever env block happens to be resolved last.

This was first reported in adjacent scope (plugin vs claude.ai collision,
#58375 and inverse #59470) and as a multi-plugin dedup request (#63749), but
the specific case of "VSCode mcp.json gallery AND user-level
settings.json mcpServers declaring the same name" is not covered by an
existing issue
. This report focuses on that specific case.

Environment

  • anthropic.claude-code VSCode extension, panel mode, current latest as of

2026-06-09 (cc_version ≈ 2.1.165)

  • ~/.config/Code - Insiders/User/mcp.json — VSCode gallery, 9 servers

declared

  • ~/.claude/settings.json — user-level, 6 servers declared in

mcpServers, 2 names overlapping with mcp.json (context7, apify)

  • Debian forky/sid, VSCode Insiders 1.96+

Repro

  1. Declare the same MCP server in both config sources with the same name

but different env (e.g., a different APIFY_TOKEN in each):

~/.config/Code - Insiders/User/mcp.json:
``json
{
"servers": {
"apify": {
"command": "npx",
"args": ["-y", "@apify/mcp-server"],
"env": { "APIFY_TOKEN": "value-from-gallery-mcp-json" }
}
}
}
``

~/.claude/settings.json:
``json
{
"mcpServers": {
"apify": {
"command": "npx",
"args": ["-y", "@apify/mcp-server"],
"env": { "APIFY_TOKEN": "value-from-user-settings-json" }
}
}
}
``

  1. Open the Claude Code extension panel. Wait for the MCP server list to

stabilize (no pending status indicators).

  1. Inspect processes: pgrep -fa "apify-mcp-server" (or the equivalent

regex for the server's binary).

  1. Inspect tool surface: /mcp and claude mcp list.

Observed

  • pgrep returns two apify-mcp-server processes, with different

APIFY_TOKEN values in their cat /proc/<pid>/environ output.

  • /mcp and claude mcp list show the server once — there is no

indication of which config source won, no warning that a duplicate
exists, no introspection that says "this came from mcp.json" or "this
came from settings.json".

  • The visible tool set is the union of both, with last-write-wins for

per-server config (env, args, transport). Last-write is not documented
and not introspectable.

  • Cost impact: paid MCP servers (e.g., apify is a paid upstream

service) consume double their normal quota per session because both
processes are making real upstream calls when invoked.

  • Security impact: an attacker with write access to mcp.json (e.g.,

a malicious VSCode extension) can shadow the user's settings.json
configuration for any server declared in both — including changing
APIFY_TOKEN to a token the attacker controls. The user sees no warning.

Expected

The extension should:

  1. Document the merge priority (last-wins is the observed default, but

undocumented).

  1. Deduplicate by name when both sources declare the same server — and

surface the dedup decision visibly (e.g., "apify: deduped, settings.json
won over mcp.json" in /mcp output).

  1. Emit a one-time warning the first time a session loads with a name

present in both sources, with the names of the conflicting sources.

  1. Provide an introspection command (e.g., /mcp sources or

claude mcp doctor) that lists which loaded server came from which
source.

Why this matters

  • Users have no way to detect a duplicate. The /mcp output is a flat

list with no source attribution.

  • For paid MCP servers, the duplicate doubles the cost per session with

no user-visible signal. Bill goes up, user does not know why.

  • The silent last-wins behavior is exploitable by an attacker who can

write to mcp.json (e.g., a malicious VSCode extension installed via
the marketplace). The attacker can shadow the user's settings.json
config for any server, including swapping auth tokens, redirecting
upstream URLs, or appending extra args. The user would not see any
warning.

  • Migration between the two config sources is fragile. Users moving

servers from mcp.json to settings.json (or vice versa) have no tool
to detect "you have duplicates, here's which one wins".

Suggested fix surface (any one of these would be a meaningful first step)

  • (a) Document the merge priority in

https://docs.claude.com/en/docs/claude-code/mcp (the page that
currently only covers the user-level mcpServers block).

  • (b) Add /mcp sources introspection in the extension and

claude mcp doctor in the CLI. Each MCP entry should show which file
it came from.

  • (c) Emit a one-time warning in the extension panel the first time a

session loads with a server name present in both sources, with the
conflicting source filenames.

  • (d) Default to a deterministic resolution (e.g., user-level

settings.json always wins, with the mcp.json entry ignored and
logged) and surface the resolution in /mcp output.

Scope boundary

This issue is strictly about the load-time merge of the two local
file-based config sources
(mcp.json and settings.json mcpServers).
It is not about:

  • Plugin-provided MCP servers (covered separately by #63749 for

multi-plugin dedup, and #59310 for plugin-MCP tools not exposed).

  • claude.ai connector collision detection (covered by #58375 and its

inverse #59470).

  • Claude Desktop MCP server leaks (covered by #61564 and #53134).
  • MCP server content (the tools, resources, prompts the server provides).
  • MCP server install / marketplace / permission systems (separate

concerns, separate config blocks).

The distinguishing axis of this report is: same binary, two local
file-based config sources, two processes spawned, no visible signal
.

Related but distinct

  • #58375 — claude.ai connector vs plugin MCP, URL-based collision

detection. Different surface (server-side catalog vs local files).

  • #59470 — Inverse of #58375. Same surface difference.
  • #63749 — "Reduce duplicate MCP server processes when multiple plugins

wrap the same binary". Multi-plugin scope, not multi-config-file scope.

  • #61564, #53134 — Claude Desktop leaks, not VSCode extension.
  • #59310 — Plugin MCPs not exposing tools to AI. Different bug

entirely.

Validation

  • Environment: claude --version 2.1.165; ~/.config/Code - Insiders/User/mcp.json

with 9 servers; ~/.claude/settings.json with 6 servers in
mcpServers; 2 names (context7 and apify) configured in both.

  • pgrep -fa "apify-mcp-server" showed 2 processes running with

different APIFY_TOKEN env values (verified via cat /proc/<pid>/environ |
tr '\0' '\n' | grep APIFY_TOKEN
).

  • The extension's /mcp output showed the server once, with no

indication of which config source won. There is no source attribution
in the visible UI.

  • Removing the duplicates from mcp.json (keeping the entries in

settings.json only) reduced the process count to 1 and left the
available tool surface unchanged — confirming the duplicate was
loaded but shadowed.

  • This is reproducible from a clean state on a Debian forky/sid host

with VSCode Insiders 1.96+ and the anthropic.claude-code extension
at the version above.

View original on GitHub ↗