Skip to content

mcp: service env ({env:...}) not passed to local MCP servers — regression in 2.0.14/2.0.15 #50882

Closed

Description

@alexklink

Summary

Since upgrading to opencode 2.0.14/2.0.15, local MCP servers configured with {env:VAR} placeholders in their environment block are spawned without any of the variables stored in the service env (service.json / opencode service get env). Servers that require those vars crash on startup, so opencode mcp list reports Connection closed. Before the upgrade (2.0.13 service) all of them connected with full tool sets.

Environment

  • opencode version: 2.0.15 (also reproduced on 2.0.14 service; worked on 2.0.13)
  • OS: Darwin 25.6.0 (macOS, arm64)
  • Terminal: Apple_Terminal, xterm-256color, truecolor
  • Shell: /bin/zsh
  • Install/channel: latest; background service launched from the OpenCode desktop app bundled CLI (.../ai.opencode.desktop/cli/2.0.15/opencode-cli serve --service)
  • Active plugins: none found

Reproduction

  1. Configure a local MCP server in ~/.config/opencode/opencode.jsonc with an environment block using substitutions:
    { "mcp": { "servers": { "demo": {
        "type": "local",
        "command": ["python3", "/tmp/env_dependent_mcp.py"],
        "environment": { "REQUIRED_VAR": "{env:MY_VAR}" }
    }}}}
  2. Store MY_VAR in the service env (e.g. ~/.config/opencode/service.json env block).
  3. Start the opencode service (2.0.14+) and run opencode mcp list.
  4. Observe the child process: the placeholder variable is not set (verified with a debug server that dumps its env — every key from service.json is missing in the child).

Expected Behavior

{env:MY_VAR} should resolve against the service env for spawned MCP processes, as it did on the 2.0.13 service (all servers connected: atlassian tools=98, cedrus tools=8, gp_* tools=9). opencode service get env still returns all configured variables.

Actual Behavior

Every local MCP server whose environment references {env:...} fails to start: the variables are absent in the child environment, so the servers exit with their "missing config" errors and opencode mcp list shows failed: Connection closed. Concrete symptoms:

  • postgres-mcp (uvx): exits ValueError: Error: No database URL provided. Please specify via 'DATABASE_URI' environment variable → Connection closed.
  • trino-mcp: exits ValueError: TRINO_USER and TRINO_PASSWORD must be set for password authentication → Connection closed.
  • mcp-atlassian connects but with tools=0 (tokens absent) while previously it had 98 tools.

Trigger: today 07:43 UTC opencode upgrade (2.0.13 -> 2.0.14/2.0.15) followed by a service restart. Repeated failures on every subsequent connect attempt (consistent, not intermittent).

Additional Context

  • Log excerpt (role=server): mcp connect failed ... status.error="Connection closed" for each affected server; mcp connected server=<name> tools=0 for atlassian.
  • Direct proof: a minimal MCP server that dumps os.environ was spawned through the service and saw all service.json env keys as missing, while opencode service get env returned them.
  • Literal (non-placeholder) values in environment do reach the child: a server with a literal DATABASE_URI started and hung on the DB connection instead of crashing instantly on the "No database URL" check.
  • Manual launch of the same commands with the env exported works fine (servers respond to the MCP initialize handshake).
  • Workaround applied: replaced {env:...} with literal values in the config — all five servers now connect (atlassian tools=98, cedrus tools=8, gp_smkt/gp_mm/gp_sbl tools=9).

Activity

  1. alexklink commented on Sep 23, 2026

    @alexklink
    Author

    Same root cause also breaks provider API-key resolution, not just MCP child processes:

    • A custom provider configured with "env": ["SAMOKAT_LLM_API_KEY"] (openai-compatible) fails every request on 2.0.14/2.0.15 with Authentication Error, No api key passed in. — the key stored in service.json (opencode service set env) is no longer visible to the request path, even though opencode service get env returns it.
    • Verified: opencode run -m ... reproduces the error; direct curl with the same key works (so the upstream API is fine).
    • Workaround that works: store the key as a credential instead of env (/api/integration/<id>/connect/key / opencode auth login), which bypasses env resolution entirely.
    • The env-var path is what's broken: {env:...} placeholders in MCP environment blocks, and env: keys in provider configs, no longer resolve against the service env after upgrading from 2.0.13.
  2. yang0228 commented on Sep 23, 2026

    @yang0228
    Contributor

    I've opened #50907 against v2 for this issue.

    The CLI launcher injects the saved service environment, but Desktop can start serve --service directly without it. The fix loads service.json's environment before importing the server, so config substitution and local MCP child processes can see those values.

    The regression tests start a real service and MCP process, covering missing/inherited variables, empty overrides, and preservation of unrelated inherited values. They fail without the fix and pass with it. Local CLI tests: 293 passed, 9 skipped, 0 failed; repository lint/typechecks and Effect checks also passed.

    The fix is at the shared service startup boundary, not MCP-specific, but I haven't independently verified the custom-provider authentication case mentioned above.

    If this is still available, could you assign it to me? I'm happy to follow up on review feedback and additional reproductions.

  3. alexklink commented on Sep 24, 2026

    @alexklink
    Author

    Thanks for the deep dive, @yang0228 — the root cause matches what we saw exactly, and I can confirm the custom-provider case is the same code path, not a separate bug:

    • The provider config uses "env": ["SAMOKAT_LLM_API_KEY"]; when the service process lacks the injected saved env, the key isn't found at request time → no Authorization header → upstream answers Authentication Error, No api key passed in.
    • Loading config.env into process.env at service startup (Object.assign) fixes that too, because provider env resolution reads process.env. So the fix is not MCP-specific — good.

    Behavior notes on the proposed semantics (from the test cases): service.json values take precedence over inherited ones (configured over inherited), empty overrides are preserved, and unrelated inherited values survive. That looks right to us.

    Two small confirmations from our side:

    • Via the CLI launcher path the env was injected and everything worked (we reproduced only from Desktop-launched serve --service); consistent with your analysis.
    • The regression tests that spawn a real service + MCP process match our manual reproduction.

    We can't assign the issue from here (no triage permissions on this repo) — if you can, please assign it to yourself.

    Happy to validate the fix against our Desktop setup once a build with it lands.

  4. self-assigned this
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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