4.7 KiB
mcpctl × opencode — native integration (TUI plugin + MCP bridge)
Motivation
With Claude Code, mcpctl config claude --project X wires a project's MCP
servers into the agent two ways:
- a
.mcp.jsonentry runningmcpctl mcp -p X(an MCP stdio bridge), and - a
SessionStarthook +mcpctl skills syncthat materialises server-side skills under~/.claude/skills/.
opencode speaks MCP natively (like Claude), consumes Agent Skills SKILL.md
trees (like pi), and lets plugins render into its TUI. This addon mirrors the
prime-agent/pi integration so mcpctl "just works" from inside opencode:
mcpctl config opencode --project Xregisters the project's MCP stdio bridge (mcpctl mcp -p X) as an opencodelocalMCP server, installs a small TUI plugin, and syncs the project's skills.- The TUI plugin gives you a
/mcpctlslash command to switch projects from the opencode prompt, and shows the current project in the bottom status bar — next to where opencode prints the model and the token counter — so you always know which project's MCP servers are live.
How it works
MCP over stdio
opencode has native MCP client support, so unlike pi there is no JSON-RPC client
to vendor. mcpctl config opencode --project X writes into opencode's global
config (~/.config/opencode/opencode.json):
{
"mcp": {
"servers": {
"homeautomation": {
"type": "local",
"command": ["mcpctl", "mcp", "-p", "homeautomation"],
"mcpctlManaged": true
}
}
}
}
mcpctlManaged is a tag opencode ignores (its schema tolerates unknown keys);
mcpctl uses it to recognise — and retire — the single active entry when you
switch, so exactly one mcpctl project is mounted at a time and hand-configured
servers are never touched.
The TUI plugin (footer indicator + /mcpctl)
opencode loads plugins from ~/.config/opencode/plugin/, but its auto-discovery
glob is *.{ts,js} — it won't pick up a JSX .tsx file. So config opencode
writes plugin/mcpctl-switch.tsx and references it from opencode.json's
plugin array as a relative path:
{
"plugin": ["./plugin/mcpctl-switch.tsx"]
}
The plugin is a TUI plugin (module default exports { id, tui }). It:
- registers a
/mcpctlslash command (surfaces in the prompt's/autocomplete) that opens a filterable project picker (opencode'sDialogSelectfilters as you type), and - renders the active project in the
app_bottomslot — the bottom strip where opencode shows the model and token counter.
Switching runs the CLI:
mcpctl config opencode --project <name> --skip-plugin --skip-marker --config-dir <dir>
which rewrites opencode.json (section above). opencode's config watcher
reloads MCP so the new project's tools come up — if a running session doesn't
pick it up, a restart does.
Deliverables
| Artifact | Purpose |
|---|---|
src/opencode-ext/mcpctl-opencode.tsx |
Real, reviewable TUI plugin source |
scripts/generate-opencode-extension.ts |
Embeds that source into the CLI (config/opencode-extension.ts) |
src/cli/src/config/opencode.ts |
opencode.json read/merge/write + state helpers |
mcpctl config opencode |
CLI wiring: MCP + plugin + state + marker + skills |
mcpctl skills sync --agent opencode |
Sync skills into ~/.config/opencode/skills/ |
docs/opencode-extension.md |
This document |
Regenerating the embedded plugin
The plugin ships as source embedded into the CLI, so an installed binary can
still provision a working addon. After editing src/opencode-ext/*.tsx,
regenerate the embedded copy or the CLI will keep installing the old source:
npx tsx scripts/generate-opencode-extension.ts
A test (tests/config/opencode-embed.test.ts) fails if the embed drifts from
the source.
Skills
mcpctl config opencode (and mcpctl skills sync --agent opencode) materialise
the active project's skills under ~/.config/opencode/skills/<name>/, the tree
opencode reads for Agent Skills. Sync state is tracked separately
(~/.mcpctl/skills-state-opencode.json), so opencode, prime-agent and Claude
never fight over bookkeeping.
Switching with --config-dir
The /mcpctl plugin shells out with --config-dir pointing at the config dir it
was installed into, so a non-default opencode config dir keeps working too.
Project switching / auth
Like the Claude path, this uses the stdio bridge (mcpctl mcp -p X) and a
locally-running mcplocal daemon, so there is no HTTP gateway / bearer-token
provisioning dance (that's specific to prime-agent's remote gateway). Pointing it
at an authenticated mcplocal serve needs a project mcptoken wired through
mcpctl mcp — outside this addon's scope for now.