feat(opencode): add mcpctl config opencode + /mcpctl TUI switcher with bottom project indicator
Some checks failed
CI/CD / lint (pull_request) Successful in 1m7s
CI/CD / test (pull_request) Successful in 1m22s
CI/CD / typecheck (pull_request) Successful in 2m49s
CI/CD / smoke (pull_request) Failing after 1m54s
CI/CD / build (pull_request) Successful in 2m16s
CI/CD / publish (pull_request) Has been skipped

This commit is contained in:
Michal
2026-08-08 21:23:12 +01:00
parent 2513da33c3
commit 0a02616c7f
16 changed files with 1146 additions and 22 deletions

123
docs/opencode-extension.md Normal file
View File

@@ -0,0 +1,123 @@
# 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:
1. a `.mcp.json` entry running `mcpctl mcp -p X` (an MCP **stdio bridge**), and
2. a `SessionStart` hook + `mcpctl skills sync` that 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 X` registers the project's MCP **stdio
bridge** (`mcpctl mcp -p X`) as an opencode `local` MCP server, installs a
small **TUI plugin**, and syncs the project's skills.
- The TUI plugin gives you a `/mcpctl` slash 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`):
```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:
```json
{
"plugin": ["./plugin/mcpctl-switch.tsx"]
}
```
The plugin is a **TUI plugin** (module `default` exports `{ id, tui }`). It:
- registers a `/mcpctl` slash command (surfaces in the prompt's `/` autocomplete)
that opens a **filterable** project picker (opencode's `DialogSelect` filters
as you type), and
- renders the active project in the **`app_bottom`** slot — 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:
```bash
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.