anomalyco / anomalyco/opencode
MCP tools missing from sessions created via POST /api/session on `opencode serve` (present in the TUI)
@kitlangton is already working on this.
Since Jul 23, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
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/message
Result: 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.