Files
mcpctl/docs/opencode-extension.md
Michal 0a02616c7f
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
feat(opencode): add mcpctl config opencode + /mcpctl TUI switcher with bottom project indicator
2026-08-08 21:52:32 +01:00

124 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.