
xuanpass/opencode-http-mcp
Developer toolsZero-dependency, pure-JS stdio MCP bridge for OpenCode 2.x/3.x servers (/api/... routing, no SDK, no build step)
Overview
A stdio MCP server that bridges MCP clients (WorkBuddy, Claude Desktop, Cursor, any JSON-RPC stdio client) to an OpenCode 2.x / 3.x server. It talks to OpenCode's official /api/... HTTP endpoints and hand-rolls the MCP stdio (JSON-RPC) protocol on top of node:readline. No @modelcontextprotocol/sdk, no zod, no build step — just Node standard library. The community opencode-mcp 3.x package is TypeScript + @opencode-ai/sdk + zod and must be built before use. Its OpenCodeClient routes to whatever the installed SDK knows about — and the SDK currently only ships 1.x-style paths, so it does not match a running OpenCode server that moved every business endpoint behind an /api/... prefix. The result: 405, the Web UI HTML fallback, or .map is not a function crashes. opencode-http-mcp speaks the current /api/... contract from the start, with to opencode-mcp 3.x — every one of the upstream's 87 unique tool names (92 locally, incl.
README
opencode-http-mcp
A zero-dependency, pure-JavaScript stdio MCP server that bridges MCP clients (WorkBuddy, Claude Desktop, Cursor, any JSON-RPC stdio client) to an OpenCode 2.x / 3.x server.
It talks directly to OpenCode’s official /api/... HTTP endpoints and
hand-rolls the MCP stdio (JSON-RPC) protocol on top of node:readline.
No @modelcontextprotocol/sdk, no zod, no build step — just Node standard
library.
Why this exists
The community opencode-mcp 3.x package is TypeScript + @opencode-ai/sdk
- zod and must be built before use. Its
OpenCodeClientroutes to whatever the installed SDK knows about — and the SDK currently only ships 1.x-style paths, so it does not match a running OpenCode 2.x/3.x server that moved every business endpoint behind an/api/...prefix. The result:405, the Web UI HTML fallback, or.map is not a functioncrashes.
opencode-http-mcp speaks the current /api/... contract from the start,
with full tooling parity to opencode-mcp 3.x — every one of the
upstream’s 87 unique tool names (92 locally, incl. 5 extras), grouped into
layered modules — and honest no-route fallbacks for the small set of
1.x-only endpoints that a 2.x build does not expose (instead of faking data).
Architecture (layered, mirrors upstream)
Layer 0 src/http.js OpenCodeClient — HTTP + SSE + { data } unwrap
Layer 0.5 src/helpers.js formatting / result envelopes / redaction
Layer 0.5 src/registry.js tool registry
Layer 0.5 src/util.js arg helpers (dir / req / modelOpts)
Layer 1 src/jobs.js JobService — session-turn dispatch & observation
Layer 2 src/tools/*.js grouped tool modules (one per upstream file)
Entry src/index.js stdio JSON-RPC MCP entry point
Upstream parity map (opencode-mcp 3.x → opencode-http-mcp)
Tool-surface parity — 100% of upstream names, plus 5 extras
| upstream file | module | upstream tools | local tools | delta |
|---|---|---|---|---|
| session.ts | session.js | 20 | 20 | — |
| message.ts | message.js | 6 | 6 | — |
| file.ts | file.js | 6 | 6 | — |
| config.ts | config.js | 3 | 3 | — |
| provider.ts | provider.js | 6 | 6 | — |
| project.ts | project.js | 3 | 3 | — |
| misc.ts | misc.js | 12 | 16 | +integration/pty/reference/skill_list (2.x routes upstream doesn’t wrap) |
| global.ts | global.js | 1 | 1 | — |
| events.ts | events.js | 1 | 1 | — |
| input.ts | input.js | 6 | 4 | job tools moved to workflow.js |
| tui.ts | tui.js | 9 | 9 | — |
| workflow.ts | workflow.js | 13 | 17 | +job_list/get/cancel/update (JobService tools live here) |
| total | 86 registrations → 87 unique names | 92 unique names | 0 missing, 5 extra |
- 0 missing: every
opencode_*tool name upstream registers exists locally, verified by name-level diff (comm), no duplicate registrations. - 5 extra:
opencode_integration_list,opencode_pty_list,opencode_reference_list,opencode_skill_list(wrap 2.x routes upstream ignores), plusopencode_job_updateas an alias of upstream’sopencode_job_input(kept for existing callers). opencode_job_input≡opencode_job_update— same implementation, two names.
opencode_run dual entry (upstream design, simplified here)
Upstream has two entry points to the same JobService.start():
- Legacy path (always available): the MCP client calls the
opencode_runtool →tools/workflow.ts→jobs.start()+ observe. Works on any MCP protocol version. - Modern path (
task-transport.tsinterceptor): if the connection is a 2026-07-28+ “modern” session and the client advertised the Tasks extension capability, the interceptor hijacks theopencode_runtool call before it reaches the tool layer and converts it into atasks/*JSON-RPC extension request (tasks/get/tasks/update/tasks/cancelbecome the observation channel instead of repeatedtools/calls). Otherwise (-32021/-32022 errors) it falls through to the legacy path.
opencode-http-mcp implements only the legacy path. The interceptor is a
transport-level concern that requires the Tasks extension handshake, which
this bridge does not advertise (capabilities: { tools: {} }). Behavior is
identical for every non-Tasks client — which is all stdio JSON-RPC clients
today, including WorkBuddy.
Short-task tools vs long-task tools — sibling layers
OpenCodeClient (http.js) ← Layer 0: single HTTP round-trip
▲ ▲
│ │
short-task tools JobService (jobs.js) ← Layer 1: multi-call state machine
(ask/reply/file/*) start() → get() → update()/cancel() + persistence
│ │
└── BOTH dispatch one-shot tools through JobService when the route is
fire-and-forget (2.x /prompt returns immediately), then observe via
waitForSnapshot() until terminal — so the caller gets the AI reply,
not an echo of its own prompt.
- Short tools (
opencode_file_read,opencode_provider_list, …): one request → format → return. They callOpenCodeClientdirectly. - Long tools (
opencode_run/fire/check/wait/job_*):jobs.start()submits once and persists a durablejobId; observation is a separate read (jobs.getper poll — no self-polling loop inside the job record);waitForSnapshot()inworkflow.jsowns the wait loop. - Neither layer calls the other: short tools and JobService are siblings
under
OpenCodeClient. The one crossing point is the one-shot tools (ask/reply/provider_test/message_send), which route throughjobs.start()+waitForSnapshot()purely to observe fire-and-forget prompt submissions.
Feature parity beyond tools
| capability | upstream | opencode-http-mcp |
|---|---|---|
| Tools | 87 unique names | 92 (100% of upstream + 5) |
| Persistent JobService (store/lock/recover/TTL) | ✅ jobs.ts | ✅ jobs.js (ported, live-tested) |
| MCP prompts (6 templates) | ✅ prompts.ts | ✅ implemented |
MCP resources (11 opencode:// URIs) |
✅ resources.ts | ✅ implemented |
Tasks extension (task-transport, tasks/*) |
✅ | ✅ implemented (handlers for tasks/get/update/cancel) |
Server auto-spawn (opencode serve) |
✅ server-manager.ts | ✅ implemented (enabled via OPENCODE_AUTO_SERVE=true) |
| MCP form elicitation for pending inputs | ✅ task-input.ts | ✅ text fallback (relies on client-side input via job_input) |
| Test suite | ✅ vitest | ✅ test.js + live smoke (extended unit tests) |
| CacheHints | ✅ (cacheHints in initialize) | ✅ implemented (tools/list, prompts/list, resources/list, resources/templates/list, resources/read) |
| Build / dependencies | TS + sdk + zod, build step | zero-dep, no build |
Features
- Zero dependencies — only
node:http,node:https,node:urlfrom stdlib. - No build step — point your MCP client at
src/index.jsand it runs. - Correct
/api/...routing for OpenCode 2.x/3.x. - Full tool parity — all 87 upstream
opencode_*tool names (92 local). - Honest no-route fallbacks — for 1.x-only endpoints a 2.x build lacks, tools return a clear “unavailable on this build” message instead of 404s.
Requirements
- Node.js >= 18
- A running OpenCode server (
opencode serve) reachable over HTTP, e.g.http://192.168.123.101:49374
Install
git clone opencode-http-mcp
cd opencode-http-mcp
# No `npm install` needed — there are no dependencies.
Configuration (environment variables)
| Variable | Default | Description |
|---|---|---|
OPENCODE_BASE_URL |
http://127.0.0.1:49374 |
Base URL of the OpenCode server |
OPENCODE_SERVER_USERNAME |
opencode |
Basic-auth username |
OPENCODE_SERVER_PASSWORD |
(empty) | Basic-auth password |
OPENCODE_TIMEOUT_MS |
120000 |
Per-request HTTP timeout (ms) |
OPENCODE_MCP_STORE_ROOT |
platform state dir | Job store root; "" = in-memory only |
OPENCODE_MCP_SCOPE |
default |
Store partition (sha256 of baseUrl+scope) |
OPENCODE_MCP_TTL_MS |
86400000 |
Durable job TTL (24h) |
OPENCODE_AUTO_SERVE |
false |
Reserved (currently not used) |
Security note: never commit a real
OPENCODE_SERVER_PASSWORD. Set it in your MCP client’senvblock or in your shell environment instead.
MCP client configuration
Example for a client that reads mcpServers:
{
"mcpServers": {
"opencode": {
"command": "node",
"args": ["/absolute/path/to/opencode-http-mcp/src/index.js"],
"env": {
"OPENCODE_BASE_URL": "http://192.168.123.101:49374",
"OPENCODE_SERVER_USERNAME": "opencode",
"OPENCODE_SERVER_PASSWORD": "YOUR_PASSWORD_HERE"
}
}
}
}
Routes pinned (live-verified on OpenCode 2.0.30)
Live 200 endpoints used by real tools:
/api/session, /api/session/{id}, /api/session/{id}/message,
/api/session/{id}/prompt, /api/session/{id}/diff, /api/session/{id}/fork,
/api/agent, /api/command, /api/config, /api/vcs, /api/vcs/status,
/api/provider, /api/model, /api/project, /api/skill, /api/mcp,
/api/integration, /api/reference, /api/pty, /api/event,
/global/health, /global/event.
Honest no-route fallbacks (1.x-only, 404 on 2.0.30):
/api/fs/find → opencode_find_text|file|symbol;
/api/question, /api/permission → opencode_question_*, opencode_job_input;
/api/provider/auth, /api/provider/{id}/oauth/*, /api/auth/{id};
/api/tui/* → opencode_tui_*;
/api/path, /api/lsp, /api/formatter, /api/log,
/api/instance/dispose, /api/project/current,
/api/session/{id}/abort|todo|status|share|summarize|revert|unrevert|children.
Run the smoke test
OPENCODE_BASE_URL=http://192.168.123.101:49374 \
OPENCODE_SERVER_USERNAME=opencode \
OPENCODE_SERVER_PASSWORD=*** \
node test.js
The test exercises tools/list plus a handful of read-only and no-route
tools against a live server.
License
MIT — see LICENSE.
Install
This server does not publish a one-line install command.
Open the repository installation guideConfiguration
{
"mcpServers": {
"opencode": {
"command": "node",
"args": ["/absolute/path/to/opencode-http-mcp/src/index.js"],
"env": {
"OPENCODE_BASE_URL": "http://192.168.123.101:49374",
"OPENCODE_SERVER_USERNAME": "opencode",
"OPENCODE_SERVER_PASSWORD": "YOUR_PASSWORD_HERE"
}
}
}
}