Skip to content

mcp: {env:NAME} substitution in mcp.servers.*.environment resolves to empty #55229

Closed as not planned

Description

@karolkieglerski

Summary

Environment-variable substitution {env:NAME} in mcp.servers.*.environment resolves to an empty string, even though the variable is present in the OpenCode service process environment. Static values in the same environment map pass through correctly, so only substitution is broken. Documented at https://opencode.ai/v2/docs/mcp-servers ("Use {env:NAME} for environment substitution").

Environment

  • opencode version: v2.0.25
  • OS: Darwin 25.6.0 (darwin arm64) — macOS, zsh
  • Terminal: iTerm.app, TERM=xterm-256color, COLORTERM=truecolor
  • Shell: /bin/zsh
  • Install/channel: latest (release)
  • Active plugins: one local plugin discovered from ~/.config/opencode/plugins/icm.ts (unrelated to this issue); no package plugins configured

Reproduction

  1. Export a test variable in the environment that launches OpenCode (verified present in the service process env via ps eww <service-pid>):

    export HEVY_API_KEY=test-secret-value
    opencode
  2. Add a local MCP server to opencode.json that dumps its received environment:

    {
      "mcp": {
        "servers": {
          "envtest": {
            "type": "local",
            "command": ["zsh", "-c", "env | grep -E '^(HEVY_API_KEY|DBG)' > /tmp/env-debug.txt; exit 1"],
            "environment": {
              "HEVY_API_KEY": "test-static-value",
              "DBG": "{env:HEVY_API_KEY}"
            }
          }
        }
      }
    }
  3. Run opencode mcp list (or let the server auto-connect) and inspect /tmp/env-debug.txt.

Expected Behavior

DBG contains test-secret-value — the value of HEVY_API_KEY from the environment, per the documented {env:NAME} substitution.

Actual Behavior

DBG is an empty string. HEVY_API_KEY=test-static-value is received correctly, proving the environment map itself is applied — only {env:...} substitution resolves to empty.

Real-world impact (a local MCP server requiring a secret):

✗ my-server  failed: Connection closed: MCP server process exited with code 1:
  Hevy API key is required. Provide it via the HEVY_API_KEY environment variable.

— despite HEVY_API_KEY being set in the very same environment that runs OpenCode.

Additional Context

  • Reproducible consistently (100%, multiple runs).

  • Hypothesis: MCP server processes are spawned with a sanitized environment and substitution is resolved against that sanitized environment rather than the parent process environment. Alternatively, substitution may never be applied to the environment map.

  • opencode.json used: plain JSON project config (mcp section only).

  • Workaround that works: bypass substitution with a wrapper that reads the secret at spawn time, e.g.

    "command": ["zsh", "-c", "export HEVY_API_KEY=\"$(cat ~/.config/opencode/hevy-api-key)\"; exec hevy-mcp"]
  • Recent changes around the repro: fresh opencode v2.0.25 install, first MCP server configuration in this project.

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report and the env-dump server. That made this easy to test.

    I couldn't reproduce it on 2.0.25 (Linux). Here's what I tried with your config:

    • Service started from a shell where HEVY_API_KEY was exported: the MCP server received HEVY_API_KEY=test-static-value and DBG=test-secret-value. Substitution worked, and the static override of the same name didn't interfere.
    • Service already running before the variable was exported: DBG came out empty, exactly as you describe. {env:...} is resolved inside the background service against the service's own environment. Exporting a variable in a new shell, then running opencode or opencode mcp list, doesn't change a service that's already running. An unset variable is silently replaced with an empty string (Missing {env:VAR} in config silently resolves to empty string #33853).

    The service keeps running after you start opencode, so it may predate your export, or it may have been started by the desktop app. Could you try these and say whether either fixes it?

    1. From a shell where the variable is exported: opencode service restart, then opencode mcp list.
    2. Or store it for the service: opencode service set env HEVY_API_KEY <value>, then opencode service restart.

    If DBG is still empty after a restart, please tell us how the service gets started (CLI, desktop app or a login item) and which PID you checked with ps eww. Also include the output of opencode service get env.

    Related issues, ordered by likelihood:

  2. opencode-agent commented on Oct 16, 2026

    @opencode-agent
    Contributor

    We haven't heard back, so I'm closing this as not planned for now.

    On 2.0.25, {env:HEVY_API_KEY} in an MCP server's environment resolved correctly when the background service had the variable. DBG only came out empty when the service was already running before the variable was exported. If you still see it after opencode service restart from a shell that exports the variable, or after opencode service set env HEVY_API_KEY <value> followed by a restart, please reply. Include how the service is started (CLI, desktop app or a login item) and the output of opencode service get env, and we can reopen this.

    Related: #50882 (service missing saved env when the desktop app starts it) and #33853 (unset {env:...} silently becomes empty). Thanks again for the clear report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions