Repository navigation
Description
MCP server tools are missing from sessions created via POST /api/session on opencode serve (present in the TUI)
Summary
On opencode serve (headless), a configured local (stdio) MCP server connects fine (GET /mcp shows status: "connected"), but sessions created and prompted through the HTTP API (POST /api/session + POST /api/session/{id}/prompt) receive none of that server's tools — the model reports only the built-ins and cannot call any MCP tool.
The exact same config, run as the interactive TUI, works: the model calls the MCP tool successfully. So the MCP server, its config, and its tools are all fine — the tools just don't reach sessions driven over the HTTP API.
This makes opencode serve unusable for programmatic/agent use cases that depend on MCP tools (drive opencode as a headless HTTP service).
Environment
- opencode
1.18.3(also reproduced reading1.18.4source) - macOS (arm64); model: Anthropic
claude-sonnet-5
Minimal reproduction
1. A trivial stdio MCP server — mcp-ping.js (one tool, ping_test, returns pong):
let buf = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (d) => {
buf += d; let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i); buf = buf.slice(i + 1);
if (!line.trim()) continue;
let m; try { m = JSON.parse(line); } catch { continue; } handle(m);
}
});
const send = (o) => process.stdout.write(JSON.stringify(o) + '\n');
function handle(m) {
if (m.method === 'initialize')
send({ jsonrpc: '2.0', id: m.id, result: { protocolVersion: (m.params && m.params.protocolVersion) || '2025-06-18', capabilities: { tools: { listChanged: false } }, serverInfo: { name: 'ping-test', version: '1.0.0' } } });
else if (m.method === 'tools/list')
send({ jsonrpc: '2.0', id: m.id, result: { tools: [{ name: 'ping_test', description: 'Diagnostic ping tool', inputSchema: { type: 'object', properties: {}, additionalProperties: false } }] } });
else if (m.method === 'tools/call')
send({ jsonrpc: '2.0', id: m.id, result: { content: [{ type: 'text', text: 'pong' }] } });
else if (m.id !== undefined && m.id !== null)
send({ jsonrpc: '2.0', id: m.id, result: {} });
}2. opencode.json in the same dir (add your Anthropic key):
{
"$schema": "https://opencode.ai/config.json",
"model": "anthropic/claude-sonnet-5",
"provider": { "anthropic": { "options": { "apiKey": "sk-ant-..." } } },
"mcp": { "pingtest": { "type": "local", "command": ["node", "./mcp-ping.js"], "enabled": true } }
}3a. Works — TUI:
opencode
> call the ping_test tool and show me exactly what it returns
Result: the model calls pingtest_ping_test and reports pong. ✅
3b. Fails — headless serve via the HTTP API:
OPENCODE_SERVER_PASSWORD=pw opencode serve --hostname 127.0.0.1 --port 39901 &
curl -s -u opencode:pw http://127.0.0.1:39901/mcp
# -> {"pingtest":{"status":"connected"}}
SID=$(curl -s -u opencode:pw -X POST http://127.0.0.1:39901/api/session \
-H 'content-type: application/json' -d '{}' | jq -r .data.id)
curl -s -u opencode:pw -X POST http://127.0.0.1:39901/api/session/$SID/prompt \
-H 'content-type: application/json' \
-d '{"prompt":{"text":"Call ping_test with no arguments and report its output verbatim."},"model":{"providerID":"anthropic","id":"claude-sonnet-5"},"delivery":"queue"}'
# wait, then read the reply:
curl -s -u opencode:pw http://127.0.0.1:39901/api/session/$SID/messageResult: the assistant replies "I don't have access to a tool called ping_test. The tools available to me are: apply_patch, bash, edit, glob, grep, question, skill, todowrite, webfetch, websearch, write." ❌
Also tried: passing x-opencode-directory: <project dir> on both the create and prompt requests, and ?directory=<project dir> — no change (the serve cwd already is the project dir, so it resolves to the same directory anyway).
Expected
A session created via POST /api/session on a server whose MCP server is connected should expose that server's tools to the model — same as the TUI.
Suspected cause (from reading 1.18.4 source)
serveisinstance: falseand resolves a per-request instance (server/routes/instance/httpapi/middleware/instance-context.ts→InstanceStore.load({ directory })), with per-request instance disposal (.../lifecycle.ts). The TUI holds one long-lived instance.- Tools reach the model via
SessionTools.resolveiteratingMCP.tools()(session/tools.ts), which yields tools only for servers that areconnectedand have populateddefs. mcp/index.tscreate():const listed = getServerCapabilities()?.tools ? McpCatalog.defs(client, timeout) : []; if (!listed) fail. Because an empty array is truthy, a server that ends up with an emptytools/listis cached asconnectedwithdefs: [](no failure) — andMCP.tools()then contributes zero tools. Net observable state:GET /mcpsaysconnected, model gets nothing.
So it looks like the per-request instance used for an API session ends up with the MCP server connected but with empty/unpopulated defs, whereas the persistent TUI instance has them fully loaded.
(Note: GET /experimental/tool/ids returns built-ins/plugins only by design and never lists MCP tools, so it can't be used to diagnose this — use an actual model turn.)
Impact
opencode serve can't be used as a headless MCP-enabled agent runtime — any integration that adds tools via MCP is silently unavailable to API-driven sessions, with no error surfaced.
Activity
github-actions commentedon Jul 23, 2026
This issue might be a duplicate of or closely related to existing issues. Please check:
If your issue is specifically about the HTTP API /
opencode servepath rather than subagent sessions, it may be a distinct (but related) code path and worth keeping open separately.brad07 commentedon Jul 23, 2026
Additional detail from a
--log-level DEBUGrun (ruling out the common 'add enabled:true / OAuth' advice):"enabled": true; the server logs no config error, no OAuth URL, and no process-startup failure. The MCP server connects (GET /mcp->connected).creating instancefor the project directory, cleanly bootstrapping (loading config -> watcher -> booting location services), with MCP initialized lazily afterward - but the resulting turn still exposes zero MCP tools to the model.evaluated permission=pingtest_ping_test ... action=allowline (the model actually invoking the tool). The API run logs no tool at all.So this isn't a config/enabled/OAuth issue - it's specifically that the per-request instance used for an API session doesn't surface the connected MCP server's tools, whereas the persistent TUI instance does.
brad07 commentedon Jul 23, 2026
Update:
opencode rundoes NOT have this problem - it exposes the MCP tools correctly.opencode run --attach http://127.0.0.1:PORT --dir <project> "call the mcp tool"drives a turn against the SAME serve that fails via rawPOST /api/session, and there the model DOES get and call the MCP tool. So the gap is specifically the server's/api/sessioncreate path not loading MCP the wayrun(and the TUI) do - likelyrunperforms additional instance setup that the bare session-create over HTTP skips.herjarsa commentedon Aug 11, 2026
PR with fix: #41728
Root cause: the serve command had \instance: false, so the lazy Plugin service (InstanceState) is never materialised in HTTP serve mode — \Plugin.init()\ is only reached via the per-process InstanceBootstrap that \instance: true\ enables. The same plugin bundle works in TUI and \opencode run\ (logs \actory_invoked), and shows only the module-scope import under \opencode serve.
The PR flips serve to \instance: true\ with \directory: () => process.cwd(); per-request \x-opencode-directory\ entries are unaffected (different cache keys). Reproducer + source evidence in PROPOSAL-server-plugin-init.md.
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