feat(opencode): native opencode addon — /mcpctl switcher + live project switching + footer indicator #100

Open
michal wants to merge 1 commits from feat/opencode-mine into main
Owner

Implements mcpctl as an addon for opencode, mirroring the existing pi and prime-agent integrations.

What you get

mcpctl config opencode --project monitoring

Inside opencode:

/mcpctl filterable project picker — switches live, no restart
/mcpctl-status active project, mount state, gateway URL
/mcpctl-skills re-sync this project's skills
footer mcpctl:<project> right of the model name, directly above the token counter

Why it isn't just an mcp block in opencode.json

config claude / config prime-agent write the project's MCP server into the host's config and need a restart. opencode exposes an HTTP API for its own MCP registry (POST /mcp, /mcp/{name}/disconnect), so the project is mounted through the running app instead:

  • the bearer token lives in ~/.mcpctl/opencode-state.json (0600), not in a mode-0644 opencode.json people paste into bug reports;
  • switching takes effect on the next turn. opencode.json is never touched.

Design notes

  • Constant MCP server name (mcpctl). Tools keep a stable mcpctl_* prefix, and because opencode re-resolves the tool list per request the model just sees the new project's tools next turn — no dead-tool-name message, unlike pi.
  • An unchanged mount is never re-registered. mcp.add rebuilds the connection, and mcplocal binds a gated project's unlocked state to that connection's mcp-session-id — re-adding every turn would re-lock a project begin_session had just opened.
  • The server plugin does not mount during setup.

setup before opencode's server accepts connections; client.mcp.add calls back into it and awaiting there hangs opencode on a blank screen.

  • The switcher is a UI shell. It shells out to this CLI (--skip-plugin --skip-marker) so token minting, state and skills stay in one place, then re-points the live mount.
  • A switch with no usable credential fails. Non-zero exit, state file untouched, so the previously active project keeps working rather than being replaced by a mount that 401s.
  • Indicator via api.kv, which is reactive — the footer slot re-renders with no signal plumbing and is right on the first frame.
  • Footer placement: session_prompt_right / home_prompt_right render on the prompt's bottom line, right of the model name, directly above the token counter. (app_bottom would cost a whole terminal row; home_footer replaces the cwd/version footer.)

Also in here

  • skills sync --agent opencode~/.config/opencode/skill (XDG-aware), same shared-tree semantics as pi/prime-agent, own state file.
  • The credential plumbing shared with config prime-agent (mint / reuse / retire) is lifted to one place and parameterised by agent instead of copied.
  • Plugin sources are embedded in the CLI (generated + freshness-tested) and typechecked against the real @opencode-ai/plugin (1.18.15) types.

Verification

Full tsc --build + typecheck:opencode-ext clean. Full CLI suite passes (619 tests, incl. 26 new for embed freshness, settings, config opencode, ordering). Completions regenerated.

Docs: docs/opencode-extension.md, README section.

