fix: ensure spawned Claude agents get proper MCP config#581
Conversation
Three fixes for spawned Claude agents not receiving correct --mcp-config: 1. Inject RELAY_API_KEY into Claude's --mcp-config JSON (Claude Code doesn't reliably inherit parent env vars to MCP server subprocesses) 2. Include project-level .claude/settings.json and settings.local.json in MCP config merge paths (matching Claude's own loading order) 3. Pass original CLI name (not resolved_cli) in spawner.rs wrap mode so CLI-specific config logic triggers correctly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- RELAY_API_KEY is now included in Claude's --mcp-config (not omitted) - Document MCP config merge order (5 sources + relaycast) - Document that spawner must pass original CLI name, not resolved_cli Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Stale doc comment on merge_relaycast_with_project_mcp omits 2 new config sources
The function-level doc comment at src/snippets.rs:245-249 lists only 3 Claude config sources (global settings, global local settings, and .mcp.json), but the implementation now loads from 5 sources after the addition of <cwd>/.claude/settings.json and <cwd>/.claude/settings.local.json at lines 301-302. The inner function's inline comment at lines 289-294 correctly lists all 5, creating an inconsistency. A developer reading only the function doc would miss two precedence levels that can override .mcp.json.
(Refers to lines 245-249)
Was this helpful? React with 👍 or 👎 to provide feedback.
barryonthecape
left a comment
There was a problem hiding this comment.
Looks good to me. I validated the three root-cause fixes in the diff:\n\n- Claude now gets injected (while preserving shared server config behavior for ).\n- Merge order now includes project-level + in the documented precedence chain.\n- Spawner now passes original to MCP config injection so provider-specific logic still triggers when differs.\n\nTests updated appropriately for the changed Claude behavior and merge expectations.
barryonthecape
left a comment
There was a problem hiding this comment.
Looks good to me. I validated the three root-cause fixes in the diff:
- Claude
--mcp-confignow getsRELAY_API_KEYinjected (while preserving shared server config behavior for.mcp.json). - Merge order now includes project-level
.claude/settings.json+settings.local.jsonin the documented precedence chain. - Spawner now passes original
clito MCP config injection so provider-specific logic still triggers whenresolved_clidiffers.
Tests are updated appropriately for the changed Claude behavior and merge expectations.
There was a problem hiding this comment.
Pull request overview
This PR fixes MCP configuration for spawned Claude agents by ensuring RELAY_API_KEY is embedded in the --mcp-config JSON, project-level .claude/settings.json files are included in the merge order, and the original CLI name is passed to config logic instead of the resolved alias.
Changes:
- Inject
RELAY_API_KEYinto Claude's inline--mcp-configJSON via newinject_api_key_into_mcp_json()helper - Add project-level
.claude/settings.jsonandsettings.local.jsonto the MCP config merge paths - Pass original CLI name (not
resolved_cli) inspawner.rsto preserve CLI-specific config logic
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/spawner.rs | Pass cli instead of &resolved_cli to configure_relaycast_mcp_with_token |
| src/snippets.rs | Add inject_api_key_into_mcp_json(), add project .claude/ config paths, update tests |
| .claude/rules/mcp-injection.md | Update documentation to reflect new merge order and API key injection |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
…wned agent MCP config PR #581 injected RELAY_API_KEY into the --mcp-config JSON for spawned agents, but RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE were still read via std::env::var inside relaycast_server_config. In "up" mode, the broker process does not have these vars in its own env — they only exist in worker_env passed to child processes. Thread workspaces_json and default_workspace explicitly through: - relaycast_server_config: new params replace std::env::var reads - merge_relaycast_with_project_mcp(_inner): pass-through params added - configure_relaycast_mcp_with_token: new params, callers updated - WorkerRegistry::spawn (PTY + Headless): extract from self.worker_env - Spawner::spawn_wrap_with_token: extract from env_vars Adds 5 new tests (63 total pass). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…wned agent MCP config (#583) * fix: forward RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE to spawned agent MCP config PR #581 injected RELAY_API_KEY into the --mcp-config JSON for spawned agents, but RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE were still read via std::env::var inside relaycast_server_config. In "up" mode, the broker process does not have these vars in its own env — they only exist in worker_env passed to child processes. Thread workspaces_json and default_workspace explicitly through: - relaycast_server_config: new params replace std::env::var reads - merge_relaycast_with_project_mcp(_inner): pass-through params added - configure_relaycast_mcp_with_token: new params, callers updated - WorkerRegistry::spawn (PTY + Headless): extract from self.worker_env - Spawner::spawn_wrap_with_token: extract from env_vars Adds 5 new tests (63 total pass). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * style: auto-format Rust code with cargo fmt * fix: use function params instead of std::env::var for workspace vars Codex, OpenCode, and Gemini/Droid paths were reading RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE from std::env::var instead of using the function parameters. This broke workspace forwarding for agents spawned from the broker's 'up' mode where these vars only exist in worker_env. - Codex: use workspaces_json/default_workspace params - OpenCode: add params to ensure_opencode_config signature - Gemini/Droid: add params to gemini_droid_mcp_add_args and configure_gemini_droid_mcp signatures - Update all call sites and test fixtures All 505 tests pass. * style: auto-format Rust code with cargo fmt * fix: thread workspace vars through Cursor and relaycast_mcp_config_json_with_token Cursor path was missed in the initial fix — ensure_cursor_mcp_config and relaycast_mcp_config_json_with_token now accept and forward workspaces_json/default_workspace params. All CLI paths (Claude, Codex, OpenCode, Gemini, Droid, Cursor) consistently use function parameters instead of std::env::var. * style: auto-format Rust code with cargo fmt * docs: update mcp-injection rule with workspace var forwarding pattern - Add RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE to env var list - Document workspace variable forwarding rule: always use function params, never std::env::var() - Add table of all functions that accept workspace params - Add Cursor to CLI Provider Support Matrix * fix: suppress clippy::too_many_arguments for workspace-forwarding functions --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…wned agent MCP config (#583) * fix: forward RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE to spawned agent MCP config PR #581 injected RELAY_API_KEY into the --mcp-config JSON for spawned agents, but RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE were still read via std::env::var inside relaycast_server_config. In "up" mode, the broker process does not have these vars in its own env — they only exist in worker_env passed to child processes. Thread workspaces_json and default_workspace explicitly through: - relaycast_server_config: new params replace std::env::var reads - merge_relaycast_with_project_mcp(_inner): pass-through params added - configure_relaycast_mcp_with_token: new params, callers updated - WorkerRegistry::spawn (PTY + Headless): extract from self.worker_env - Spawner::spawn_wrap_with_token: extract from env_vars Adds 5 new tests (63 total pass). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * style: auto-format Rust code with cargo fmt * fix: use function params instead of std::env::var for workspace vars Codex, OpenCode, and Gemini/Droid paths were reading RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE from std::env::var instead of using the function parameters. This broke workspace forwarding for agents spawned from the broker's 'up' mode where these vars only exist in worker_env. - Codex: use workspaces_json/default_workspace params - OpenCode: add params to ensure_opencode_config signature - Gemini/Droid: add params to gemini_droid_mcp_add_args and configure_gemini_droid_mcp signatures - Update all call sites and test fixtures All 505 tests pass. * style: auto-format Rust code with cargo fmt * fix: thread workspace vars through Cursor and relaycast_mcp_config_json_with_token Cursor path was missed in the initial fix — ensure_cursor_mcp_config and relaycast_mcp_config_json_with_token now accept and forward workspaces_json/default_workspace params. All CLI paths (Claude, Codex, OpenCode, Gemini, Droid, Cursor) consistently use function parameters instead of std::env::var. * style: auto-format Rust code with cargo fmt * docs: update mcp-injection rule with workspace var forwarding pattern - Add RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE to env var list - Document workspace variable forwarding rule: always use function params, never std::env::var() - Add table of all functions that accept workspace params - Add Cursor to CLI Provider Support Matrix * fix: suppress clippy::too_many_arguments for workspace-forwarding functions --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Summary
--mcp-configJSON — Claude Code doesn't reliably pass parent env vars to MCP server subprocesses, so the API key must be embedded directly in the config (same approach already used for Cursor).claude/settings.jsonandsettings.local.jsonin the MCP config merge paths, matching Claude's own settings loading orderresolved_cli) inspawner.rswrap mode so CLI-specific config logic (e.g. Claude vs Cursor) triggers correctlyRoot Cause
When an agent spawned another Claude agent (via
add_agentMCP tool or SDKspawnPty), the child agent's--mcp-configwas missing RELAY_API_KEY and project-level MCP servers because:relaycast_server_config()deliberately omits RELAY_API_KEY (codex strips it from.mcp.json), but Claude's inline--mcp-configarg needs itmerge_relaycast_with_project_mcp_inner()only read~/.claude/settings.jsonand.mcp.json, not<cwd>/.claude/settings.jsonspawner.rspassedresolved_cli(which maps "cursor" → "agent") instead of the original CLI nameTest plan
claude_includes_api_key_in_mcp_config— verifies RELAY_API_KEY is present in Claude's --mcp-configmerge_mcp_e2e_claude_spawn_with_user_servers— verifies RELAY_API_KEY in e2e merge scenario🤖 Generated with Claude Code