opencode-agent-mcp
by define9
README.md
# opencode-agent-mcp
[English](./README.md) · **[简体中文](./README.zh-CN.md)**
[](https://www.npmjs.com/package/opencode-agent-mcp)
[](./LICENSE)
[]()
[](https://modelcontextprotocol.io)
[](https://www.npmjs.com/package/@opencode-ai/sdk)
MCP server that exposes independent [OpenCode](https://github.com/sst/opencode) AI coding agents to upstream Agents (Claude Desktop, Cursor, opencode TUI, custom) over stdio JSON-RPC, with **multi-workspace concurrent routing**.
> Full product specification: [`opencode-agent-mcp.md`](./opencode-agent-mcp.md)
---
## Overview
In polyglot or monorepo-style codebases, a single fault often spans multiple projects (e.g. a frontend interaction that depends on a backend API and a native mobile shell). The conventional "spawn a subagent to look at the other repo" pattern fails because the subagent inherits the parent agent's workspace, MCP servers, and skills — it does not load the target project's own conventions.
`opencode-agent-mcp` addresses this by exposing OpenCode as a multi-workspace MCP server:
- Upstream agents register any number of workspaces via `set_workspace({name, cwd})`.
- Each workspace maps to a dedicated `OpencodeClient` instance, scoped by the `x-opencode-directory` header so the OpenCode host routes requests to the correct project.
- Sessions are created, messaged, cancelled, and queried per workspace, with full isolation between workspaces and concurrency within a workspace.
- When the inner agent requires approval or clarification, those events are surfaced back to the upstream agent via blocking MCP tools (`wait_for_question` / `answer_question`) for it to decide.
This server is an **interim MCP implementation** of a more general multi-workspace A2A pattern. The current schema is designed to be migratable: once A2A stabilises, the same conceptual primitives map onto A2A's `task` / `message` / `artifact` triples, and the MCP server can be replaced by an A2A server without breaking the upstream contract.
| Dimension | Current MCP implementation | Target A2A model |
|---|---|---|
| Topology | Parent → child (client → server) | Peer-to-peer |
| Question flow | Blocking MCP tool emulation | Native request/response |
| Protocol surface | JSON-RPC + SSE + blocking tool | Unified A2A message/artifact/task |
| Lifecycle | Co-hosted with upstream agent | Independent per agent |
---
## Quick Start
The package is published on npm and resolvable via `npx`. No global install is required.
```bash
# Verify the package is reachable
npm view opencode-agent-mcp version
# Launch the MCP server directly (foreground; used by Claude Desktop / opencode TUI)
npx -y opencode-agent-mcp
```
To make the server reachable from Claude Desktop, Cursor, or the opencode TUI, point the MCP client at `npx -y opencode-agent-mcp` with the appropriate environment variables. See [Integration](#integration) for concrete `mcpServers` / `mcp` config snippets.
### Pre-flight
`npx -y opencode-agent-mcp` does not start an OpenCode host on its own — it expects an existing `opencode serve` (external host mode) or spawns one itself (managed mode). Confirm the desired mode by inspecting the environment variables in [Configuration](#configuration) before launching the client.
---
## Installation
Two installation paths are supported; `npx` is preferred unless the source needs to be modified locally.
### Option A — `npx` (recommended for end users)
```bash
# Implicit install via npx on first invocation
npx -y opencode-agent-mcp
```
The package is fetched to the npx cache on first use and re-used on subsequent invocations. To pin a specific version:
```bash
npx -y opencode-agent-mcp@0.1.0
```
### Option B — From source (for development or local modification)
```bash
git clone <repository-url>
cd opencode-agent-mcp
pnpm install
pnpm run build
node build/index.js
```
Requirements: Node.js ≥ 18.18.0, pnpm ≥ 8 (or npm ≥ 9 with `npm install && npm run build`).
---
## Configuration
The server reads the following environment variables. All paths must be absolute on the host.
| Variable | Default | Description |
|---|---|---|
| `OPENCODE_URL` | _(unset)_ | When set, the server runs in **external host mode** and connects to the given `opencode serve` URL. When unset, **managed mode** is used and the server spawns `opencode serve` itself. |
| `OPENCODE_BIN` | `opencode` | Path to the `opencode` executable (managed mode only). |
| `OPENCODE_PORT` | `4096` | Port for the managed host (managed mode only). |
| `OPENCODE_HOSTNAME` | `127.0.0.1` | Bind address for the managed host (managed mode only). |
| `OPENCODE_DEFAULT_CWD` | process CWD | Initial workspace registered as `default`. |
| `MCP_LOG_LEVEL` | `info` | One of `debug` / `info` / `warn` / `error`. |
| `MCP_LOG_FILE` | _(stderr)_ | Optional file path for log output; default is stderr. |
---
## Running
The MCP server speaks stdio JSON-RPC. It is normally launched by an MCP client (Claude Desktop, opencode TUI, etc.) rather than by hand. The sections below assume the client has been configured with one of the snippets in [Integration](#integration).
For manual smoke-testing:
```bash
# External host mode — OpenCode host must be running on :4096
OPENCODE_URL=http://127.0.0.1:4096 \
OPENCODE_DEFAULT_CWD="C:/path/to/aggregated-project" \
npx -y opencode-agent-mcp
# Managed mode — server spawns `opencode serve` itself
OPENCODE_DEFAULT_CWD="C:/path/to/project" \
npx -y opencode-agent-mcp
```
In managed mode the server spawns `opencode serve` on startup and terminates it on shutdown. The host process is not reused across MCP restarts.
---
## Integration
The configuration below assumes the package is installed via `npx` (no global install required). For source installs, replace `npx` with `node` and point `args` at `build/index.js`.
### Claude Desktop
Edit `claude_desktop_config.json` (Windows: `%APPDATA%\Claude\claude_desktop_config.json`):
```json
{
"mcpServers": {
"opencode-agent": {
"command": "npx",
"args": ["-y", "opencode-agent-mcp"],
"env": {
"OPENCODE_URL": "http://127.0.0.1:4096",
"OPENCODE_DEFAULT_CWD": "C:/path/to/aggregated-project"
}
}
}
}
```
### opencode TUI
Edit `~/.config/opencode/opencode.json`:
```json
{
"mcp": {
"opencode-agent": {
"type": "local",
"command": ["npx", "-y", "opencode-agent-mcp"],
"environment": {
"OPENCODE_URL": "http://127.0.0.1:4096"
}
}
}
}
```
### Custom agent (Node.js SDK)
```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const client = new Client(
{ name: "my-agent", version: "0.1.0" },
{ capabilities: {} },
);
await client.connect(new StdioClientTransport({
command: "npx",
args: ["-y", "opencode-agent-mcp"],
env: { OPENCODE_URL: "http://127.0.0.1:4096" },
}));
const { tools } = await client.listTools();
```
---
## Multi-workspace Routing
### Lifecycle
```
1. set_workspace({name: "backend", cwd: "C:/repo/backend"})
2. set_workspace({name: "h5", cwd: "C:/repo/h5"})
3. set_workspace({name: "app", cwd: "C:/repo/app", default: false})
4. create_session({workspace: "backend", title: "API regression"})
5. create_session({workspace: "h5", title: "Button handler"})
6. create_session({workspace: "app", title: "Native bridge"})
7. send_message({session_id: <h5_session>, text: "..."})
→ the server resolves the session back to its workspace ("h5")
and dispatches the request via the h5-scoped OpencodeClient
```
### Semantics
- The `workspace` parameter accepts a **workspace name** registered via `set_workspace`, not a raw `cwd`. When omitted, the default workspace is used.
- A `(name, cwd)` pair already registered is reused (idempotent). Registering the same `name` with a different `cwd` raises `WORKSPACE_NOT_REGISTERED`.
- One `OpencodeClient` is instantiated per registered workspace, distinguished by the `x-opencode-directory` HTTP header.
- `SessionStore` records `session_id → workspace_name`. Subsequent operations on the session (`send_message`, `cancel_session`, `answer_question`) are routed by that mapping without requiring the caller to re-supply `workspace`.
### Topology
```
Upstream Agent
│
│ create_session({workspace: "h5"})
▼
MCP server (stdio)
│
│ workspaces.get("h5") → OpencodeClient (x-opencode-directory = /repo/h5)
▼
OpenCode host (127.0.0.1:4096)
│
├─ /session?directory=/C:/repo/backend
├─ /session?directory=/C:/repo/h5
└─ /session?directory=/C:/repo/app
```
---
## MCP Tools
| Tool | Description |
|---|---|
| `list_workspaces` | List all registered workspaces and the current default. |
| `set_workspace` | Register a new workspace (`name` + `cwd`) or switch to an existing one by name. |
| `list_sessions` | List OpenCode sessions under a workspace. |
| `create_session` | Create a new session in the specified workspace and start its SSE subscription. |
| `send_message` | Send a message synchronously; returns the assistant text, token usage, and completion status. |
| `wait_for_message` | Block until the session reaches `idle` or an error is observed. |
| `cancel_session` | Abort the session and stop its SSE subscription. |
| `wait_for_question` | Block until the OpenCode host emits a `permission.asked` or `question.asked` event. |
| `answer_question` | Forward the upstream agent's decision (`once` / `always` / `reject` / `{answers}`) back to the OpenCode host. |
Tool schemas are generated from the source code and reported by the server at runtime via the MCP `tools/list` endpoint.
---
## Testing
Two integration scripts are provided; both assume an OpenCode host reachable at `127.0.0.1:4096`.
```bash
opencode serve --hostname=127.0.0.1 --port=4096 &
node e2e-test.mjs # single-workspace end-to-end
node multi-workspace-test.mjs # concurrent multi-workspace routing
```
Both scripts use the official `@modelcontextprotocol/sdk` client and assert on returned content. The test sessions are created against scratch directories under the platform temp path; no real project state is touched.
## Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
| `HOST_UNREACHABLE` | `opencode serve` not reachable | Start `opencode serve --port=4096` or unset `OPENCODE_URL` to use managed mode. |
| `CWD_NOT_EXISTS` | Workspace directory missing on disk | Create the directory or correct the `cwd` path. |
| `WORKSPACE_NOT_REGISTERED` | Unknown workspace name supplied | Register the workspace via `set_workspace({name, cwd})` first. |
| `SESSION_NOT_FOUND` | `session_id` not present in the current MCP process | Create the session and send within the same MCP lifetime. |
| `SDK returned no session id` | OpenCode host version is too old | Upgrade OpenCode to ≥ 1.18.x. |
| Garbled protocol output on stdout | logger writing to stdout | Ensure `MCP_LOG_FILE` is unset or set to a file path; the logger must not write to stdout. |
---
## License
[MIT](./LICENSE)
## Contributing
Issues and pull requests are welcome. Please open an issue before submitting non-trivial changes so the design intent can be discussed.This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues