opencodev2-mcp
Run a panel of pinned-model OpenCode v2 agents from any MCP host.
opencodev2-panel is a Bun/TypeScript MCP server for parallel reviews, delegated
tasks, and cross-model comparisons. Give each member a role, model, and task;
your host agent supervises the panel and brings the results together. Each
member embeds @opencode/sdk in its own process — no HTTP listener or separate
OpenCode server required.
Quickstart
You need Bun and provider access configured for local OpenCode v2. Runners inherit the MCP server's environment and use OpenCode's local database; see credentials and configuration.
Install dependencies in this repository:
bun installRegister
bun /absolute/path/to/opencodev2-mcp/index.tsas a local stdio MCP server. For hosts using anmcpServersconfiguration:{ "mcpServers": { "opencodev2-panel": { "command": "bun", "args": ["/absolute/path/to/opencodev2-mcp/index.ts"] } } }Use an absolute Bun executable path if your host cannot find
bunonPATH. OpenCode v2 hosts use a different configuration wrapper.Restart your MCP host. Confirm the panel tools are available, then call
panel_modelsto discover models.
How panels work
A panel is one or more independent members working under a shared
panel_id. Each member has its own task_id, OpenCode session, pinned model,
and optional system prompt, permissions, packet override, and working directory.
Members do not coordinate automatically; the host collects and synthesizes their work.
The supervisor launches detached runners and keeps prompts, statuses, results,
and manifests under ~/.opencodev2-mcp/. Runners can outlive an MCP-server
restart; use panel_list to rediscover them. Persisted results remain readable
after a runner exits, but this server does not restart dead runners.
Between turns, runners wait for follow-ups for linger_secs (default 3600
seconds). idle means the turn finished, not that the process exited.
Using a panel
- Choose live model IDs. Take
providerIDandidfrompanel_models, not remembered model names. Enumeration is not entitlement: test a newly selected model with a small one-turn panel before relying on it. - Write a useful packet. Include Goal, Context (paths to read), Constraints, Output format, and Done criteria. These are prompt conventions, not a server-enforced format.
- Call
spawn_panelwithpacket, an absoluteworkdir, and amembersarray. Each member needsnameandmodel: { providerID, id }; usesystemfor its role andpacketfor a distinct assignment. See the spawn example. - Watch, then collect. Poll
panel_overviewuntil members reachidlewithturn >= 1, or report their errors/exits.readyis not completion; a liveerrorcan accept a follow-up. Fetchpanel_results, verify important claims, and synthesize the answer. Results accumulate across turns. - Continue or stop. Check
resumableandlinger_expires_atbefore sending follow-ups, then watch for a new turn—not the previous turn'sidlestatus. Cancel members when you no longer need them.
Tools
The server exposes 12 tools, grouped by purpose. See the tool reference for inputs, outputs, and error handling.
Discover and start
panel_models— list deduplicated models; cached for 60 seconds.spawn_panel— launch members; returnpanel_id,manifest_path, and member IDs.
Watch and collect
panel_list— rediscover tasks, optionally filtered bypanel_id.panel_status/panel_overview— inspect onetask_id/ an entirepanel_id.panel_result/panel_results— retrieve accumulated text for one member / a panel.
Continue
panel_send— queue a follow-up for one live member.panel_broadcast— queue the same follow-up for all eligible live panel members.
Stop and clean up
panel_cancel/panel_cancel_panel— request cancellation and wait for process stop.panel_cleanup— remove old, stopped panels' task directories and manifests. Dry-run by default, with a seven-day cutoff; inspect candidates before settingdry_run: false. Live members are always skipped.
Permissions
Every member has a permission_preset (default editor):
readonlystarts with catch-all deny, then allowsread,glob,grep.editorstarts with catch-all deny, then allowsread,glob,grep,edit,shell,webfetch,websearch; explicitly deniesexternal_directoryandsubagent.questionis denied by the catch-all.fullstarts with catch-all allow, then deniesquestion. It is broad access, not unrestricted YOLO: baseline secret read/edit denies still apply.
The supervisor writes agent.json rules in order: preset → custom
permissions (may widen the preset) → BASELINE_DENY → question deny. The
engine uses the last matching rule. The runner enforces this resolved ruleset at
session level via sessions.create: it merges after all agent/config rules and
child sessions inherit it at creation. Discovered config documents in the
workdir or any ancestor up to the filesystem root, including above the Git
root, still load (providers, MCP servers, skills), and their permission rules
append after the runner's agent transform — but they cannot override the
member's effective policy or make it ask. Child (subagent) sessions run under
the member's policy, not their agent persona's own rules.
Member permissions accept only allow/deny; the schema rejects ask. If
any evaluation still ends in ask (config rules, engine defaults), the runner
denies it immediately — headless members have no approver, so nothing hangs
waiting for one. Under unmodified readonly/editor rules, wholly denied
tools are hidden from the model, including todowrite, skill, generated MCP
actions, and Code Mode execute; custom allows can make them available.
BASELINE_DENY denies read and edit for these exact globs from
contracts.ts:
".env*", "**/.env*", "*.env"
"*.pem", "*.key", "*.p12", "*.pfx", "*.jks", "*.keystore"
"id_rsa*", "**/id_rsa*", "id_ed25519*", "**/id_ed25519*"
"id_ecdsa*", "**/id_ecdsa*", "id_dsa*", "**/id_dsa*"
"credentials*", "**/credentials*"
".netrc", "**/.netrc", ".npmrc", "**/.npmrc", ".pypirc", "**/.pypirc"
".git-credentials", "**/.git-credentials"
".ssh/**", "**/.ssh/**", ".aws/**", "**/.aws/**", ".gnupg/**", "**/.gnupg/**"
".docker/**", "**/.docker/**", ".kube/**", "**/.kube/**", "*.tfvars"
File resources inside the active Location or its non-root project Git worktree
are workdir-relative (possibly ../...); external file resources are absolute.
external_directory checks paths outside both boundaries, not containment
within a member's workdir: files elsewhere in the same Git worktree are internal.
Whole-value wildcards use * (including /) and ?, not character classes.
This is an accidental read/edit guardrail, not a secrets boundary. grep,
allowed by all presets, matches the search expression rather than the path and
can dump .env; shell under editor/full can cat secrets. On macOS,
case-sensitive globs miss .ENV/x.KEY even on case-insensitive APFS. An
innocently named in-repo symlink to external secrets can bypass both read/edit
globs and external_directory. Intentional overcatch includes .env.example
and src/id_rsa_parser.ts. Keep secrets out of workdirs; there is no
filesystem sandbox, and trusted config/plugins and allowed shell have broader
authority. See permission guidance.
Important pitfalls
- Give reasoning time.
stall_secsdefaults to 300 seconds without progress, not total runtime. Increase it for reasoning-heavy models: silent thinking before the first event can look stalled.staleis only a display flag. - Separate writers. Read-only members may share a workdir. Give concurrent writers separate prepared checkouts. The server creates directories, not worktrees or copies; it provides no filesystem sandbox.
- Use the follow-up tools. Publication is multi-process-safe: concurrent MCP servers do not overwrite each other's prompts. A queued prompt is not proof of execution; dead or exited members cannot be resumed.
Examples and further reading
- Ready-made host skills: Devin and Muse, including model selection, watching, and synthesis conventions.
- Configuration: authentication, member options, packets, workdirs, and permissions.
- Tool reference: all 12 tools and their response contracts.
- Internals: file formats, lifecycle, watchdog, cancellation, cleanup, and regression coverage.
Development
bun test # helper and local-filesystem tests; no model calls
bunx tsc --noEmit # typecheck
Read AGENTS.md for repository conventions and integration checks before running the credential-dependent smoke test.
License
MIT — see LICENSE.