opencode-config-valid¶
opencode.json and opencode.jsonc must parse and use keys and MCP server shapes OpenCode reads
| Severity | error (auto) |
| Autofix | - |
| Since | v0.20.0 |
| Repo Types | opencode |
| Category | OpenCode |
Why¶
opencode.json is where an OpenCode project declares its MCP servers, its
agents and its slash commands. It ships in the repository, so a shape its
loader rejects stops OpenCode from starting for everyone on the team, and a
misspelled key fails quietly: OpenCode does not find the key it wanted and
carries on with a default. An MCP server whose command was
written as a string rather than an argv array makes OpenCode refuse to
start at all.
OpenCode configuration schemas have evolved: 1.x spellings (agent, command,
permission, flat MCP server declarations) and 2.0 spellings (agents, commands,
permissions, nested mcp.servers) are both supported.
OpenCode merges agent/agents and command/commands by entry name.
Different names may appear in both sections; only conflicting definitions of
the same name are reported. One-to-one renamed settings still accept either
spelling but report when both are present.
The MCP servers declared in OpenCode configuration are also evaluated by policy
rules such as mcp-prohibited.
Severity¶
Errors — OpenCode refuses to load a project configuration with any of
these, so opencode exits before it starts:
- The top-level document is not an object, or the file has syntax errors
(comments and trailing commas in
.jsoncare supported). - An agent or command section that is not an object, an entry that is not
an object, a
templatethat is not a non-empty string, or an entry field of the wrong type. - An MCP server with a missing or unknown
type, acommandthat is not a non-empty array of strings, a non-string or emptyurl, anenvironment,headersoroauththat is not an object (oauth: falseis the documented way to switch OAuth off), atimeoutthat is neither a number nor an object ofstartup/catalog/execution/request, or a non-booleanenabled/disabled. - Committed credentials or secrets detected in MCP server URLs, headers, or environment mappings.
The one shape OpenCode tolerates is a server carrying a boolean enabled:
the 1.x mcp union has a bare {"enabled": …} toggle branch that ignores
other properties, so a broken server with enabled loads as a toggle and
simply never starts. Its shape findings are warnings.
Warnings — the file loads, but one setting is dead: both spellings of one
renamed key (including the 1.x and 2.0 OAuth field names), a server declared
under both layouts at once, a $schema that is not a string, and a $schema
pointing at https://opencode.ai/tui.json, which describes tui.json rather
than this file.
Information-level findings never fail a build:
- An unrecognized top-level key. OpenCode's schema changes weekly, so a key
this release has not heard of is more likely new than wrong —
extra-keysaccepts it without waiting for a skillsaw release. - An unrecognized key on an MCP server. Same reasoning, same remedy:
extra-keyscovers these too. - A
$schemathat is neither the documented URL nor the TUI one. A vendored or mirrored copy is legitimate, so this is a note rather than a defect.
Examples¶
Bad — a Claude-shaped MCP server in an OpenCode config, and a file that declares one setting twice:
{
"$schema": "https://opencode.ai/config.json",
"agent": { "reviewer": { "prompt": "Review this change." } },
"agents": { "reviewer": { "system": "Review this change." } },
"mcp": {
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
}
}
type: "stdio" is not a transport OpenCode knows, command must be the
argv array, there is no args key, and agent/agents define reviewer
differently. OpenCode merges those sections and keeps the agent entry when
names overlap. The unknown transport is reported first and on its own: the rest
of a server's shape depends on which transport it is, so those checks resume
once type is fixed and the file is linted again.
Good, 1.x spelling — comments and a trailing comma are fine:
{
"$schema": "https://opencode.ai/config.json",
// Local servers are spawned directly, so command is argv.
"mcp": {
"playwright": {
"type": "local",
"command": ["npx", "-y", "@playwright/mcp@latest"],
"enabled": true,
}
},
"agent": {
"reviewer": {
"description": "Reviews a diff for correctness bugs",
"prompt": "Review this change.",
"disable": false
}
}
}
Good, 2.0 spelling — the same configuration after migrating:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"servers": {
"playwright": {
"type": "local",
"command": ["npx", "-y", "@playwright/mcp@latest"],
"disabled": false,
"timeout": { "catalog": 30000, "execution": 30000 }
}
}
},
"agents": {
"reviewer": {
"description": "Reviews a diff for correctness bugs",
"system": "Review this change.",
"disabled": false
}
}
}
How to fix¶
- Give every MCP server a
typeoflocalorremote. Alocalserver needscommandas a non-empty array of strings; aremoteserver needs aurl. - Entries with different names may be split between
agent/agentsorcommand/commands; OpenCode merges them. Keep only one definition when the same name occurs in both sections. For one-to-one settings, keep one spelling:promptorsystem,enabledordisabled.enabledanddisabledare the same switch with the sense inverted, so a server carrying both is saying two different things. - Replace a committed credential with OpenCode's substitution syntax:
{env:VAR} and {file:./path} both work, and skillsaw recognises them as
placeholders.
- For a key newer than this skillsaw release, accept it without waiting. One list covers both places a key can be unrecognized — the top level and an MCP server entry:
rules:
opencode-config-valid:
extra-keys:
- somethingNew # a new top-level key
- elicitation # a new key on an MCP server
Configuration¶
| Parameter | Description | Default |
|---|---|---|
extra-keys |
Additional config keys to accept, at the top level or on an MCP server entry, for keys newer than this skillsaw release | [] |
Run skillsaw explain opencode-config-valid to see this documentation and the rule's effective configuration in your terminal.