Skip to content

feat(apr-code): thread .mcp.json env field to subprocess spawn (PMAT-CODE-MCP-ENV-001)#1574

Merged
noahgift merged 18 commits into
mainfrom
feat/apr-code-mcp-env-threading
May 13, 2026
Merged

feat(apr-code): thread .mcp.json env field to subprocess spawn (PMAT-CODE-MCP-ENV-001)#1574
noahgift merged 18 commits into
mainfrom
feat/apr-code-mcp-env-threading

Conversation

@noahgift
Copy link
Copy Markdown
Contributor

@noahgift noahgift commented May 7, 2026

Summary

Closes PMAT-CODE-MCP-ENV-001 — the P2 follow-up from PMAT-CODE-MCP-JSON-LOADER-001 (which noted env was parsed but not applied). This PR plumbs it end-to-end.

Three-layer plumbing

Layer Change
manifest.rs McpServerConfig gains env: BTreeMap<String,String> (serde-default; back-compat)
mcp_json.rs entry_to_config copies entry.env.clone() into the config (was previously dropping it)
tool/mcp_client.rs New StdioMcpTransport::new_with_env; send_jsonrpc applies via Command::env(k,v); old new(...) preserved

register_mcp_client_tools updated to call new_with_env. Both McpServerConfig and McpTransport now derive Default so test literals can use ..Default::default().

Tests

  • 4 new mcp_json tests — parse → convert → merge env preservation; empty-env case
  • 3 new mcp_client tests — back-compat ctor empty-env, new ctor stores pairs, LIVE subprocess smoke spawning sh -c 'printf ... "$MCP_ENV_TEST_KEY"' and verifying the env var arrived
  • All existing tests pass (26 mcp_json, 29 mcp_client) — McpServerConfig literal sites updated with env: Default::default()

Doc updates

McpServerEntry.env doc comment fixed: was "parsed but not applied — tracked in PMAT-CODE-MCP-ENV-001 (P2)"; now "Threaded through to subprocess spawn ... PMAT-CODE-MCP-ENV-001 (CLOSED 2026-05-07)".

Test plan

  • cargo test -p aprender-orchestrate --lib agent::mcp_json --features agents-mcp — 26/26 pass
  • cargo test -p aprender-orchestrate --lib agent::tool::mcp_client --features agents-mcp — 29/29 pass (incl. LIVE subprocess smoke)
  • cargo build -p aprender-orchestrate --features agents-mcp — clean
  • cargo fmt -p aprender-orchestrate -- --check clean

🤖 Generated with Claude Code

…CODE-MCP-ENV-001)

Closes PMAT-CODE-MCP-ENV-001 — the P2 follow-up from
PMAT-CODE-MCP-JSON-LOADER-001. Previously the `env` field on
.mcp.json's `mcpServers` entries was parsed (the JSON shape was
deny-unknown-fields safe) but DISCARDED at the
McpServerEntry → McpServerConfig conversion step.

Three-layer plumbing:

1. **manifest.rs** — `McpServerConfig` gains `env: BTreeMap<String, String>`
   (with `#[serde(default)]` so existing TOML manifests stay valid). Both
   the struct and `McpTransport` derive `Default` so test literals can
   use `..Default::default()` if they prefer.

2. **mcp_json.rs** — `entry_to_config` now copies `entry.env.clone()`
   into the produced `McpServerConfig.env` (single-line fix at the
   step that was previously dropping the field).

3. **tool/mcp_client.rs** — `StdioMcpTransport` gains an `env` field +
   a new `new_with_env(server, command, env)` constructor.
   `send_jsonrpc` (the spawn site shared by `call_tool` and
   `discover_tools`) now applies env via `tokio::process::Command::env`
   for each (k, v) pair. The existing `new(...)` constructor is
   preserved for back-compat — it just yields an empty env map.

`register_mcp_client_tools` updated to call `new_with_env(...)` so the
manifest's env reaches the spawned subprocess in the live happy path.

## Tests

* 4 new mcp_json tests:
  - `parse_entry_env_is_collected` — env shape parses unchanged
  - `entry_to_config_threads_env` — env flows through conversion
  - `entry_to_config_preserves_empty_env` — no-env yields empty map
  - `merge_threads_env_to_manifest_servers` — end-to-end JSON →
    AgentManifest.mcp_servers[].env

* 3 new mcp_client_tests:
  - `test_stdio_transport_new_starts_with_empty_env` — back-compat
    constructor
  - `test_stdio_transport_new_with_env_carries_pairs` — new constructor
    stores env
  - `test_stdio_transport_env_flows_to_subprocess` — LIVE smoke
    spawning `sh -c 'printf ... "$MCP_ENV_TEST_KEY"'` and verifying
    the env var reached the spawned process. CI-skipped (subprocess
    timing unreliable) but passes locally.

* All existing mcp_json (26 total now) + mcp_client (29 total now)
  tests still pass — the McpServerConfig literal-construction sites
  in `runtime_tests_guards.rs` got `env: Default::default()` added.

## Doc updates

`McpServerEntry.env` doc comment updated to reflect that it's no
longer "parsed but not applied" — fixed pointer to make the next
operator who reads it see the correct status.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@noahgift noahgift enabled auto-merge (squash) May 7, 2026 17:48
@noahgift noahgift merged commit f26d1ec into main May 13, 2026
10 checks passed
@noahgift noahgift deleted the feat/apr-code-mcp-env-threading branch May 13, 2026 08:27
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.

1 participant