Skip to content

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

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

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

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Mar 17, 2026

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-actions Bot and 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>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

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 thread src/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.

Copy link
Copy Markdown
Contributor

@barryonthecape barryonthecape left a comment

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.

Copy link
Copy Markdown
Contributor

@barryonthecape barryonthecape left a comment

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.

Copy link
Copy Markdown

Copilot AI left a comment

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.

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.

Copy link
Copy Markdown

Copilot AI left a comment

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 main Mar 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 free to 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