-
-
Notifications
You must be signed in to change notification settings - Fork 80.7k
[Feature]: Support per-request dynamic resolution of ENV_VAR substitution in MCP transport headers and env #93144
Copy link
Copy link
Open
Labels
P2Normal backlog priority with limited blast radius.Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.ClawSweeper found an open linked pull request for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.ClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.ClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.ClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.ClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.enhancementNew feature or requestNew feature or requestimpact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.Auth, provider routing, model choice, or SecretRef resolution may break.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.Security boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Description
Metadata
Metadata
Assignees
Labels
P2Normal backlog priority with limited blast radius.Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.ClawSweeper found an open linked pull request for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.ClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.ClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.ClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.ClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.enhancementNew feature or requestNew feature or requestimpact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.Auth, provider routing, model choice, or SecretRef resolution may break.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.Security boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Type
Fields
Priority
None yet
Summary
MCP
${ENV_VAR}substitution in headers and env should be re-evaluated fromprocess.envat connection/request time, not frozen at Gateway startup.Problem to solve
Documentation claims
${ENV_VAR}values in MCP headers are "read from the environment at runtime" and "resolved at connection time" (docs/cli/mcp-config.md:180,:713), butresolveConfigEnvVars()executes once at Gateway startup. Runtime updates toprocess.envhave no effect on already-created MCP transports. All three transport types (stdio, SSE, streamable-http) are affected.This blocks credential rotation (OAuth token refresh), multi-tenant header switching, session-specific auth injection, and integration with apps that dynamically update
process.env(e.g., MatrixAssistant, Electron apps). The only workaround is a full Gateway restart, which drops active sessions and WebSocketProposed solution
Preserve
${ENV_VAR}templates from the pre-substitution config and re-evaluate them in the transport layer for all three transport types. The resolution granularity differs by transport due to protocol and OS constraints:streamable-http (per-request) — Pass a custom
fetchclosure toStreamableHTTPClientTransportthat re-evaluates${ENV_VAR}fromprocess.envon every HTTP request. Each tool call is an independent HTTP request, so dynamic headers take effect immediately with no connection constraints.SSE (per-request on POST channel; EventSource GET only on reconnection) — Pass a custom
fetchviaeventSourceInitfor the SSE transport. The POST channel (tool calls) supports per-request dynamic headers. The long-lived EventSource GET connection only sends headers at connection establishment; dynamic header changes on the GET side only take effect on reconnection.stdio (per-spawn) — Child process
envis fixed at OS spawn time (execve). Re-evaluate${ENV_VAR}when the child process is spawned (e.g., on Transport recreation after hot-reload). Runtimeprocess.envchanges in the parent do not propagate to the already-running child without a respawn.Implementation approach: Reuse existing
substituteString()andcontainsEnvVarReference()helpers fromsrc/config/env-substitution.ts. For HTTP transports, wrap the SDK'sfetchparameter with a closure that re-substitutes${ENV_VAR}per-request. The MCP TypeScript SDK already supports afetchparameter on bothSSEClientTransportandStreamableHTTPClientTransport(SDK PR #721).All three transport types must be addressed as part of this feature. The granularity of dynamic resolution differs due to protocol and OS constraints:
${ENV_VAR}re-evaluated when child process is (re)spawnedenvis fixed at OS spawn time (execve); requires Transport recreation to pick up env changesAlternatives considered
Gateway restart (
openclaw gateway restart) — Disruptive: active sessions are lost, WebSocket connections drop, service is interrupted. Not viable for production credential rotation.HTTP proxy injecting dynamic headers — Adds infrastructure complexity, requires external deployment, and does not help stdio transports. Not a source-level solution.
Claude Code
headersHelperapproach — Claude Code executes an external shell script per-connection to resolve dynamic headers. This adds a security surface (arbitrary script execution), requires CLI-style lifecycle management (ill-suited for daemon mode), and is more complex than thefetchclosure approach. OAuth auth provider is a more natural fit for OpenClaw's daemon lifecycle if token refresh is needed beyond env-var resolution.Config hot-reload only — The existing hot-reload mechanism (
configFingerprint) only triggers onmcp.servers.*config file edits, not onprocess.envchanges. Addingprocess.envto the fingerprint would cause spurious restarts. Per-request resolution is the correct granularity.Impact
${ENV_VAR}in MCP headers or env parameters for any transport type${ENV_VAR}usage is affected.process.envcannot propagate changesEvidence/examples
Documentation claims runtime resolution:
docs/cli/mcp-config.md:180:docs/cli/mcp-config.md:713(Security Properties table):Source code shows one-time startup substitution:
Config loading flow (current — values frozen at startup):
Competitive analysis:
headersHelper(shell script executed per-connection). Known bugs: [Bug]: Non-default agents get 401 Missing scopes: model.request with OpenAI Codex OAuth — same token works for default agent #28293, fix(reply): auto-inject replyToCurrent so replyToMode actually works #14976, WebChat UI freezes and becomes unresponsive with accumulated session history #48514.StreamableHTTPClientTransportbut without custom fetch).Additional information
${ENV_VAR}configs should work unchanged with the new dynamic resolution behavior.resolveDynamicEnv()must apply the sameisDangerousHostEnvVarName()security filter used by the existingtoMcpEnvRecord()to blockNODE_OPTIONS,LD_PRELOAD,BASH_ENV, etc.process.env; this is intentional. Dynamic resolution at the transport layer is the correct approach, not fingerprint expansion.@modelcontextprotocol/sdkversion that supportsfetchparameter on bothSSEClientTransportandStreamableHTTPClientTransport(available since SDK PR docs(showcase): add Visual Morning Briefing Scene #721).docs/analysis/mcp-dynamic-params-fix-plan.md