Skip to content

fix: ensure spawned Claude agents get proper MCP config - #581

Merged
khaliqgant merged 3 commits into
mainfrom
fix-spawned-mcp-claude-config
Mar 17, 2026
Merged

fix: ensure spawned Claude agents get proper MCP config#581
khaliqgant merged 3 commits into
mainfrom
fix-spawned-mcp-claude-config

Conversation

@khaliqgant

@khaliqgantkhaliqgant commented Mar 17, 2026

Copy link
Copy Markdown
Member

Summary

  • Inject RELAY_API_KEY into Claude's --mcp-config JSON — 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)
  • Include project-level .claude/settings.json and settings.local.json in the MCP config merge paths, matching Claude's own settings loading order
  • Pass original CLI name (not resolved_cli) in spawner.rs wrap mode so CLI-specific config logic (e.g. Claude vs Cursor) triggers correctly

Root Cause

When an agent spawned another Claude agent (via add_agent MCP tool or SDK spawnPty), the child agent's --mcp-config was missing RELAY_API_KEY and project-level MCP servers because:

  1. relaycast_server_config() deliberately omits RELAY_API_KEY (codex strips it from .mcp.json), but Claude's inline --mcp-config arg needs it
  2. merge_relaycast_with_project_mcp_inner() only read ~/.claude/settings.json and .mcp.json, not <cwd>/.claude/settings.json
  3. spawner.rs passed resolved_cli (which maps "cursor" → "agent") instead of the original CLI name

Test plan

  • claude_includes_api_key_in_mcp_config — verifies RELAY_API_KEY is present in Claude's --mcp-config
  • merge_mcp_e2e_claude_spawn_with_user_servers — verifies RELAY_API_KEY in e2e merge scenario
  • All 213 lib tests pass

🤖 Generated with Claude Code


Open with Devin

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>
github-actionsBotand others added 2 commits March 17, 2026 13:28
- 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>

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment threadsrc/snippets.rs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@barryonthecapebarryonthecape left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@barryonthecapebarryonthecape left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. I validated the three root-cause fixes in the diff:

  • Claude --mcp-config now gets RELAY_API_KEY injected (while preserving shared server config behavior for .mcp.json).
  • Merge order now includes project-level .claude/settings.json + settings.local.json in the documented precedence chain.
  • Spawner now passes original cli to MCP config injection so provider-specific logic still triggers when resolved_cli differs.

Tests are updated appropriately for the changed Claude behavior and merge expectations.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_KEY into Claude's inline --mcp-config JSON via new inject_api_key_into_mcp_json() helper
  • Add project-level .claude/settings.json and settings.local.json to the MCP config merge paths
  • Pass original CLI name (not resolved_cli) in spawner.rs to preserve CLI-specific config logic

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

FileDescription
src/spawner.rsPass cli instead of &resolved_cli to configure_relaycast_mcp_with_token
src/snippets.rsAdd inject_api_key_into_mcp_json(), add project .claude/ config paths, update tests
.claude/rules/mcp-injection.mdUpdate 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.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@khaliqgant
khaliqgant merged commit 6ad5f9b into mainMar 17, 2026
43 of 44 checks passed
@khaliqgant
khaliqgant deleted the fix-spawned-mcp-claude-config branch March 17, 2026 16:33
khaliqgant added a commit that referenced this pull request Mar 18, 2026
…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>
khaliqgant added a commit that referenced this pull request Mar 18, 2026
…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>
khaliqgant added a commit that referenced this pull request Mar 25, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@khaliqgant@barryonthecape