feat(mcp): manage MCP servers per provider and per agent - #1144
Merged
Merged
Conversation
Agent Code has no way to add or toggle third-party MCP servers. The
design settles on launch-time injection through the existing built-in
MCP path rather than writing provider config, the de facto mcpServers
shape with ${input:id} secret references, and per-provider defaults
plus per-agent overrides. The evidence behind each decision is recorded
in the spec so implementation does not re-derive it.
Refs #1143
Refs #244
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ervers The user approved a unified interface: built-in and user servers share one Settings grid with per-provider columns, and one staged per-agent modal replaces the individual Enable * MCP commands. Recorded as a Revision 2 section so the superseded decisions stay visible. Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The model stores servers in the de facto mcpServers entry shape so a
README snippet round-trips losslessly, with VS Code style ${input:id}
secret references that may only appear where they can travel through
the environment. The translators turn resolved servers into Claude's
private --mcp-config entries and Codex -c overrides without putting a
secret on argv or on disk, and drop a server rather than fail a launch.
Refs #1143
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main owns the server document, the encrypted secrets and the decision of what attaches to each launch: the renderer only contributes the pane's explicit per-agent choices. A requested server that cannot attach is reported with a reason and never fails the launch, and the servers each backend actually launched with are reported on its snapshot the same way built-in MCP domains are. Also lists the servers each CLI loads from its own user-scope config, read-only and with every value withheld. Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settings gains an MCP category whose grid lists Agent Code's built-in servers and the user's own servers with one column per enabled provider, so every checkbox means the same thing: new agents of that provider get it. Built-in defaults become per provider; a pre-existing flat list is copied to every provider so upgrading changes nobody's agents. Per-agent choices move to one staged Agent MCP Servers picker that applies with a single reload and keeps Root Management behind its confirmation. It retires the nine per-capability MCP commands and Use Global MCP Settings, and keeps their rule that removing Goal Loop's tools ends a running loop first. User-server choices ride the existing override map under user:<id> keys, so every path that already carries per-agent MCP choices carries them too. Adding a server is paste-first, with tokens lifted into encrypted secrets. Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aults Adds the MCP feature's control reference, README and architecture notes, and moves the remaining tests that pinned the retired commands or the flat default list onto the new contracts. Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds an mcp_servers built-in capability (off by default, configurable in Settings → MCP) whose tools list, add, update and remove the user's MCP servers and set secrets through the same service Settings uses. A skill pointing at mcp-servers.json would not work: main owns that document behind one write queue and secrets are not in it. No tool can read a secret back, and every agent-made change is announced to the user and journaled, because this list decides what runs in every future agent. Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Stops a running goal loop only on the reload that actually runs, so a declined Root Management confirmation no longer leaves the loop dead while the agent keeps its tools. Refuses MCP writes while the document cannot be read, instead of replacing the user's servers with an empty list. Surfaces the Codex protected-env rule and user-scope name collisions in Settings and the per-agent picker, and stops counting user servers towards a pending reload, so Apply can no longer stay armed for a server that is dropped at every launch. Documents the downgrade limitation of per-provider defaults. Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude user-server secrets now resolve into the private per-launch config file instead of Claude's environment, which every child process and model shell inherits; Codex keeps env delivery but excludes every carried secret from its model shells, in the form the user's own config already uses so the two never mix. Changing where a server connects forgets its secrets, so neither an edit nor an agent can redirect a stored token. Also: dotted env/header names are rejected before they can fail a Codex launch; generated secret variables are collision-free; proxy, CA and loader variables are protected from Codex; placeholders are not stored as secrets; summaries and the native list no longer carry credential-looking args or URL parts; a crashed run's private configs are swept at startup; a failed save rolls back. The editor refuses to save over a server changed elsewhere, an emptied secret field no longer clears the secret, the sign-in command is shell-quoted, a multi-server paste can be retried, and the per-agent picker can always reload. Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…und 2
An agent can now only propose MCP servers: anything it adds, or points at a
different URL, command, arguments or environment, is saved switched off and
flagged for review, and only the user can turn a server on. Orchestration
children can no longer be handed mcp_servers or root_management unless the
parent holds them, closing a path around the off-by-default gate.
Any literal change to an entry now counts as a new destination and forgets
its secrets (a literal NODE_OPTIONS could run attacker code with the token),
secret values containing ${ are refused because Claude would expand them,
and the private Claude config file is removed once Claude is ready.
Codex: shell exclusions are deduplicated case-insensitively (Codex rejects
case-only duplicates and would fail the launch), a secret never silently
replaces the user's own variable, and project config is read from every
ancestor .codex/config.toml the way Codex loads it.
Also: the goal-loop stop runs inside the reload after its checks; saves
persist before touching secret blobs; the add dialog keeps typed secrets and
saved servers across a re-parse; the edit dialog warns before a URL change
forgets secrets; agents get redacted summaries, not raw entries; Claude name
collisions are shown; per-session maps no longer leak.
Refs #1143
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… is set Refs #1143 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Agent Code could not add, configure or switch off a third-party MCP server. A user who wanted, say, the Beeper Desktop MCP server had to hand-edit
~/.claude.json/~/.codex/config.tomloutside the app. The server then landed on every agent of that provider, could not be turned off for one agent, and never appeared in Agent Code. Built-in MCP choices were also spread across eight Settings rows and ten palette commands, and each command reloaded the agent on its own.What this does
One MCP interface for every MCP server
mcpServersblocks, the VS Codeservers/inputsform, a bare map, or a single entry all work.${input:id}(VS Code's syntax). README placeholders such asYOUR_TOKEN_HEREare recognized and left unset.use-global-mcp-settingsand the eightenable-*-mcptoggles. They are listed inRETIRED_BUILT_IN_COMMAND_IDSso stale overrides are pruned.~/.claude.json/config.tomlare listed read-only, with every value withheld. Copy in imports one as a managed server.Agents can manage it too (off by default)
mcp_serversbuilt-in capability gives agentsmcp_servers_list/add/update/remove/set_secret, through the same service Settings uses.mcp_serversorroot_managementunless the parent holds them. This closes a path around off-by-default; the same hole existed forroot_managementbefore this PR.Delivery at launch, no provider config writes
mcp-servers.json(mode 0600) and thesafeStoragesecret blobs. The renderer only sends a pane's explicit per-agent choices, asuser:<id>keys in the existing override map.userMcpServerIds), the same waybuiltInMcpDomainsis reported.--mcp-configfile as the built-in server, never into Claude's environment.--config mcp_servers.<name>.*. Headers go throughenv_http_headers, and stdio secrets throughenv_vars. No secret reaches argv.shell_environment_policy. It uses whichever form (filtersor legacyexclude) the user's own config across all layers already uses, because mixing the two fails Codex's config load.user-mcp-unavailabletoast, journal entry).Design decisions
The full evidence and rationale are in
docs/superpowers/specs/2026-09-22-user-mcp-servers-design.md; see the Revision 2 section for the unified interface.~/.claude.json.codex mcp addtable rewrite.codex mcp login <name> -c …command and Claude/mcpguidance. Generated secret variable names are deterministic, so Claude's OAuth token key (name|hash(type,url,headers)) stays stable across launches.user:keys. Every path that already carries per-agent MCP choices (spawn, reload, recovery, undo, provider switch, duplicate, control API) carries them without being re-threaded.Review
Two adversarial rounds, each with 2 Claude, 1 Grok and 2 OpenCode reviewers; findings files are in the branch's git-ignored
tmp/review/. Every valid finding was fixed. Round 1 was mostly secret delivery and editor state; round 2 was mostly the agent tools' authority. Accepted residuals:notifyinherit Codex's full environment, including carried secrets. Closing that needs a file-fed wrapper (follow-up).Verification
npx tsc -b: clean.npm run test:contractandcheck:keybindings: OK.npm test: 5,756 passed. The 10 failures in the first run were tests pinning the retired commands and the old flat default shape; all are fixed here and re-run green.codex mcp list --json(codex-cli 0.155.1) parsed the generated overrides for an HTTP server (env_http_headers,auth_status: bearer_token) and a stdiomcp-remoteserver (env_vars) exactly as intended.claude mcp listignores--mcp-config, and a real check needs a model call. The file uses the same shape as the built-in server's working private config, and was checked against Claude's zod schema and${VAR}expansion invendor/claude-code-src.Known limitations / follow-ups
disabledMcpServers(set via/mcpin a repo) also applies by name to injected servers.Fixes #1143
Refs #244
🤖 Generated with Claude Code