Skip to content

mcp: {env:PATH} in a local server's environment rejects the entire config file, dropping all MCP servers #51637

Closed

Description

@z291173301

Description

Using a {env:...} substitution in a local MCP server's environment block causes the entire opencode.jsonc document to be rejected during config normalization. Every configured MCP server disappears — not just the one containing the substitution — and the log blames JSON syntax, which sends you debugging a file that is actually valid.

Minimal trigger — append one line to an otherwise valid config:

"mcp": { "servers": { "demo": {
  "type": "local",
  "command": ["python.exe", "server.py"],
  "environment": { "PATH": "{env:PATH};C:\\extra\\bin" }
}}}

The path=$ diagnostic (document root, not a field path) is the tell that the whole document was thrown away rather than one entry being skipped.

Two things make this especially costly:

  1. Blast radius. 10 unrelated MCP servers (local + remote) all went offline. There is no partial degradation and no error surfaced on the CLI beyond No MCP servers configured.
  2. Misleading diagnostic. action="rejected malformed JSON or JSONC document" is factually wrong. The file has no BOM, parses under strict json.loads, and is byte-identical apart from that one added line. opencode debug config also lists the file as a loaded document.

Literal (non-placeholder) values in the same environment block work fine — "MY_VAR": "literal" is passed to the child. Only the {env:...} form triggers this.

Plugins

@tarquinen/opencode-dcp

OpenCode version

2.0.18 (opencode v2.0.18), channel latest, Windows build

Steps to reproduce

  1. Have a valid global config at C:\Users\<user>\.config\opencode\opencode.jsonc with several MCP servers. Confirm opencode mcp list shows all of them as connected.
  2. Add "environment": { "PATH": "{env:PATH};C:\\extra\\bin" } to one local server's entry.
  3. Run opencode reload (or restart the service).
  4. opencode mcp list → No MCP servers configured.
  5. Delete just that one environment block and reload → all servers come back immediately.

Verified the file is valid at the broken state: no BOM (first bytes 7B 0A 20 20 22 24 73 63), strict json.loads succeeds, all 10 server keys present.

Log line (role=server), repeated on every reload:

level=WARN message="configuration normalization diagnostic"
  source="C:\\Users\\<user>\\.config\\opencode\\opencode.jsonc"
  path=$ kind=invalid action="rejected malformed JSON or JSONC document"

opencode service restart does not recover it. Only editing the config does.

Screenshot and/or share link

No response

Operating System

Windows 11 Pro, build 26200 (Microsoft Windows NT 10.0.26200.0, AMD64)

Terminal

No response (reproduced via opencode mcp list from the desktop app's bundled service)


Expected Behavior

A {env:...} substitution in environment should resolve against the service env and be passed to the spawned child, as documented. If the substitution genuinely cannot be resolved, the failure should be scoped to the offending server entry (as with #50807) and should name the actual field — not reject the whole document and not report a JSON syntax error.

Actual Behavior

The entire config document is discarded. All MCP servers — local and remote, including ones with no environment block at all — vanish from the catalog, and the only diagnostic misattributes the cause to malformed JSON.

Additional Context

  • Related but distinct from mcp: server entries with a "timeout" field are silently dropped from the normalized config #50807 (timeout field silently drops one server entry). That one is scoped to a single entry with action="skipped malformed recognized value" and a field-level path. Here the action is action="rejected malformed JSON or JSONC document" at path=$, so the loss is total.
  • Related but distinct from mcp: service env ({env:...}) not passed to local MCP servers — regression in 2.0.14/2.0.15 #50882 ({env:...} not passed to local MCP servers, 2.0.14/2.0.15). That report describes the placeholder silently resolving to nothing, leaving the server running in a degraded state. Here the config never loads at all.
  • Practical impact: the natural way to let a stdio MCP server see extra tools is to extend PATH in its environment block. There is no working alternative in config — users have to either abandon {env:...} entirely, mutate the global user PATH instead, or symlink/copy binaries into an already-on-PATH directory. Note that pointing at a venv's Scripts directory is not a safe substitute on Windows, since its python.exe shadows the system interpreter.
  • Redacted for privacy: real username, drive paths, and repository locations replaced with placeholders.

Activity

  1. github-actions commented on Sep 27, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. neriousy commented on Oct 20, 2026

    @neriousy
    Member

    Fixed by #55915, released in v2.0.31. {env:...} values containing backslashes or quotes, like a Windows PATH, are now escaped instead of making the whole config file get rejected. If you still see this on v2.0.31 or later, comment and we'll reopen.

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