An MCP server (stdio transport) that delegates subtasks from Kimi Code — or any MCP-compatible agent — to a locally running opencode 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 · AI 安装指南(给 Agent 看的)
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.
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.)
- 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.jsonto start a fresh session next time.
- Session continuation — pass the same
agent_idto reuse the same opencode session with full history; ideal for iterative multi-round tasks. - Per-call model & reasoning effort —
modelis forwarded toopencode -m,variantto--variant(e.g.deepseek/deepseek-v4-prosupportshigh/max). Both can change between rounds of the same session. - Permission bridging —
auto_approve: trueruns opencode with--auto(YOLO-style); by default, permissionasks 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 servewith 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_idmutex, process-tree kill on Windows, UTF-8-safe argument passing (no cmd.exe codepage mangling).
- Node.js ≥ 18
- opencode CLI installed and in PATH, with at least one model provider configured (
opencode modelslists models). Developed and tested against opencode 1.18.x on Windows; other platforms should work via the PATH fallback (seeOPENCODE_EXEbelow). - An MCP client. Kimi Code is documented below, but any client that speaks MCP over stdio works.
git clone <your-fork-url>
cd subagent-gateway
npm install- Protocol handshake + tool list (no opencode needed):
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.jsYou should see the initialize result and two tool definitions (delegate_task, list_models).
- End-to-end delegation (really calls the configured model; auto-starts
opencode serveon first run):
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.jsAfter success, <projectDir>/.kimi-code/external-agents.json should contain a smoke-test → ses_… session mapping.
Add the following snippets manually — this repo never touches your config files.
{
"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" }.
[[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.
## 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.
| 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 asks; 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 |
Runs opencode models and returns the list verbatim.
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
runmode (how this gateway invokes opencode), anaskrule 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:
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
denyin opencode config also works (the task fails immediately), it just bypasses the gateway's stall detection. auto_approve: trueis deliberately dangerous — it approves everything not explicitly denied. Only use it in YOLO / fully autonomous contexts you trust.
- On first call the gateway checks
http://localhost:4096(GET /configmust return JSON, so a random process occupying the port is not mistaken for opencode). If unreachable it spawnsopencode serve --port 4096and 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 runwaits for stdin EOF when stdin is not a TTY; opencode.exeis spawned directly (default%APPDATA%\npm\node_modules\opencode-ai\bin\opencode.exe, override withOPENCODE_EXE) to avoid cmd.exe codepage mangling of non-ASCII arguments;- in
--attachmode--format jsonprints only astep_startline and exits — the gateway takes thesessionIDfrom that first line, then pollsGET /session/{id}/messageuntil this round's last assistant message completes (finish≠tool-calls) and extracts its text parts.
- stdin is closed on spawn —
- 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 itssessionID(ses_…); - "session not found" errors → automatically retry without
--sessionand update the mapping.
- mapping exists → append
- One mutex per
agent_id: concurrent calls are serialized. Total timeout is 10 minutes per call; on timeout the serve-side session is aborted viaPOST /session/{id}/abortand the local process tree is killed. - Stall detection: if the latest assistant message completed with
finish=tool-callsand 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.
| 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.
- Server logs go to stderr (startup, executed commands, errors); check
/mcpstatus on the client side. - Port 4096 occupied by something else: the identity check (
GET /configreturning JSON) will refuse to treat it as ready — free the port or changePORTinserver.js. - Delete an
agent_identry from.kimi-code/external-agents.jsonto 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 (
sessionIDcasing,--format jsonoutput shape, headless ask auto-rejection) that may change.
{ "permission": { "*": "allow", "edit": "ask", "bash": "ask" } }