Files
mcpctl/docs/opencode-extension.md
Michal 9448c117df
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
feat(opencode): add opencode integration — /mcpctl switcher + footer indicator
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).
2026-08-08 22:45:38 +01:00

117 lines
4.5 KiB
Markdown
Raw Permalink 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 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).