Skip to main content
Glama
README.md
# opencode-agent-mcp

[English](./README.md) · **[简体中文](./README.zh-CN.md)**

[![npm version](https://img.shields.io/npm/v/opencode-agent-mcp)](https://www.npmjs.com/package/opencode-agent-mcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Node](https://img.shields.io/badge/node-%3E%3D18.18.0-brightgreen)]()
[![MCP](https://img.shields.io/badge/MCP-v1.30-blue)](https://modelcontextprotocol.io)
[![opencode SDK](https://img.shields.io/badge/opencode--sdk-1.18.16-blue)](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.