Files
mcpctl/docs/opencode-extension.md
Michal 9aea1189bd
Some checks failed
CI/CD / lint (pull_request) Successful in 1m10s
CI/CD / test (pull_request) Successful in 1m23s
CI/CD / typecheck (pull_request) Successful in 2m58s
CI/CD / smoke (pull_request) Failing after 1m55s
CI/CD / build (pull_request) Successful in 4m56s
CI/CD / publish (pull_request) Has been skipped
feat(opencode): native opencode addon — bottom-row project indicator + in-TUI switcher
Adds an opencode integration mirroring the existing pi and prime-agent addons.

The addon is a TUI plugin (`src/opencode-ext/open-mcpctl.tsx`) that:
- renders the active mcpctl project into opencode's `app_bottom` slot — the
  status row that also carries the token counter / model info,
- registers a `mcpctl.switch_project` command bound to `<leader>m` that opens a
  searchable `DialogSelect` project picker,
- switches live by re-pointing opencode's `mcpctl` MCP server at the new
  project's mcplocal endpoint via the SDK client (native MCP, no JSON-RPC code),
- persists state in `~/.mcpctl/opencode-state.json` (+ `.mcpctl-project` marker).

Pure helpers (`filterProjects`, `toolChangeAnnouncement`) live in
`projects.ts` so they are unit-tested.

`mcpctl config opencode --project X` provisions it: writes the plugin into
`~/.config/opencode/plugin/`, registers the `.tsx` in opencode.json's plugin
array (auto-discovery only matches {ts,js}), best-effort installs the TUI peer
deps (`@opentui/*`), and syncs skills into `~/.config/opencode/skill`.
`mcpctl skills sync --agent opencode` is added as a fourth shared-tree target.

Closes typecheck gap with `typecheck:opencode-ext` against the real
`@opencode-ai/plugin` + `@opentui/solid` types.

Tests: embed freshness, opencode-settings, projects helpers, skills agent
root. Full `pnpm run typecheck` + cli tests pass; completions regenerated.
2026-08-08 21:48:14 +01:00

135 lines
5.2 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 integration (TUI plugin)
## Motivation
With Claude Code, `mcpctl config claude --project X` wires a project's MCP
servers into the agent using `.mcp.json`, and with pi/prime-agent it ships a
dedicated extension. This addon does the same for
[opencode](https://opencode.ai):
1. a **TUI plugin** (`open-mcpctl.tsx`) that shows the active mcpctl project in
opencode's bottom status row (the row that also carries the token counter /
model / mode info) and lets you **switch projects from inside the interface**
with a searchable picker, and
2. `mcpctl config opencode` which provisions it: writes the plugin into
`~/.config/opencode/plugin/`, registers it in `opencode.json`, provisions the
plugin deps, and persists the active project.
Where pi had to *reimplement* an MCP client (pi has no MCP), opencode has
first-class MCP support, so the plugin leans on opencode's own MCP machinery:
switching a project re-points opencode's `mcpctl` MCP server at the new
project's mcplocal endpoint (`{mcplocalUrl}/projects/<project>/mcp`) via the
SDK client, and opencode enumerates those tools natively. No JSON-RPC code in
the plugin.
## How it works
### The indicator (bottom status row)
The plugin registers a slot into `api.slots.register({ slots: { app_bottom } })`.
`app_bottom` is the row opencode renders directly beneath the session footer
statusline — i.e. beside the token counter / model / mode readout. It renders a
compact `mcpctl <project>` segment in the theme's accent/text colours.
### Switching projects
`<leader>m` (or the `mcpctl.switch_project` command in the command palette)
opens a `DialogSelect` listing projects from mcpd with search-as-you-type
filtering (active project first, then alphabetical). Picking one:
1. `client.mcp.disconnect({ name: "mcpctl" })` — drop the previous project's
tools,
2. `client.mcp.add({ name: "mcpctl", config: { type: "remote", url, headers } })`
+ `client.mcp.connect(...)` — point at the new project's mcplocal endpoint,
3. persists the choice to `~/.mcpctl/opencode-state.json`,
4. updates the bottom-row indicator and fires a toast.
Because the tools travel over opencode's native MCP, they pick up
`begin_session` gating exactly like any other mcpctl client. If a project is
gated, opencode only exposes its `begin_session` tool until it is called.
### State
The active project lives in `~/.mcpctl/opencode-state.json` (immune to your
shell's cwd) and is also inferred from a `.mcpctl-project` marker walk-up,
mirroring the pi extension.
## Deliverables
| Artifact | Purpose |
|----------|---------|
| `src/opencode-ext/open-mcpctl.tsx` | Self-contained opencode TUI plugin (the addon) |
| `mcpctl config opencode` | CLI wiring: installs the plugin, registers it, provisions deps + skills |
| `docs/opencode-extension.md` | This document |
## Using it
```bash
mcpctl config opencode --project <name>
```
This:
1. writes `~/.config/opencode/plugin/open-mcpctl.tsx`,
2. registers that `.tsx` in `opencode.json` under `plugin` (opencode's plugin
auto-discovery only matches `{ts,js}`, so the explicit entry is required for
a JSX plugin),
3. best-effort installs `@opencode-ai/plugin` + `@opentui/*` into the config
dir's `node_modules` (opencode provisions the former but not the TUI peers
a TUI plugin needs), and
4. syncs the project's skills into `~/.config/opencode/skill`.
Restart opencode (or start a new session) — the active project and its MCP
tools appear in the bottom status row. Switch with `<leader>m`.
Re-sync skills later:
```bash
mcpctl skills sync --agent opencode --project <name>
```
## Layout
```
src/opencode-ext/
open-mcpctl.tsx # the plugin (JSX, self-contained)
tsconfig.json # typechecks against real @opencode-ai/plugin types
```
The plugin imports only opencode-bundled packages
(`@opencode-ai/plugin/tui`, `@opentui/solid`, `solid-js`) and the node standard
library — no `@mcpctl/*`, no `~/.claude`.
## Typechecking
The plugin ships as *source*: it is embedded into the CLI
(`src/cli/src/config/opencode-extension.ts`, generated by
`scripts/generate-opencode-extension.ts`) and written verbatim into
`~/.config/opencode/plugin/`. The CLI's own build never compiles it, so without
a dedicated project nothing would check it against opencode's API — exactly how
a slotted component with the wrong shape would ship.
`src/opencode-ext/tsconfig.json` closes that gap, checking against the **real**
published `@opencode-ai/plugin` + `@opentui/solid` types (dev dependencies):
```bash
pnpm run typecheck # includes typecheck:opencode-ext
```
After editing `src/opencode-ext/*.tsx`, regenerate the embedded copy or the CLI
will keep installing the 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 plugin sends whatever bearer it finds in `~/.mcpctl/credentials`. A locally
running `mcpctl-local` daemon does not authenticate `/projects/*`, so this
works — the header is simply ignored. `mcplocal serve` registers a token-auth
preHandler that accepts **only** `mcpctl_pat_` bearers (project mcptokens), so
pointing it at an authenticated `mcplocal serve` needs a project mcptoken
(`mcpctl create mcptoken <name> --project <name>`).