pub fn write_mcp_json_respecting_user_servers(
base_path: &Path,
generated_content: &str,
) -> Result<()>Expand description
Write .mcp.json into base_path without destroying a user’s own MCP
server entries (issue #3680). Shared by agent.open and the
WriteAgentConfig “click Launch” path — the two config-materializing call
sites this module keeps in sync.
An agent’s working_directory may be the user’s own project, where a
.mcp.json often already exists (and is often committed). A plain
fs::write replaced it, dropping every server entry the agent definition
didn’t duplicate. Instead:
- Merge. Every existing
mcpServersentry is kept, except the ones a previous materialization wrote (perMANAGED_MCP_SERVERS_MANIFEST), which are replaced by this launch’s set — so a server unbound in Connectors still disappears. Other top-level keys in the existing file are kept too. On a name clash this launch’s entry wins (theagentmuxentry must always be ours). - No manifest yet (first launch after this change, or a user’s file):
a file that already has an
agentmuxentry was written by AgentMux, so all its entries are treated as managed — the old regenerate-every-launch behavior, once. A file without one is the user’s; all of it is kept. - Unreadable or not a JSON object → left untouched and logged, never
overwritten — the same “never overwrite a stranger’s file” rule
write_claude_md_respecting_ownershipfollows. Claude Code couldn’t parse such a file either. - Owner-only. The file carries the agent’s jekt signing material
(
inject_jekt_signing_keys_into_mcp_json), so it is written0600on Unix, via a temp file renamed into place so it is never briefly visible with the default mode. - One launch at a time per directory.
.mcp.jsonand its manifest must describe the same launch, or a server AgentMux wrote gets mistaken for the user’s and is never removed. Neither call site serializes by directory (agent_open’s lock is per agent;WriteAgentConfighas none), so the whole read-merge-write holds an exclusive lock onMCP_JSON_WRITE_LOCK. A file lock rather than an in-process mutex, so two AgentMux instances pointed at one project are serialized too; it is released when the handle drops, including on error or panic.
Not addressed here: the signing material being in this file at all
(the identity plan’s M4d/M5 move it out — see
SPEC_AGENT_IDENTITY_CARRIED_NOT_DERIVED_2026_09_23.md §6.5), and a
user’s project not git-ignoring .mcp.json.