Files
mcpctl/docs/pi-extension.md
Michal 28f1a411fd feat(pi): add native pi integration — extension, config pi, skills --agent pi
- src/pi-ext/: self-contained pi extension (mcpctl-pi.ts + vendored
  mcp-http client) that talks JSON-RPC directly to mcplocal and registers
  project MCP tools as native pi tools. No MCP client, no ~/.claude.
- Persistent per-project session so gated projects ungate on begin_session.
- /mcpctl command: status, switch project (GUI), refresh tools, sync skills.
- mcpctl config pi: installs extension, wires pi settings, persists active
  project, syncs skills into ~/.pi/agent/skills.
- skills sync: add --agent pi (target install root).
- docs + tests.
2026-08-08 16:23:02 +01:00

98 lines
3.9 KiB
Markdown
Raw 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 × pi — native integration (no MCP, no Claude)
## 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/`.
Claude Code supports MCP natively, so tools travel over MCP. **pi does not
support MCP.** But pi has two native mechanisms that cover the same ground:
- **Extensions** — TypeScript modules that can register tools, commands, and
custom UI, and can talk HTTP directly.
- **Skills** — pi implements the [Agent Skills standard](https://agentskills.io/specification)
and loads `SKILL.md` trees (the exact format mcpctl already syncs).
This addon exploits both so mcpctl works with **pi only**, with **no MCP** and
**no dependency on `~/.claude/`** (so it keeps working if you drop Claude).
## How it works
The MCP layer is not magic. `mcpctl mcp` is just a JSON-RPC ↔ stdio bridge that
forwards to mcplocal's Streamable-HTTP endpoint:
```
POST {mcplocalUrl}/projects/<project>/mcp
Accepts: application/json, text/event-stream
Authorization: Bearer <token>
```
The protocol is JSON-RPC over HTTP:
- `initialize` → returns an `mcp-session-id` header
- `notifications/initialized`
- `tools/list``{ tools: [{ name, description, inputSchema }] }`
- `tools/call``{ name, arguments }``{ content, isError }`
The pi extension is a **direct JSON-RPC client** to that same endpoint. It
replaces the MCP transport with a plain function call, so pi never needs an MCP
client library.
### Gating
Some projects are *gated*: until `begin_session` is called, `tools/list` returns
only `begin_session`. After it is called the full tool set appears.
**Critical subtlety:** mcplocal's gate state is bound to a `mcp-session-id`. A
fresh session is gated again even after another session was ungated — so the
extension keeps **one persistent `McpHttpSession` per active project** and
routes every `tools/call` through it. After each call it re-runs `tools/list`
and reconciles the active pi tool set, so calling `begin_session` automatically
opens the gate and reveals the rest — no push listener required (mcplocal's HTTP
responses are request/response, not a persistent push stream).
## Deliverables
| Artifact | Purpose |
|----------|---------|
| `src/pi-ext/mcpctl-pi.ts` | Self-contained pi extension (load via `pi -e` or settings `extensions`) |
| `mcpctl config pi` | CLI wiring: registers the extension with pi + initial skill sync |
| `docs/pi-extension.md` | This document |
## Project switching
The active project is persisted in `~/.mcpctl/pi-state.json` (settable, immune to
your shell's cwd) and is also inferred from a `.mcpctl-project` marker walk-up.
`/mcpctl` opens an interactive menu; **switch project** lists projects via
`ctx.ui.select()` and reloads the tool set for the new project, namespaced so
projects never collide.
### Tool naming
To allow multiple projects and avoid schema collisions, registered pi tools are
namespaced: `mc_<project-slug>_<mcp-tool-name>`. Only the **current project's**
tools are in the active set at any time.
## Skills
Skills already sync in the Agent Skills `SKILL.md` format. `mcpctl config pi`
(when given a project) writes them under pi's own directory
`~/.pi/agent/skills/` and registers that directory in `~/.pi/agent/settings.json`
— no Claude involvement. `mcpctl pi sync-skills` (a thin wrapper) re-runs the
existing `skills` code path against the pi target directory.
## Layout
```
src/pi-ext/
mcpctl-pi.ts # the extension (self-contained)
mcp-http.ts # vendored Streamable-HTTP JSON-RPC client (no deps)
```
The extension imports only from pi-bundled packages
(`@earendil-works/pi-coding-agent`, `@earendil-works/pi-ai`, `typebox`), so it
loads standalone.