Repository navigation
Description
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:
- 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. - Misleading diagnostic.
action="rejected malformed JSON or JSONC document"is factually wrong. The file has no BOM, parses under strictjson.loads, and is byte-identical apart from that one added line.opencode debug configalso 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
- Have a valid global config at
C:\Users\<user>\.config\opencode\opencode.jsoncwith several MCP servers. Confirmopencode mcp listshows all of them asconnected. - Add
"environment": { "PATH": "{env:PATH};C:\\extra\\bin" }to onelocalserver's entry. - Run
opencode reload(or restart the service). opencode mcp list→No MCP servers configured.- Delete just that one
environmentblock 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 (
timeoutfield silently drops one server entry). That one is scoped to a single entry withaction="skipped malformed recognized value"and a field-level path. Here the action isaction="rejected malformed JSON or JSONC document"atpath=$, 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
PATHin itsenvironmentblock. There is no working alternative in config — users have to either abandon{env:...}entirely, mutate the global userPATHinstead, or symlink/copy binaries into an already-on-PATHdirectory. Note that pointing at a venv'sScriptsdirectory is not a safe substitute on Windows, since itspython.exeshadows the system interpreter. - Redacted for privacy: real username, drive paths, and repository locations replaced with placeholders.
Activity
github-actions commentedon Sep 27, 2026
This issue might be a duplicate of existing issues. Please check:
{env:...}substitutions in local MCP server environment settings, though mcp: service env ({env:...}) not passed to local MCP servers — regression in 2.0.14/2.0.15 #50882 reports missing variables in the spawned process rather than rejection of the entire config.neriousy commentedon Oct 20, 2026
Fixed by #55915, released in v2.0.31.
{env:...}values containing backslashes or quotes, like a WindowsPATH, 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.