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
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:
123
docs/opencode-extension.md
Normal file
123
docs/opencode-extension.md
Normal 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.
|
||||
Reference in New Issue
Block a user