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
- Set a secret at user-level env (Windows):
[System.Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', 'sk-test-...', 'User')
- 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"
}
}
}
}
- Launch opencode from a fresh PowerShell session.
- Verify the var is visible in the PowerShell session:
Get-ChildItem env:ANTHROPIC_API_KEY returns the value. ✓
- 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:
-
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.
-
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.
-
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.)
Summary
When configuring a
localMCP server with anenvironmentblock 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. viaSetEnvironmentVariable("VAR", "val", "User")on Windows). Hardcoded literal values in the sameenvironmentblock 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
dark-memory-mcp.exe(custom Go binary, stdio transport)os.Getenv("ANTHROPIC_API_KEY")and fails to find the key even though the operator can verify it exists in their shell.Repro
~/.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" } } } }Get-ChildItem env:ANTHROPIC_API_KEYreturns the value. ✓os.Getenv("ANTHROPIC_API_KEY")at boot (added a stderr print to dark-mem-mcp.exe).Expected
The MCP subprocess receives
ANTHROPIC_API_KEYas 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 literalSDD_LLM_BASE_URLin the same block DOES reach the MCP correctly.Workaround (until upstream fix)
Three options, in order of preference:
Hardcode the literal value in the
environmentblock. Works for non-secrets (URLs, paths). Security risk for secrets — would put the API key in plain text inopencode.jsoncwhich is operator-readable.Set the var in
$env:before launching opencode in the same PowerShell session:Then opencode inherits it from the PowerShell process env, and
{env:VAR}resolves correctly.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
[System.Environment]::GetEnvironmentVariable('ANTHROPIC_API_KEY', 'User')returns the value.$env:.SDD_LLM_BASE_URLin the SAME environment block IS passed to the subprocess. So theenvironmentblock IS being read and forwarded — the issue is specifically with{env:VAR}interpolation when the var is in user-level env.Hypothesis:
{env:VAR}interpolation inopencode.jsoncresolves 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.Getenvsemantics 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
environmentblock).(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.)