OpenCode MCP server not connecting, or its tools missing: the fixes
By Jake Marsh, who builds Taskhold. Updated 2026-10-31. Facts about other tools checked 2026-10-31. 6 min read.
Short answer: Run opencode mcp list to see each server as connected, failed, needs authentication or disabled. Servers go under mcp in opencode.json, each with type local or remote. Environment variables are written {env:NAME}, and an unset one becomes an empty string. Set oauth to false for servers that take an API key, and read ~/.local/share/opencode/log/opencode.log for details.
You added an MCP server to OpenCode and the model can't use it: the server shows failed, asks you to authenticate when it shouldn't, or connects with no tools. Most of these come down to OpenCode's config format, which differs from the mcpServers files other clients use, plus a few remote server behaviors that give misleading errors. This guide goes through how to read the status, where OpenCode looks for servers, and the fixes for each status.
The last section covers Taskhold's server, which feeds the live board where OpenCode's tasks and steps show up. Setup is on the OpenCode page, and Taskhold's error codes are in Taskhold troubleshooting.
Start with opencode mcp list
opencode mcp list (or opencode mcp ls) connects to every configured server and prints its status: connected, failed with the error underneath, needs authentication, needs client ID, or disabled. Inside the TUI, /status lists the same servers and /mcps opens a dialog that switches each one on or off.
For more detail, OpenCode appends logs to ~/.local/share/opencode/log/opencode.log on macOS and Linux (%USERPROFILE%\.local\share\opencode\log on Windows). Start it with --log-level DEBUG for verbose output, or add --print-logs to send the log to stderr. opencode debug paths shows the log folder if you use custom XDG paths.
OpenCode never reads the server
- The key is mcp, not mcpServers. A block copied from Claude Code, Cursor or Gemini CLI won't load until you rewrite it in OpenCode's format.
- Every entry needs type: "local" or "remote". A local server's command is a single array holding the program and its arguments, with no separate args field, and its variables go in environment, not env.
- OpenCode merges config files instead of picking one. The global file is ~/.config/opencode/opencode.json (or .jsonc), opencode.json in the project root overrides it, and OPENCODE_CONFIG points at an extra file loaded between the two. Organizations can also ship servers from a .well-known/opencode endpoint, sometimes disabled by default; add the same name locally with enabled: true to turn one on.
- enabled: false stops a server from starting and keeps the entry. opencode mcp list shows it as disabled.
- A tools rule can hide a server that connects fine. OpenCode prefixes each MCP tool with the server name, so "myserver_*": false under tools removes all of that server's tools, globally or for one agent.
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"my-remote": {
"type": "remote",
"url": "https://mcp.example.com/mcp",
"oauth": false,
"headers": { "Authorization": "Bearer {env:MY_SERVICE_TOKEN}" }
},
"my-local": {
"type": "local",
"command": ["npx", "-y", "some-mcp-server"],
"environment": { "API_KEY": "{env:API_KEY}" }
}
}
}Variables that come through empty
- OpenCode substitutes {env:NAME} and {file:path} anywhere in the config. ${NAME} and $NAME, the forms Claude Code and Gemini CLI use, are left as literal text and sent that way.
- An environment variable that isn't set becomes an empty string, with no warning. A header written "Bearer {env:MY_TOKEN}" then goes out as "Bearer ", and the server rejects it.
- The variable has to exist in the environment of the process that starts OpenCode. Check with echo $MY_TOKEN in the same shell, or keep the secret in a file and use {file:~/.secrets/my-token}, which OpenCode reads relative to the config file or from an absolute or ~ path.
Remote servers that fail with a misleading error
For a remote server, OpenCode tries Streamable HTTP first and then falls back to SSE on the same URL. When the first attempt is refused, the status shows the error from the fallback. On a Streamable HTTP server that only accepts POST, a wrong or empty key typically appears as "SSE error: Non-200 status code (405)" even though the real answer was a 401. Treat that message as a credential problem first: test the URL and header with curl.
- Use the server's final address. A redirect to another host drops the Authorization header, so the server can list its tools anonymously and still reject every call.
- The default timeout is 30 seconds for connecting and fetching tools. Raise it per server with timeout, in milliseconds.
- Each server's tools take up context. If the model stops picking a tool, check how many servers are on and turn off the ones you don't need.
Needs authentication, or OAuth you don't want
- OpenCode starts OAuth when a remote server answers 401, and the status changes to needs authentication with a hint to run opencode mcp auth <name>. That command opens the browser and stores tokens in ~/.local/share/opencode/mcp-auth.json. opencode mcp logout <name> removes them.
- For a server that uses an API key, set "oauth": false. Without it, a mistyped key also reads as needs authentication, which sends you looking for a sign-in that doesn't exist.
- Needs client ID means the server doesn't support dynamic client registration. Add oauth.clientId (and clientSecret if the provider gives you one).
- opencode mcp auth list shows the auth status of every OAuth-capable server, and opencode mcp debug <name> tests the HTTP connection and the OAuth discovery for one server.
Taskhold-specific checks
Taskhold is a remote Streamable HTTP server at https://taskhold.com/api/mcp. When the taskhold tools don't show up in OpenCode, check these in order:
- The entry sits under mcp with type remote and url set to exactly https://taskhold.com/api/mcp. The www host redirects and the redirect drops your header: opencode mcp list then says connected, but every call fails. A setup URL from Settings, pasted into OpenCode with "set this up", writes the entry for you.
- The header is Authorization: Bearer followed by a key that starts with atd_sk_. Taskhold's 401 names the cause: missing_key, bad_header, not_a_taskhold_key or unknown_key. A key read with {env:TASKHOLD_API_KEY} from an unset variable arrives as bad_header.
- Set "oauth": false. Sign-in without a key (OAuth) is set up for Claude Code, Cursor, Codex, claude.ai and ChatGPT; OpenCode sends the key in the header. With no header at all, the server lists as connected and its tools load, but every call comes back 401 missing_key.
- A 402 means the key works but the trial ended or the plan was canceled. Restart the plan and the same config works again.
- Settings in Taskhold shows Connected once the first call arrives. Ask OpenCode to call the taskhold list tool to check.
With the tools loaded and the AGENTS.md snippet from the OpenCode page, every task OpenCode starts, its steps and its questions show on the live board next to your other agents. The same checks for other clients: Claude Code, Gemini CLI and Antigravity CLI.
See how far along your coding agents are.
$12/month or $120/year after the trial. Card up front. Cancel before day 7 and you pay nothing. Every agent you run is included.
Questions
- Where does OpenCode read MCP servers from?
- From the mcp object in opencode.json: ~/.config/opencode/opencode.json for every project and opencode.json in the project root for one project. OpenCode merges the files, and the project file wins where they overlap.
- Why does OpenCode ignore ${MY_TOKEN} in my headers?
- OpenCode's variable syntax is {env:MY_TOKEN}. Other forms are sent as literal text. A variable that isn't set becomes an empty string.
- Why does OpenCode say SSE error 405 for a Streamable HTTP server?
- OpenCode falls back to SSE when the Streamable HTTP attempt fails and shows the fallback's error. The first attempt usually failed on credentials, so check the key and set oauth to false for API-key servers.
Sources
- OpenCode docs: MCP servers (local, remote, OAuth, mcp auth/debug, tool globs), checked 2026-10-31
- OpenCode docs: config (file locations, precedence, {env:} and {file:} variables), checked 2026-10-31
- OpenCode docs: CLI (opencode mcp, --log-level, --print-logs), checked 2026-10-31
- OpenCode docs: troubleshooting (log location), checked 2026-10-31
- OpenCode source v1.19.5: MCP client (Streamable HTTP, then SSE), checked 2026-10-31
Related
Updated 2026-10-31. Something wrong or out of date? Email hello@greatwork.company.