Implements mcpctl as an addon for **opencode**, mirroring the existing pi and prime-agent integrations. ## What you get ```bash mcpctl config opencode --project monitoring ``` Inside opencode: | | | |---|---| | `/mcpctl` | filterable project picker — switches **live, no restart** | | `/mcpctl-status` | active project, mount state, gateway URL | | `/mcpctl-skills` | re-sync this project's skills | | footer | `mcpctl:<project>` right of the model name, directly above the token counter | ## Why it isn't just an `mcp` block in opencode.json `config claude` / `config prime-agent` write the project's MCP server into the host's config and need a restart. opencode exposes an HTTP API for its **own** MCP registry (`POST /mcp`, `/mcp/{name}/disconnect`), so the project is mounted through the running app instead: - the bearer token lives in `~/.mcpctl/opencode-state.json` (0600), not in a mode-0644 `opencode.json` people paste into bug reports; - switching takes effect on the next turn. `opencode.json` is never touched. ## Design notes - **Constant MCP server name (`mcpctl`).** Tools keep a stable `mcpctl_*` prefix, and because opencode re-resolves the tool list per request the model just sees the new project's tools next turn — no dead-tool-name message, unlike pi. - **An unchanged mount is never re-registered.** `mcp.add` rebuilds the connection, and mcplocal binds a gated project's unlocked state to that connection's `mcp-session-id` — re-adding every turn would re-lock a project `begin_session` had just opened. - **The server plugin does not mount during setup.** setup before opencode's server accepts connections; `client.mcp.add` calls back into it and awaiting there hangs opencode on a blank screen. - **The switcher is a UI shell.** It shells out to this CLI (`--skip-plugin --skip-marker`) so token minting, state and skills stay in one place, then re-points the live mount. - **A switch with no usable credential fails.** Non-zero exit, state file untouched, so the previously active project keeps working rather than being replaced by a mount that 401s. - **Indicator via `api.kv`**, which is reactive — the footer slot re-renders with no signal plumbing and is right on the first frame. - **Footer placement**: `session_prompt_right` / `home_prompt_right` render on the prompt's bottom line, right of the model name, directly above the token counter. (`app_bottom` would cost a whole terminal row; `home_footer` replaces the cwd/version footer.) ## Also in here - `skills sync --agent opencode` → `~/.config/opencode/skill` (XDG-aware), same shared-tree semantics as pi/prime-agent, own state file. - The credential plumbing shared with `config prime-agent` (mint / reuse / retire) is lifted to one place and parameterised by agent instead of copied. - Plugin sources are embedded in the CLI (generated + freshness-tested) and typechecked against the real `@opencode-ai/plugin` (1.18.15) types. ## Verification Full `tsc --build` + `typecheck:opencode-ext` clean. Full CLI suite passes (619 tests, incl. 26 new for embed freshness, settings, `config opencode`, ordering). Completions regenerated. Docs: [docs/opencode-extension.md](docs/opencode-extension.md), README section.
michal added 1 commit 2026-08-08 23:55:12 +00:00
feat(opencode): native opencode addon — /mcpctl switcher, live project switching, footer indicator
Some checks failed
CI/CD / lint (pull_request) Successful in 1m10s
CI/CD / test (pull_request) Successful in 1m22s
CI/CD / typecheck (pull_request) Successful in 3m8s
CI/CD / smoke (pull_request) Failing after 1m55s
CI/CD / build (pull_request) Successful in 2m8s
CI/CD / publish (pull_request) Has been skipped
2022e584d1
Implement mcpctl as an opencode addon mirroring the pi/prime-agent
integrations. Mounts the active project's MCP gateway through opencode's own
live MCP API, so switching projects needs no restart and opencode.json is
never touched (the bearer token stays in a 0600 state file).

- server plugin (plugin/mcpctl.ts): headless mount under a stable 'mcpctl'
  name, re-asserted on first contact + before each turn; skips re-registering
  an unchanged mount so a gated project's begin_session state survives.
- TUI plugin (mcpctl/mcpctl-tui.tsx): /mcpctl filterable picker (live switch),
  /mcpctl-status, /mcpctl-skills, and a mcpctl:<project> footer indicator
  right of the model name, directly above the token counter.
- mcpctl config opencode: mint/reuse project mcptoken, install + register
  plugins, write 0600 state, write marker, sync skills.
- skills sync --agent opencode: new shared-tree target (XDG-aware).
- shared credential plumbing lifted from config prime-agent and parameterised
  by agent so both hosts share mint/reuse/retire logic.
- embedded-source generator + freshness test; typechecked against the real
  @opencode-ai/plugin types (1.18.15); unit tests for settings, order, embed.

typecheck (incl. opencode-ext) and the full cli suite (619 tests) pass.
Some checks failed
CI/CD / lint (pull_request) Successful in 1m10s
CI/CD / test (pull_request) Successful in 1m22s
CI/CD / typecheck (pull_request) Successful in 3m8s
CI/CD / smoke (pull_request) Failing after 1m55s
CI/CD / build (pull_request) Successful in 2m8s
CI/CD / publish (pull_request) Has been skipped
This pull request has changes conflicting with the target branch.
  • README.md
  • docs/opencode-extension.md
  • package.json
  • pnpm-lock.yaml
  • scripts/generate-opencode-extension.ts
  • src/cli/src/commands/config.ts
  • src/cli/src/commands/skills.ts
  • src/cli/src/config/opencode-extension.ts
  • src/cli/src/utils/opencode-settings.ts
  • src/cli/tests/commands/config-opencode.test.ts
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/opencode-mine:feat/opencode-mine
git checkout feat/opencode-mine
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: michal/mcpctl#100