Skip to content

feat: a shared tool server names its own header variables, for a credential that differs per rollout - #3012

Open
faresobeid wants to merge 1 commit into
mainfrom
feat/shared-server-header-env
Open

faresobeid wants to merge 1 commit into
mainfrom
feat/shared-server-header-env

Conversation

@faresobeid

@faresobeid faresobeid commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

SharedToolServer.header_env: the MCP headers a harness sends that server, each read from a named host env var, as HarnessConfig.mcp_header_env names them. A rollout given that server sends them, merged into a copy of its harness's config (the server's win, header by header).

Why: mcp_header_env lives on the harness config, so it is the same for every rollout of an agent. A caller that signs each rollout into a server as its own account (worlds: each agent of a swarm is its own account on the team world, and the server enforces what each may do) needs a different variable per rollout. Today that means a copy of the agent per rollout with its own harness config: worlds re-runs Agent.__init__ on a copy and restores _gate, trainable and _warned_resources by hand, which breaks silently when the agent gains another private field (its rollouts would stop reaching the trainer). With this, one agent serves every rollout, and each rollout's server carries its own variable name:

tools = {"world": vf.SharedToolServer(url=url, local=False, external=True,
                                      header_env={"Authorization": "WORLD_TOKEN_B1"})}
await agent.run(task, runtime=box, tools=tools)

Secrets: unchanged. Only variable names are held, as with mcp_header_env; values are read from the host env when the harness session starts, and never enter a config, a task or a trace. SharedToolServer is a live object passed to run, never serialized.

The change

  • SharedToolServer.header_env (mcp/launch.py), default empty.
  • Harness.with_mcp_header_env(header_env) (harness.py): a shallow copy whose config's mcp_header_env also names them.
  • Rollout uses that copy when any shared server has header_env (rollout.py), so every harness that reads self.config.mcp_header_env (ACP, hermes) gets it without new parameters.

Checked: tests/unit/harnesses/test_header_env.py (merge, precedence, resolution from the env, no value in the config's dump). uv run pytest tests/unit -n auto: passed (Docker, Prime and harbor tests skipped). ruff check, ruff format --check pass.

Related: #2915 (Agent.with_config) solves the per-rollout copy in general; this removes the need for it in the per-rollout-credential case.

🤖 Generated with Claude Code

Note

Let shared tool servers name their own MCP header env variables

  • Adds a header_env field to SharedToolServer in launch.py that maps header names to host environment-variable names
  • Adds Harness.with_mcp_header_env in harness.py, which returns a copied harness whose config merges these mappings into mcp_header_env; supplied header keys override existing ones for a server present in both
  • Rollout.__init__ in rollout.py collects nonempty header_env mappings from shared tools and swaps in the merged harness when any exist
  • Adds a unit test in test_header_env.py covering merge behavior, host-env resolution, and that the resolved secret value stays out of the serialized config
  • Behavioral Change: rollouts with shared tools that carry header_env now run on a copied harness; the original harness config is never mutated

Macroscope summarized dcab198.


Note

Medium Risk
Changes how rollout-scoped MCP credentials are wired (auth-sensitive), but keeps the existing name-only config model and isolates per-rollout state via harness copies rather than mutating the agent.

Overview
Rollouts can now supply different MCP auth headers per shared tool server without cloning the whole agent/harness config.

SharedToolServer gains optional header_env: header name → host environment variable name (values still resolved at session start, not stored in config or traces). Harness.with_mcp_header_env returns a shallow harness copy whose mcp_header_env merges these mappings; keys from the shared server override the base config header-by-header, and the original harness is unchanged.

Rollout collects nonempty header_env from passed-in shared tools and runs on the merged harness copy so existing harness paths that call resolve_mcp_headers pick up rollout-specific credentials automatically.

A unit test covers merge precedence, env resolution, and that resolved secret values never appear in serialized config.

Reviewed by Cursor Bugbot for commit dcab198. Bugbot is set up for automated code reviews on this repo. Configure here.

…ential that differs per rollout

SharedToolServer.header_env joins the harness config's mcp_header_env for the rollouts given that server, so
callers that sign each rollout into a server as its own account need no copy of the agent per rollout.
@faresobeid
faresobeid marked this pull request as ready for review October 30, 2026 14:03
@macroscopeapp

macroscopeapp Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at dcab198

Macroscope's review found this PR approvable — Shared tool servers can now specify host-environment header variables for a rollout-local harness copy. The mapping defaults to empty and reuses existing credential resolution, leaving ordinary rollouts unchanged.

You can add or adjust custom eligibility rules. Learn more.

This branch has not been deployed

No deployments
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.

2 participants