Content
# subagent-gateway
An MCP server (stdio transport) that delegates subtasks from [Kimi Code](https://www.kimi.com/code) — or any MCP-compatible agent — to a locally running [opencode](https://opencode.ai) CLI. It gives your main agent a "coworker powered by a different vendor's model": opencode executes the task with whatever model it has configured (DeepSeek, GLM, …), while the gateway handles session continuity, permissions, timeouts and result retrieval.
[中文版 README](README.zh-CN.md) · [AI 安装指南(给 Agent 看的)](给Kimi的安装指南.md)
## Install with your AI agent (one sentence)
Don't want to install manually? Paste this to your AI coding agent (Kimi Code, Claude Code, etc.) and it will do everything for you:
```
Read https://github.com/SiliconLight/subagent-gateway/blob/main/给Kimi的安装指南.md and follow it to install the subagent-gateway MCP server on this machine: clone the repo to a permanent location, install dependencies, run the smoke test, register it in my MCP client config (merge into existing entries, never overwrite the whole file), then tell me to restart the client.
```
Only prerequisites: Node.js ≥ 18 and the opencode CLI with a configured model provider — the agent will verify these for you. Manual installation is documented [below](#install).
## Using it day to day
Once installed, just talk to your main agent naturally:
- **Delegate work** — "hand this to the sub-agent: …". Note each call starts cold unless you keep using the same task thread (`agent_id`), so make the task self-contained.
- **List available models** — "what models can the sub-agent use?" (runs `list_models`).
- **Change the model** — say "change the sub-agent model": the agent lists opencode's configured models for you to pick, then asks for a reasoning effort — pick **Auto** to let the agent decide per task. (Requires the AGENTS.md snippet [below](#3-project-agentsmd--delegation-policy-optional-helps-the-main-agent-use-the-tools-correctly).)
- **Iterate in one conversation** — keep critiquing the result; the same conversation keeps full context across rounds (session continuation).
- **Permission gate** — outside YOLO mode, when the sub-agent tries to edit files or run shell commands you get back an error describing the blocked action; approve it and the agent retries with `auto_approve: true`, continuing in the same session.
- **Reset a sub-agent** — delete its entry in `<projectDir>/.kimi-code/external-agents.json` to start a fresh session next time.
## Features
- **Session continuation** — pass the same `agent_id` to reuse the same opencode session with full history; ideal for iterative multi-round tasks.
- **Per-call model & reasoning effort** — `model` is forwarded to `opencode -m`, `variant` to `--variant` (e.g. `deepseek/deepseek-v4-pro` supports `high`/`max`). Both can change between rounds of the same session.
- **Permission bridging** — `auto_approve: true` runs opencode with `--auto` (YOLO-style); by default, permission `ask`s are auto-rejected by headless opencode, the gateway detects the resulting stall (~60 s), aborts the session, and returns the rejected tool call so the main agent can ask its user and retry.
- **Robustness** — auto-starts `opencode serve` with an identity check (not just "port open"), fail-fast polling when the server dies or a session 404s, timeout that actually aborts the serve-side session (no orphaned token burn), per-`agent_id` mutex, process-tree kill on Windows, UTF-8-safe argument passing (no cmd.exe codepage mangling).
## Requirements
- Node.js ≥ 18
- [opencode](https://opencode.ai) CLI installed and in PATH, with at least one model provider configured (`opencode models` lists models). Developed and tested against opencode 1.18.x on Windows; other platforms should work via the PATH fallback (see `OPENCODE_EXE` below).
- An MCP client. Kimi Code is documented below, but any client that speaks MCP over stdio works.
## Install
```bash
git clone <your-fork-url>
cd subagent-gateway
npm install
```
## Smoke test
1. Protocol handshake + tool list (no opencode needed):
```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node server.js
```
You should see the `initialize` result and two tool definitions (`delegate_task`, `list_models`).
2. End-to-end delegation (really calls the configured model; auto-starts `opencode serve` on first run):
```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"delegate_task","arguments":{"task":"Reply with exactly the two characters OK.","model":"deepseek/deepseek-v4-flash","agent_id":"smoke-test"}}}' \
| node server.js
```
After success, `<projectDir>/.kimi-code/external-agents.json` should contain a `smoke-test` → `ses_…` session mapping.
## Client configuration (Kimi Code)
Add the following snippets manually — this repo never touches your config files.
### 1. `~/.kimi-code/mcp.json`
```json
{
"mcpServers": {
"subagent-gateway": {
"command": "node",
"args": ["<absolute-path-to>/subagent-gateway/server.js"],
"startupTimeoutMs": 60000,
"toolTimeoutMs": 600000
}
}
}
```
The server's project directory defaults to its process working directory (i.e. where Kimi launched it). To pin a project directory, add `"env": { "SUBAGENT_PROJECT_DIR": "/path/to/project" }`.
### 2. `~/.kimi-code/config.toml` — permission rule (skip per-call approvals)
```toml
[[permission.rules]]
decision = "allow"
pattern = "mcp__subagent-gateway__*"
```
> Heads-up: this rule makes the main client auto-approve every delegation. What the sub-agent may then do is governed by *opencode's* permission config, not the client's — see [Permission model](#permission-model).
### 3. Project `AGENTS.md` — delegation policy (optional, helps the main agent use the tools correctly)
```markdown
## Sub-task delegation policy
- Prefer the MCP tool `mcp__subagent-gateway__delegate_task` (opencode + external model)
over the built-in Agent tool when delegating subtasks.
- Call `mcp__subagent-gateway__list_models` first to confirm available models.
- Use a fast model (e.g. deepseek/deepseek-v4-flash) for simple tasks and a stronger one
(e.g. deepseek/deepseek-v4-pro) for complex tasks; set `variant` for reasoning effort.
- In YOLO / fully autonomous mode pass `auto_approve: true`; otherwise omit it. If a call
fails with a permission-rejected error, ask the user first, then retry with
`auto_approve: true` (the same `agent_id` session is preserved).
- When the user says "set/change the sub-agent model" or similar: run
`mcp__subagent-gateway__list_models` and let the user pick a model, then let them pick
a reasoning effort — always offer "Auto" first (you decide per task: omit `variant` for
simple tasks, use a supported variant for complex ones), followed by the variants the
chosen model actually supports (skip this step if none). Remember the choice for all
subsequent `delegate_task` calls until the user changes it again.
- Do not call the same `agent_id` in parallel (the server serializes, but sequential
semantics are clearer); use one `agent_id` for context-preserving multi-round work.
```
Restart the client, run `/mcp` — `subagent-gateway` should show as connected. Then ask the agent to call `mcp__subagent-gateway__list_models` to confirm the link.
## Tools
### delegate_task
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `task` | string | yes | Self-contained task description |
| `model` | string | no | Forwarded to `opencode -m`; default `deepseek/deepseek-v4-pro` |
| `agent_id` | string | no | Sub-agent identifier; passing it enables session continuation |
| `auto_approve` | boolean | no | `true`: run opencode with `--auto` (auto-approves everything not explicitly denied — YOLO scenarios). Default `false`: opencode auto-rejects `ask`s; the gateway detects the stall (~60 s), aborts the session, and returns the rejected tool call as an error |
| `variant` | string | no | Reasoning effort, forwarded to `opencode --variant` (provider-specific). `deepseek/deepseek-v4-pro` and `deepseek/deepseek-v4-flash` support `high`/`max`; omit for the model default |
### list_models
Runs `opencode models` and returns the list verbatim.
## Permission model
**Read this before delegating real work.**
- The sub-agent runs under **opencode's own permission config** (`~/.config/opencode/opencode.jsonc`), completely outside the MCP client's permission system.
- In headless `run` mode (how this gateway invokes opencode), an `ask` rule is **instantly auto-rejected** by opencode — no pending request ever exists, so nobody can answer it interactively (observed on 1.18.x).
- Recommended opencode config for use with this gateway:
```jsonc
{
"permission": { "*": "allow", "edit": "ask", "bash": "ask" }
}
```
With this, file edits and shell commands flow through the gateway's bridging logic:
- `auto_approve: true` → opencode gets `--auto`, nothing is ever asked.
- default → the ask is auto-rejected, the gateway's stall detection aborts the session within ~60 s and returns exactly which tool call was rejected, so the main agent can ask its user and retry with `auto_approve: true`.
- Setting a tool to `deny` in opencode config also works (the task fails immediately), it just bypasses the gateway's stall detection.
- `auto_approve: true` is deliberately dangerous — it approves everything not explicitly denied. Only use it in YOLO / fully autonomous contexts you trust.
## How it works
- On first call the gateway checks `http://localhost:4096` (`GET /config` must return JSON, so a random process occupying the port is not mistaken for opencode). If unreachable it spawns `opencode serve --port 4096` and waits up to 30 s (concurrent calls share one startup).
- It then runs:
`opencode run --attach http://localhost:4096 -m <model> --dir <projectDir> --format json [--auto] [--variant v] "<task>"`
- Adaptations to observed opencode 1.18.x behavior (Windows):
- stdin is closed on spawn — `opencode run` waits for stdin EOF when stdin is not a TTY;
- `opencode.exe` is spawned directly (default `%APPDATA%\npm\node_modules\opencode-ai\bin\opencode.exe`, override with `OPENCODE_EXE`) to avoid cmd.exe codepage mangling of non-ASCII arguments;
- in `--attach` mode `--format json` prints only a `step_start` line and exits — the gateway takes the `sessionID` from that first line, then polls `GET /session/{id}/message` until this round's last assistant message completes (`finish` ≠ `tool-calls`) and extracts its text parts.
- The `agent_id` → session mapping lives at `<projectDir>/.kimi-code/external-agents.json` (contains task summaries — local material, gitignored by this repo's `.gitignore`):
- mapping exists → append `--session <sessionID>` to continue;
- no mapping → append `--title <agent_id>` to create a named session and record its `sessionID` (`ses_…`);
- "session not found" errors → automatically retry without `--session` and update the mapping.
- One mutex per `agent_id`: concurrent calls are serialized. Total timeout is 10 minutes per call; on timeout the serve-side session is aborted via `POST /session/{id}/abort` and the local process tree is killed.
- Stall detection: if the latest assistant message completed with `finish=tool-calls` and no new message appears within 60 s, the turn is considered dead (typical cause: permission auto-rejected in headless mode); the session is aborted and the tool errors are returned.
- Poll failures (serve down, session 404, non-200) throw immediately instead of waiting out the timeout.
## Environment variables
| Variable | Purpose | Default |
| --- | --- | --- |
| `SUBAGENT_PROJECT_DIR` | Pin the project directory (drives `--dir` and the mapping file location) | server process cwd |
| `OPENCODE_EXE` | Path to the opencode executable | `%APPDATA%\npm\node_modules\opencode-ai\bin\opencode.exe`, falling back to `opencode` from PATH |
Set them in the server entry's `"env"` block in `mcp.json`.
## Troubleshooting
- Server logs go to stderr (startup, executed commands, errors); check `/mcp` status on the client side.
- Port 4096 occupied by something else: the identity check (`GET /config` returning JSON) will refuse to treat it as ready — free the port or change `PORT` in `server.js`.
- Delete an `agent_id` entry from `.kimi-code/external-agents.json` to start a fresh session for it next time.
- After an opencode major upgrade, re-run the smoke tests — this server adapts to 1.18.x behavior (`sessionID` casing, `--format json` output shape, headless ask auto-rejection) that may change.
## License
[MIT](LICENSE)
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available