Skip to content

MCP \environment\ block: \{env:VAR}\ interpolation does not propagate to local subprocess when var is at OS user-level env #37644

Description

@nicourrutia98

Summary

When configuring a local MCP server with an environment block that uses {env:VAR} interpolation, the interpolated values do NOT reach the MCP subprocess if the env var is set only at the OS user-level (e.g. via SetEnvironmentVariable("VAR", "val", "User") on Windows). Hardcoded literal values in the same environment block DO propagate correctly.

This breaks any MCP server that relies on operator-supplied secrets sourced from the user's shell environment (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY).

Environment

  • opencode v1.18.3 (installed via scoop on Windows 11)
  • Windows 11 23H2, PowerShell 5.1
  • MCP server: dark-memory-mcp.exe (custom Go binary, stdio transport)
  • The MCP reads env via os.Getenv("ANTHROPIC_API_KEY") and fails to find the key even though the operator can verify it exists in their shell.

Repro

  1. Set a secret at user-level env (Windows):
    [System.Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', 'sk-test-...', 'User')
  2. Configure the MCP server in ~/.config/opencode/opencode.jsonc:
    {
      "mcp": {
        "dark-memory": {
          "type": "local",
          "command": ["C:/path/to/dark-mem-mcp.exe"],
          "environment": {
            "ANTHROPIC_API_KEY": "{env:ANTHROPIC_API_KEY}",
            "SDD_LLM_BASE_URL": "https://example.com"
          }
        }
      }
    }
  3. Launch opencode from a fresh PowerShell session.
  4. Verify the var is visible in the PowerShell session: Get-ChildItem env:ANTHROPIC_API_KEY returns the value. ✓
  5. From the MCP server, log os.Getenv("ANTHROPIC_API_KEY") at boot (added a stderr print to dark-mem-mcp.exe).

Expected

The MCP subprocess receives ANTHROPIC_API_KEY as a non-empty env var (matching what the PowerShell session sees). os.Getenv("ANTHROPIC_API_KEY") in the MCP returns the value.

Actual

os.Getenv("ANTHROPIC_API_KEY") in the MCP subprocess returns empty string. The hardcoded literal SDD_LLM_BASE_URL in the same block DOES reach the MCP correctly.

# stderr from dark-mem-mcp.exe (with debug print added at boot):
dark-mem-mcp: BOOT ENV: ANTHROPIC_API_KEY="" SDD_LLM_BASE_URL="https://example.com"

Workaround (until upstream fix)

Three options, in order of preference:

  1. Hardcode the literal value in the environment block. Works for non-secrets (URLs, paths). Security risk for secrets — would put the API key in plain text in opencode.jsonc which is operator-readable.

  2. Set the var in $env: before launching opencode in the same PowerShell session:

    $env:ANTHROPIC_API_KEY = "sk-test-..."
    opencode

    Then opencode inherits it from the PowerShell process env, and {env:VAR} resolves correctly.

  3. Set the var at the System level (SetEnvironmentVariable(... 'Machine')) instead of User level. Requires admin. May have unexpected side-effects on other software.

Investigation notes

  • Confirmed the var is set at Windows user-level registry: [System.Environment]::GetEnvironmentVariable('ANTHROPIC_API_KEY', 'User') returns the value.
  • Confirmed the same PowerShell session sees the var in $env:.
  • Confirmed opencode runs (process exists) and the MCP subprocess spawns.
  • Confirmed the hardcoded literal SDD_LLM_BASE_URL in the SAME environment block IS passed to the subprocess. So the environment block IS being read and forwarded — the issue is specifically with {env:VAR} interpolation when the var is in user-level env.
  • Adding a stderr print to dark-mem-mcp.exe that dumps all relevant env vars at boot revealed the empty string clearly.

Hypothesis: {env:VAR} interpolation in opencode.jsonc resolves against opencode's own process env at the moment of subprocess spawn. If the env var was set at Windows user-level AFTER the PowerShell session that launched opencode started, PowerShell's process env won't have the var, and {env:VAR} resolves to empty.

Suggested fix direction

Either:

(a) Document the limitation in the opencode docs and recommend workarounds 1-3 above.
(b) Make {env:VAR} interpolation re-read from the OS user-level env (os.Getenv semantics in the opencode process) instead of the opencode process env.
(c) For local MCP subprocesses, always pass the entire OS user-level env (with optional explicit override from the environment block).

(c) is the most permissive but might leak unexpected vars to MCP subprocesses.

(cc anomalyco for the original repo if there's still active maintenance there.)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions