🧰 Tools
The tools mcp-opencode exposes — query a model, list models, talk to a live opencode session, and run headless jobs on private instances.
mcp-opencode exposes ten tools: query and list_models for one-off
questions; list_sessions, send and read for holding a conversation with
a live opencode session; and start_instance, task, wait, list_instances
and stop_instance for running headless jobs on a private opencode server.
⚡ query
Send a prompt to an opencode model and return the response.
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt | string | yes | The prompt to send. |
model | string | no | Model in provider/model format. Defaults to github-copilot/gpt-4.1. |
Each call creates a temporary opencode session, sends the prompt, returns the text response, and deletes the session. If the requested model is blocked by your allow/block filters, the call returns an error explaining the model isn't allowed — see Model filtering.
The tool description surfaced to your assistant includes the default model and a summary of the active filters, so the assistant always knows what's available.
🔍 list_models
List the models available for use.
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | no | Provider name to filter by (e.g. anthropic, openai). Omit for all. |
Returns one provider/model ID per line. The list reflects your opencode
configuration — exactly what opencode models prints in your terminal — passed
through your allow/block filters. Pass a provider to narrow the list to a
single provider.
If no models match (for example, an unknown provider name), the tool returns a
clear No models found message rather than an empty response.
🗂️ list_sessions
List the sessions on opencode's background service (shared by every TUI window) and on the headless instances this MCP started, most recently updated first.
| Parameter | Type | Required | Description |
|---|---|---|---|
directory | string | no | Only list sessions for this project directory. |
Returns one session per line: ID, title, directory, the port(s) of the servers serving it, and last update. Use it to find the session you have open in the opencode TUI.
💬 send
Send a message to an existing session and return its reply.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | yes | Session ID from list_sessions. |
prompt | string | yes | The message to send. |
agent | string | no | opencode agent for the session (e.g. build, plan). |
model | string | no | Model for the session, provider/model. Must pass the allow/block filters. |
port | number | no | Server to use. Defaults to the server that owns the session. |
timeout_seconds | number | no | How long to wait for the reply. Defaults to MCP_OPENCODE_SEND_TIMEOUT (600). |
The session keeps its history, and the exchange appears live in any TUI
showing it. send never creates or deletes a session. opencode 2 sets agent
and model on the session rather than per message, so passing either switches
the session for later turns too, as picking it in the TUI does. Model
allow/block filters apply only when you pass a model.
If the reply takes longer than the timeout, send returns a "still running"
note instead of hanging. opencode keeps working; call read later for the
answer.
📜 read
Read a session's recent messages as a condensed transcript.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | yes | Session ID from list_sessions. |
limit | number | no | Number of most recent messages to return. Default 20. |
port | number | no | Server to read from. Defaults to the one that owns the session. |
Each message is headed by its role, with text in full and tool calls summarised
as [tool: name (status)].
🏗️ Headless instances
The five tools below run jobs on a private opencode server per job, away
from the background service your windows use. An instance is started
for one project directory, takes one or more tasks, and is stopped when you
are done (or reaped once idle).
start_instance
| Parameter | Type | Required | Description |
|---|---|---|---|
directory | string | yes | Absolute path of the project the instance works in. |
Spawns opencode serve --port 0 --hostname 127.0.0.1 in directory, reads the
port from its server listening on … line (10s timeout), registers it with
the password it was started with, and returns { port, url }. With MCP_OPENCODE_SANDBOX=srt the same command
is wrapped as srt --settings <generated file> opencode serve … (or your
MCP_OPENCODE_SANDBOX_SETTINGS file verbatim), and the registry row records
"sandbox": "srt" (else null). The server runs with a hardened environment:
GIT_TERMINAL_PROMPT=0,GIT_SSH_COMMAND=falseand an emptycredential.helper, so git cannot authenticate to a remote;GH_TOKENandGITHUB_TOKENremoved, and a fresh random password inOPENCODE_PASSWORD(basic auth, useropencode);OPENCODE_CONFIG_CONTENTin opencode 2's native shape:modelandagents.title.modelpinned toMCP_OPENCODE_MODEL, the permission rules below inpermissionsso every agent in the instance, subagents included, follows them, and every deny repeated inpolicies.
It refuses to start if the default model fails the allow/block filters.
task
| Parameter | Type | Required | Description |
|---|---|---|---|
port | number | yes | Port from start_instance. |
prompt | string | yes | What the job should do. |
model | string | no | provider/model. Defaults to MCP_OPENCODE_MODEL; filtered. |
agent | string | no | opencode agent. Defaults to build. |
title | string | no | Session title. |
Creates a fresh session with the guard rails, prompts it in the background and
returns at once with { session_id, port, attach }. It never deletes the
session. A port that start_instance didn't register is refused.
The job tries [its model, ...MCP_OPENCODE_MODEL_FALLBACK] once each, in
order (deduped, allow/block-filtered). When the session stays in retry for
MCP_OPENCODE_RETRY_TIMEOUT_SECONDS (default 90) or its reply ends in a
provider error, a per-job watchdog interrupts the session, switches its model
and re-prompts the same session with the same prompt on the next model, so history and worktree edits
carry over — even when nobody calls wait. When the list is exhausted the job
settles as error.
wait
| Parameter | Type | Required | Description |
|---|---|---|---|
port | number | yes | Port of the instance. |
session_id | string | yes | Session ID from task. |
timeout_seconds | number | no | Default and maximum 570. |
Polls every 2s until the session is idle or the timeout hits, then returns its
status (idle, busy, retry or error), the current model, the
fallbacks taken so far ([{ from, to, reason, at }]), the last assistant
text, and the files the job changed with line counts. The changed-files diff
spans the whole session, so fallback re-prompts don't re-base it. The 570s cap keeps it under a 600s tool timeout; on busy or retry,
call wait again.
list_instances
Reaps first, then returns every registered instance with the sessions task
started on it, each with its live status, current model and fallbacks —
plus each instance's sandbox mode ("srt" or null).
stop_instance
| Parameter | Type | Required | Description |
|---|---|---|---|
port | number | yes | Port of the instance. |
Interrupts busy sessions, sends SIGTERM (then SIGKILL if needed), deregisters
the instance and confirms the port has stopped listening.
🛡️ Guard rails
Every task session gets a ruleset with no ask anywhere, because nobody
is there to answer and a headless session would wait forever. opencode applies
the last matching rule, so the shell denies come after the shell allow:
| Action | Effect |
|---|---|
read, edit, glob, grep, subagent | allow |
external_directory, question, webfetch, websearch | deny |
shell * | allow |
shell *git* push*, *git* remote*, *git* reset*--hard*, gh *, * gh *, *npm* publish* | deny |
opencode 2 allows external_directory by default; the instance keeps it
denied. Each deny is also a tool.use policy (shell:*git* push*,
webfetch:*, …). Policies run after every rule and can only deny, so a
repository's own opencode config cannot allow these back.
📦 Sandboxing (opt-in)
Set MCP_OPENCODE_SANDBOX=srt to wrap each instance in an OS-level sandbox
via srt (npm i -g @anthropic-ai/sandbox-runtime; macOS sandbox-exec,
Linux bubblewrap). Unset, everything behaves as before. Anything else fails
start_instance with the supported values, and a missing srt fails the
start rather than running unsandboxed — never silently.
The generated settings file allows writes only to the instance directory, its
git dir(s) (common and per-worktree, so worktrees work), opencode's own
data/config/cache/state dirs and the temp dir; denies reads of credential
paths (~/.ssh, ~/.aws, ~/.config/gh, ~/.gnupg, ~/.netrc, ~/.npmrc,
**/.env, **/.env.*, ~/Library/Keychains, ~/.config/gcloud, ~/.kube,
~/.docker/config.json, and the background service's service.json files,
which hold its password); and allows only the provider, github, npm and
loopback domains plus local binding. MCP_OPENCODE_SANDBOX_SETTINGS points at
your own settings file to use instead of the generated one. Sandboxed
instances additionally deny rm -rf*, sudo * and piped curl/wget in
their opencode permission config — deny, never ask, because a headless
session would wait forever on a prompt.
This is defence in depth, not a guarantee: see the README's Sandboxing section for the verified-vs-assumed allowlist and the limits.
🧹 Reaping
The reaper runs at MCP start-up, on start_instance and on list_instances. It
drops instances whose process is gone and stops instances where every session
has been idle for longer than MCP_OPENCODE_INSTANCE_TTL seconds (default
1800). It never stops a busy instance and never touches a server it didn't
start. When the MCP exits, it stops the instances it started.
🔭 Watching a job
task returns an attach command. Run it in a terminal to open the session
in the TUI against the instance, with the instance's password, and watch it
live or step in:
OPENCODE_PASSWORD=… opencode --server http://127.0.0.1:53817 --session ses_…