Retire mcp-loader.ts in favour of pi's built-in MCP support
pi has had MCP as a built-in extension (builtin:mcp) since 0.99.0. It reads ~/.pi/agent/mcp.json and adds `pi mcp add|list|login`, a /mcp manager, OAuth, per-tool exposure and resources. An extension that registers /mcp REPLACES the built-in, so with mcp-loader installed pi never read mcp.json and printed "registers command /mcp, so built-in extension mcp was not loaded" on every start. Measured on pi 1.1.0 (pi-devbox v1.11.0, tor-ms22) before removing it: - time to the (auto) footer in a real pty, 7 interleaved runs each: -ne 0.276 s | -ne -e mcp-loader.ts 0.283 s | -ne -e builtin:mcp 0.272 s - with no servers, the same prompt costs 79 input + 7680 cached tokens with the loader, the built-in, or neither - a stdio server (server-everything) connected via `pi mcp list` in 3 s, and its echo tool ran end to end from `pi -p` with direct exposure - no device in the fleet had an `mcp` block, so nothing needs migrating install.sh now prunes links into this repo whose target is gone (.ts and .ts.off), so the stale mcp-loader.ts link on a preserved pi-devbox volume is removed on the first boot of an image without the file. It also warns once if settings.json still carries an `mcp` block. Tested two-sided in a temp HOME: the new script removes the repo link and a stale .ts.off, and keeps a foreign dangling link and a real file. The old script leaves the dangling link. Re-runs are idempotent, and there is no warning without an mcp block. mempalace.ts (mempalace-toolkit) is unaffected: it registers mempalace_* tools itself. Its vendored RemoteMcpClient loses its sync partner; that is handled in mempalace-toolkit.
This commit is contained in:
@@ -17,7 +17,7 @@ chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Each `.ts` file in `extensions/` is symlinked into `~/.pi/agent/extensions/`. Existing real files are backed up with a timestamp. Re-runs are idempotent.
|
||||
Each `.ts` file in `extensions/` is symlinked into `~/.pi/agent/extensions/`. Existing real files are backed up with a timestamp. Re-runs are idempotent. A link into this repo whose file is gone (an extension that was retired, like `mcp-loader.ts`) is removed.
|
||||
|
||||
## Bundled skill
|
||||
|
||||
@@ -335,72 +335,42 @@ State lives in the session's tool result `details`, not an external file. So:
|
||||
|
||||
This is a verbatim copy of the upstream `examples/extensions/todo.ts` shipped with `pi-coding-agent`. Refresh from upstream when desired (see `AGENTS.md`).
|
||||
|
||||
### `mcp-loader.ts`
|
||||
### MCP servers — use pi's built-in support (`mcp-loader.ts` is retired)
|
||||
|
||||
Generic MCP server loader. Reads an `mcp` block from `~/.pi/agent/settings.json` (same shape as opencode and Claude Desktop) and connects to each declared server, exposing all of their tools to pi as native tools — namespaced as `<server-name>_<tool-name>` to avoid collisions, with non-`[A-Za-z0-9_]` characters replaced by `_` so the names pass the strictest provider tool-name regex (e.g. AWS Bedrock).
|
||||
`mcp-loader.ts` was removed on 2026-10-12. pi has shipped MCP support as a built-in extension since 0.99.0, and it covers everything the loader did and more:
|
||||
|
||||
**Settings.json shape:**
|
||||
| | `mcp-loader.ts` (retired) | pi built-in (`builtin:mcp`) |
|
||||
|---|---|---|
|
||||
| Config | `mcp` block in `settings.json` | `~/.pi/agent/mcp.json` (`mcpServers`, Claude Desktop shape); per-project `.pi/mcp.json` after trust |
|
||||
| Manage | `/mcp` toggle overlay | `/mcp` manager (inspect tools, reconnect, sign in, enable/disable, exposure), `pi mcp add/remove/list/login/logout` from a shell |
|
||||
| Auth | pre-issued bearer token in `headers` | headers with `${ENV}` / `!command` values, **and OAuth** (dynamic registration, CIMD, refresh) |
|
||||
| Startup | awaited every server's `tools/list` while extensions loaded | connects in the background at session start; the first prompt waits (up to 10 s) only for servers with `direct` tools |
|
||||
| Tools | every tool declared to the model as `<server>_<tool>` | `mcp__<server>__<tool>`; per-server/per-tool `exposure`: `codemode` (default), `deferred`, `direct`, `hidden` |
|
||||
| Also | — | resources (`list_mcp_resources`, `read_mcp_resource`), per-request `timeout`, reconnect on next call, `mcp.log` |
|
||||
|
||||
```jsonc
|
||||
Measured before retiring it (pi 1.1.0, pi-devbox v1.11.0, 7 interleaved runs each, time to pi's `(auto)` footer in a real pty): `-ne` 0.276 s, `-ne -e mcp-loader.ts` 0.283 s, `-ne -e builtin:mcp` 0.272 s. With no servers configured, the built-in adds nothing to a request: the same prompt cost 79 input + 7680 cached tokens with either loader, and with neither. A stdio server (`npx -y @modelcontextprotocol/server-everything`) connected in 3 s via `pi mcp list` and its `echo` tool was called end to end from `pi -p`.
|
||||
|
||||
**Why remove it rather than keep both:** an extension that registers `/mcp` *replaces* the built-in, so with the loader installed pi never read `mcp.json` and printed `Extension … registers command /mcp, so built-in extension mcp was not loaded` on every start.
|
||||
|
||||
**Moving an old `mcp` block** to `~/.pi/agent/mcp.json` (pi `docs/mcp.md`, "Migrate configuration from another client"):
|
||||
|
||||
```json
|
||||
{
|
||||
// … existing pi settings …
|
||||
"mcp": {
|
||||
"searxng": {
|
||||
"type": "local",
|
||||
"command": ["uvx", "mcp-searxng"],
|
||||
"env": { "SEARXNG_URL": "https://searxng.your-host.lan" }
|
||||
},
|
||||
"gitea": {
|
||||
"type": "local",
|
||||
"command": ["gitea-mcp", "-t", "stdio"],
|
||||
"enabled": false,
|
||||
"env": { "GITEA_ACCESS_TOKEN": "...", "GITEA_HOST": "https://gitea.example.com" }
|
||||
},
|
||||
"context7": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.context7.com/mcp"
|
||||
}
|
||||
"mcpServers": {
|
||||
"searxng": { "command": "uvx", "args": ["mcp-searxng"], "env": { "SEARXNG_URL": "https://searxng.your-host.lan" }, "exposure": "direct" },
|
||||
"context7": { "url": "https://mcp.context7.com/mcp", "headers": { "Authorization": "Bearer ${CONTEXT7_TOKEN}" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Per-server keys:**
|
||||
- `"type": "local"` + `"command": [exe, ...args]` becomes `"command": exe` + `"args": [...]`. `"type": "remote"` becomes a `url` entry.
|
||||
- Set `"exposure": "direct"` to keep the loader's behaviour of declaring every tool to the model. The built-in default, `codemode`, makes tools reachable only from `codemode` scripts.
|
||||
- Tool names change from `<server>_<tool>` to `mcp__<server>__<tool>`. Non-`[A-Za-z0-9_]` characters still become `_`, so the names stay valid for AWS Bedrock.
|
||||
- `pi mcp list` validates the file and connects every enabled server.
|
||||
|
||||
| Key | Description |
|
||||
|---|---|
|
||||
| `type` | `"local"` (stdio subprocess) or `"remote"` (streamable-http). Default `"local"`. |
|
||||
| `command` | Argv array. First element is the executable, rest are args. Local servers only. |
|
||||
| `url` | Remote MCP endpoint URL. Remote servers only. |
|
||||
| `headers` | Optional object of HTTP headers (e.g. `Authorization`, `X-API-Key`) sent with every request. Remote servers only. |
|
||||
| `enabled` | Default `true`. Set `false` to disable a server without removing the entry. |
|
||||
| `env` | Optional object of env vars injected into the subprocess. Inherits parent env first, then overlays these keys. Local servers only. |
|
||||
`install.sh` now removes the stale `~/.pi/agent/extensions/mcp-loader.ts` link (and any other link into this repo whose file is gone). It warns once if `settings.json` still has an `mcp` block.
|
||||
|
||||
**Transports:**
|
||||
|
||||
- `local` — stdio JSON-RPC subprocess (mcp-searxng, gitea-mcp, mcp-server-time…).
|
||||
- `remote` — streamable-HTTP per MCP spec 2025-03-26: POST JSON-RPC, server replies either `application/json` or `text/event-stream`. Optional `Mcp-Session-Id` round-trip if the server issues one. No GET subscription stream (server-initiated notifications are not consumed).
|
||||
|
||||
**Limitations:**
|
||||
|
||||
- **No stdio reconnect** if a subprocess dies mid-session — those tools become unavailable until `/reload` (same as `mempalace.ts`).
|
||||
- **Remote sessions self-heal on 404.** If a streamable-HTTP server forgets our session id (e.g. server restart), the client transparently re-initializes and retries the request once.
|
||||
- **No OAuth flow.** Remote servers requiring OAuth must be accessed with a pre-issued bearer token via `headers`.
|
||||
- **Coexists with `mempalace.ts`** but does not replace it. The mempalace bridge has bespoke handling (agent identity injection) that's worth keeping. Don't list `mempalace` in the `mcp` block too — you'd get duplicate tool registrations.
|
||||
|
||||
**Debug:** set `PI_MCP_LOADER_DEBUG=1` in the environment to surface per-server stderr and connection logs.
|
||||
|
||||
**Slash command:** `/mcp` opens a multi-toggle overlay listing every server in the `mcp` block with its runtime status:
|
||||
|
||||
- `running · N tools` — connected, tools registered
|
||||
- `failed: <message>` — start handshake threw
|
||||
- `disabled in settings` — `enabled: false`
|
||||
- `invalid: <message>` — malformed config (read-only row)
|
||||
|
||||
UX matches `/ext`: **space** stages a toggle, **enter** writes back to `settings.json` and reloads pi, **esc** cancels. Toggling re-enables a previously-disabled server by removing the explicit `enabled` key (the default is `true`).
|
||||
|
||||
Each extension is a TypeScript module loaded by [jiti](https://github.com/unjs/jiti) — no compilation step. See the [pi extensions docs](https://github.com/earendil-works/pi/blob/main/docs/extensions.md) and the [built-in examples](https://github.com/earendil-works/pi/tree/main/examples/extensions) for the API surface.
|
||||
|
||||
---
|
||||
**MemPalace is unaffected.** `mempalace.ts` (mempalace-toolkit) runs its own MCP client and registers `mempalace_*` tools directly, so `exposure` never applies to them. Don't add MemPalace to `mcp.json`: you would get a second, `mcp__mempalace__*`, copy of every tool.
|
||||
|
||||
### `aws-sso.ts`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user