feat(opencode): add opencode integration — /mcpctl switcher + footer indicator
Some checks failed
CI/CD / typecheck (pull_request) Successful in 1m16s
CI/CD / test (pull_request) Successful in 1m25s
CI/CD / lint (pull_request) Successful in 2m42s
CI/CD / smoke (pull_request) Failing after 1m59s
CI/CD / build (pull_request) Successful in 4m54s
CI/CD / publish (pull_request) Has been skipped
Some checks failed
CI/CD / typecheck (pull_request) Successful in 1m16s
CI/CD / test (pull_request) Successful in 1m25s
CI/CD / lint (pull_request) Successful in 2m42s
CI/CD / smoke (pull_request) Failing after 1m59s
CI/CD / build (pull_request) Successful in 4m54s
CI/CD / publish (pull_request) Has been skipped
Implements mcpctl as an opencode addon, mirroring the prime-agent UX (active-project indicator + a /mcpctl project switcher). opencode is a native MCP client, so instead of a bridge this mounts the active project's proxy MCP gateway through opencode's own live MCP API. - src/opencode-ext/mcpctl-opencode.ts — server plugin (headless runs): mounts the active project under a stable `mcpctl` name via `client.mcp.add`, re-asserting on first contact and before each turn; never throws. - src/opencode-ext/mcpctl-opencode-tui.tsx — TUI plugin: `/mcpctl`, `/mcpctl-status`, `/mcpctl-skills`, and a `mcpctl:<project>` indicator in the prompt footer (next to the model name, above the token counter); the switch delegates to the CLI then re-points the mount live. - `mcpctl config opencode` — provision/reuse the project mcptoken, install + register the plugins (tui.json), write the 0600 state file, sync skills. - `skills sync --agent opencode` — new shared-tree target (~/.config/opencode/skill). - embedded-source generator + embed test, settings/config/order tests, completions, README + docs/opencode-extension.md. Typechecked against the real @opencode-ai/plugin types (1.18.15).
This commit is contained in:
116
docs/opencode-extension.md
Normal file
116
docs/opencode-extension.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# mcpctl × opencode — native MCP integration
|
||||
|
||||
## Motivation
|
||||
|
||||
opencode is a native MCP client, so mcpctl can mount a project's MCP servers
|
||||
without any bridge process. `mcpctl config opencode --project X`:
|
||||
|
||||
1. installs two plugins into opencode's config directory, and
|
||||
2. provisions the bearer credential the gateway needs.
|
||||
|
||||
Switching projects stays **live** — no restarting opencode — which is the
|
||||
difference between this and a static `mcp` block in `opencode.json`.
|
||||
|
||||
## How it works
|
||||
|
||||
Both plugins read `~/.mcpctl/opencode-state.json` (0600):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"project": "monitoring",
|
||||
"gatewayUrl": "https://mcp.ad.itaz.eu",
|
||||
"tokens": {
|
||||
"monitoring": "mcpctl_pat_..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`opencode.json` is **never touched**. The gateway needs an
|
||||
`Authorization: Bearer <mcpctl PAT>` header — a secret that does not belong in a
|
||||
mode-0644 config file users paste into bug reports — and the mount has to be
|
||||
re-pointable at runtime, which a config file cannot do.
|
||||
|
||||
### Server plugin (`plugin/mcpctl.ts`)
|
||||
|
||||
Auto-discovered by opencode; covers **headless** runs (`opencode run ...`),
|
||||
which load no TUI plugins. It mounts the active project as a remote MCP server
|
||||
under the constant name `mcpctl` via opencode's own live MCP API:
|
||||
|
||||
- on first server contact (`event`), and
|
||||
- before every user turn (`chat.message`), re-reading the state file so a
|
||||
switch made externally takes effect on the next message.
|
||||
|
||||
The mount is only re-registered when the `(url, token)` target actually changed
|
||||
— re-adding an unchanged config would rebuild the connection and drop mcplocal's
|
||||
gated `begin_session` state mid-conversation. Never throws: an unreachable
|
||||
gateway degrades to "no mcpctl tools", never to "opencode fails to start".
|
||||
|
||||
### TUI plugin (`mcpctl/mcpctl-tui.tsx`, registered in `tui.json`)
|
||||
|
||||
Adds to the opencode TUI:
|
||||
|
||||
- **`/mcpctl`** — switch the active project from a filterable picker (active
|
||||
project first; terms match name **or** description; a pre-filter prompt is
|
||||
offered when the list is long), matching the prime-agent switcher UX;
|
||||
- **`/mcpctl-status`** — show the active project and its MCP mount state;
|
||||
- **`/mcpctl-skills`** — re-sync this project's skills into opencode's skill dir;
|
||||
- a **`mcpctl:<project>`** indicator in the prompt footer, immediately right of
|
||||
the model name and just above the token counter.
|
||||
|
||||
The switch is delegated to the `mcpctl` CLI (`--skip-plugin --skip-marker`), so
|
||||
token minting, state and skills stay in the CLI; once the state file is updated
|
||||
the TUI re-points the live MCP mount through `api.client.mcp.add`.
|
||||
|
||||
## Deliverables
|
||||
|
||||
| Artifact | Purpose |
|
||||
|----------|---------|
|
||||
| `src/opencode-ext/mcpctl-opencode.ts` | Server plugin (headless mount) |
|
||||
| `src/opencode-ext/mcpctl-opencode-tui.tsx` | TUI plugin (`/mcpctl` switcher + footer indicator) |
|
||||
| `src/cli/src/utils/opencode-settings.ts` | CLI wiring: paths, `tui.json` registration, state file |
|
||||
| `mcpctl config opencode` | Provision token + install/register plugins + skill sync |
|
||||
| `docs/opencode-extension.md` | This document |
|
||||
|
||||
## Skills
|
||||
|
||||
`mcpctl config opencode` (when given a project and an API client) syncs the
|
||||
project's skills into `~/.config/opencode/skill/`. Re-sync later with:
|
||||
|
||||
```bash
|
||||
mcpctl skills sync --agent opencode --project <name>
|
||||
```
|
||||
|
||||
`opencode` is a shared-tree target (like `prime-agent`/`pi`): its own flat skill
|
||||
tree and its own state file (`~/.mcpctl/skills-state-opencode.json`), no Claude
|
||||
integration.
|
||||
|
||||
## Layout & typechecking
|
||||
|
||||
The plugins ship as *source*: they are embedded into the CLI
|
||||
(`src/cli/src/config/opencode-extension.ts`, generated by
|
||||
`scripts/generate-opencode-extension.ts`) and written verbatim into
|
||||
`~/.config/opencode/`. The CLI's own build never compiles them, so
|
||||
`src/opencode-ext/tsconfig.json` checks them against the **real**
|
||||
`@opencode-ai/plugin` types (dev dependency pinned to the opencode release they
|
||||
target) — the same guarantee `src/pi-ext/` gives.
|
||||
|
||||
```bash
|
||||
pnpm run typecheck # includes typecheck:opencode-ext
|
||||
```
|
||||
|
||||
After editing `src/opencode-ext/*`, regenerate the embedded copy or the CLI will
|
||||
keep installing old sources:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/generate-opencode-extension.ts
|
||||
```
|
||||
|
||||
A test (`tests/config/opencode-extension-embed.test.ts`) fails if you forget.
|
||||
|
||||
## Authentication caveat
|
||||
|
||||
The server and TUI plugins send the stored `mcpctl_pat_` project token as the
|
||||
bearer. A locally running `mcpctl-local` daemon does not authenticate
|
||||
`/projects/*`, so the header is simply ignored; an authenticated
|
||||
`mcplocal serve` accepts only `mcpctl_pat_` bearers (`mcpctl config opencode`
|
||||
mints one per project).
|
||||
Reference in New Issue
Block a user