MCP via pi's built-in support: mcp-loader retired upstream; skill floor, docs, sanity check
Lint / skill-floor (push) Successful in 11s
Lint / hadolint (push) Successful in 15s
Lint / actionlint (push) Successful in 20s
Lint / doc-drift (push) Successful in 17s

pi-extensions 750b2a8 retired mcp-loader.ts in favour of builtin:mcp (an
extension registering /mcp replaced the built-in and printed a warning on
every start). This repo has no pin to move, since PI_EXTENSIONS_REF and
MEMPALACE_TOOLKIT_REF float on main, so this commit carries what the next
build changes:

- CHANGELOG Unreleased: component table (pi-extensions a88fa99 -> 750b2a8,
  mempalace-toolkit 975ab92 -> a30af61), the switch and its measurements, the
  stale-link pruning, the new sanity check, and the floor refresh.
  check-doc-drift.sh rc=0. A two-sided check: with 750b2a8 blanked out of the
  CHANGELOG it fails rc=1, naming pi-extensions a88fa99 -> 750b2a8.
- rootfs pi-extensions skill floor refreshed to 750b2a8. check-skill-floor.sh
  was rc=1 before and is rc=0 after. Base hash changes, so the next tag
  rebuilds the base.
- recreate-sanity-check.sh: "no dangling extension links" (WARN). The
  existing count includes dangling links because ls lists them. Tested
  two-sided in a temp HOME (PASS clean; WARN naming mcp-loader.ts and x.ts.off).
  On tor-ms22 v1.11.0: rc=0, 36 pass / 3 warn / 0 fail (was 35/3/0).
- DOCKER_HUB.md: pi-extensions list was three releases stale; it now lists the
  10 that ship, plus an MCP line. Dockerfile.variant comment updated.
- lint-shell.sh: 19 files clean at severity error.
This commit is contained in:
2026-10-13 07:42:23 +02:00
parent 4ee94da523
commit 3da7312166
5 changed files with 74 additions and 4 deletions
+47
View File
@@ -11,6 +11,53 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
--- ---
## Unreleased
### Components that move with the next build
| Component | Baked in v1.11.0 | Next build | Why |
|---|---|---|---|
| pi-extensions | `a88fa99` | **`750b2a8`** (or later: CI resolves `main` at build time) | `mcp-loader.ts` retired; `install.sh` prunes stale links (below) |
| mempalace-toolkit | `975ab92` | **`a30af61`** (or later) | comment/README only: the bridge's `RemoteMcpClient` is now the only copy; `scripts/check-mcp-client-sync.sh` removed. No code change |
### Changed
- **MCP servers now use pi's built-in MCP support; pi-extensions'
`mcp-loader.ts` is retired.** pi has shipped MCP as a built-in extension
(`builtin:mcp`) since 0.99.0: servers go in `~/.pi/agent/mcp.json` or are
added with `pi mcp add`, and `/mcp` manages them (tools, reconnect, OAuth
sign-in, enable/disable, exposure). Because an extension that registers
`/mcp` *replaces* the built-in, every start on v1.11.0 printed
`Extension … mcp-loader.ts registers command /mcp, so built-in extension mcp
was not loaded`, and `mcp.json` was never read. Measured on tor-ms22
(v1.11.0, pi 1.1.0) before the switch: time to ready, 7 interleaved pty runs
each, was 0.276 s with no MCP, 0.283 s with the loader and 0.272 s with the
built-in. A prompt cost the same tokens with either and with neither, and a
stdio server (`server-everything`) connected and answered a tool call
through the built-in. No device had servers in the loader's `mcp` block, so
nothing needs migrating. If one does, pi-extensions' README ("MCP servers")
shows how to move it to `mcp.json`. Tools from `mcp.json` default to
`codemode` exposure; set `"exposure": "direct"` to declare them to the model.
MemPalace is unaffected: `mempalace.ts` registers `mempalace_*` itself.
- **A preserved volume drops the stale link by itself.** `~/.pi/agent/` survives
a recreate, so the old `extensions/mcp-loader.ts ->
/opt/pi-extensions/extensions/mcp-loader.ts` link would outlive the file. The
entrypoint's pi-extensions `install.sh --yes` now removes links into its own
repo whose target is gone (`.ts` and `/ext`-disabled `.ts.off`). It warns once
if `settings.json` still has an `mcp` block.
- **`recreate-sanity-check.sh`: one new check, "no dangling extension links".**
The existing "N extensions deployed" line counts a dangling link as deployed
(`ls` lists it), so it cannot see this case. The new check WARNs instead of
FAILing, because whatever it still finds points outside pi-extensions, which
`install.sh` leaves alone. Expect one more ✓ than v1.11.0 printed.
- **Vendored `pi-extensions` skill floor refreshed** to pi-extensions `750b2a8`.
`check-skill-floor.sh` went red on the retirement commit and is green again.
The skill now says MCP servers are the built-in's, not an extension. Because
`rootfs/` is in the base hash, the next tag rebuilds the base.
- DOCKER_HUB.md's pi-extensions list was three releases stale (`mcp-loader`
listed; `task`, `fork-gate`, `secret-guard`, `aws-sso` missing). It now lists
the 10 that ship and has an MCP line.
## v1.11.0 — 2026-10-12 ## v1.11.0 — 2026-10-12
Minor release. Headlines: **pi 1.1.0**, **secret env values are kept out of Minor release. Headlines: **pi 1.1.0**, **secret env values are kept out of
+2 -1
View File
@@ -49,7 +49,8 @@ Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini
- **pi `{{PI_VERSION}}`** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — installed at `/usr/bin/pi`, pinned to an audited version (not npm `latest`) - **pi `{{PI_VERSION}}`** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — installed at `/usr/bin/pi`, pinned to an audited version (not npm `latest`)
- **pi-atelier** — TUI sidebar (ordered panels, split-pane, themes), vendored at `/opt/pi-atelier` and pinned to an audited tag; the exact tag is in the image labels (`se.jordbo.pi-devbox.pi-atelier-version`) and `/etc/pi-devbox/build-manifest.json` - **pi-atelier** — TUI sidebar (ordered panels, split-pane, themes), vendored at `/opt/pi-atelier` and pinned to an audited tag; the exact tag is in the image labels (`se.jordbo.pi-devbox.pi-atelier-version`) and `/etc/pi-devbox/build-manifest.json`
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template - **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive` - **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 10 user-facing extensions: `ext-toggle`, `todo`, `task`, `fork-gate`, `secret-guard`, `aws-sso`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
- **MCP servers** — pi's built-in MCP support: declare servers in `~/.pi/agent/mcp.json` (or `pi mcp add`), manage them with `/mcp`
- **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools - **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools
- **mempalace bridge** — MCP extension auto-symlinked so pi reads/writes the host-mounted palace - **mempalace bridge** — MCP extension auto-symlinked so pi reads/writes the host-mounted palace
- **image-baked agent skills** — skills under `/usr/local/share/pi-devbox/skills/` (e.g. `pi-devbox-environment`, which teaches agents the container's persistence/networking/DNS/tmux/REPL specifics) are symlinked into `~/.agents/skills/` on start, available with or without a mounted skillset repo - **image-baked agent skills** — skills under `/usr/local/share/pi-devbox/skills/` (e.g. `pi-devbox-environment`, which teaches agents the container's persistence/networking/DNS/tmux/REPL specifics) are symlinked into `~/.agents/skills/` on start, available with or without a mounted skillset repo
+4 -2
View File
@@ -212,9 +212,11 @@ ARG USER_NAME=developer
# `exposure` is `codemode`, i.e. such tools are "neither declared to the model # `exposure` is `codemode`, i.e. such tools are "neither declared to the model
# nor listed" and must be found with `searchTools()`. That would gut the # nor listed" and must be found with `searchTools()`. That would gut the
# MemPalace protocol if MemPalace were a builtin-MCP server. It is not: both # MemPalace protocol if MemPalace were a builtin-MCP server. It is not: both
# mempalace.ts and mcp-loader.ts run their OWN MCP client and surface tools via # mempalace.ts runs its OWN MCP client and surfaces tools via
# `pi.registerTool()`, which is why they are named `mempalace_search` and not # `pi.registerTool()`, which is why they are named `mempalace_search` and not
# `mcp__mempalace__search`. Confirmed on a second route — settings.json has # `mcp__mempalace__search`. (So did pi-extensions' mcp-loader.ts until it was
# retired after v1.11.0 in favour of builtin:mcp. Servers in ~/.pi/agent/mcp.json
# ARE exposure-governed: they need `"exposure": "direct"` to be declared.) Confirmed on a second route — settings.json has
# neither an `mcpServers` block (builtin, exposure-governed) nor an `mcp` block. # neither an `mcpServers` block (builtin, exposure-governed) nor an `mcp` block.
# Extension-registered tools are declared like built-ins, so `exposure` cannot # Extension-registered tools are declared like built-ins, so `exposure` cannot
# reach them. # reach them.
@@ -23,11 +23,13 @@ Pi has **two distinct extension locations** and it's easy to look in the wrong o
| Location | Mechanism | Examples | | Location | Mechanism | Examples |
|---|---|---| |---|---|---|
| `~/.pi/agent/extensions/*.ts` (or `.ts.off`) | **Local extensions** — TypeScript files, usually symlinks into `/opt/pi-extensions/extensions/` or similar. Toggled via `/ext` slash command. | `ssh-controlmaster`, `git-checkpoint`, `notify`, `todo`, `mempalace`, `mcp-loader`, `ext-toggle`, `confirm-destructive` | | `~/.pi/agent/extensions/*.ts` (or `.ts.off`) | **Local extensions** — TypeScript files, usually symlinks into `/opt/pi-extensions/extensions/` or similar. Toggled via `/ext` slash command. | `ssh-controlmaster`, `git-checkpoint`, `notify`, `todo`, `mempalace`, `ext-toggle`, `task`, `fork-gate`, `secret-guard`, `aws-sso`, `confirm-destructive` |
| `~/.pi/agent/git/<host>/<owner>/<repo>/` | **Package extensions (git-installed)** — git-cloned npm packages registered via the `packages` array in `~/.pi/agent/settings.json`. | `pi-fork` (`github.com/elpapi42/pi-fork`), `pi-observational-memory` (`github.com/elpapi42/pi-observational-memory`, **default branch `master`** — a `main` branch does not exist, so `pi install git:...` resolves against `master`) | | `~/.pi/agent/git/<host>/<owner>/<repo>/` | **Package extensions (git-installed)** — git-cloned npm packages registered via the `packages` array in `~/.pi/agent/settings.json`. | `pi-fork` (`github.com/elpapi42/pi-fork`), `pi-observational-memory` (`github.com/elpapi42/pi-observational-memory`, **default branch `master`** — a `main` branch does not exist, so `pi install git:...` resolves against `master`) |
| `~/.pi/agent/npm/node_modules/<pkg>/` | **Package extensions (npm-installed)** — `pi install npm:<pkg>`; recorded in `packages[]` as `npm:<pkg>`. | `pi-atelier` (status rail + sidebar TUI) | | `~/.pi/agent/npm/node_modules/<pkg>/` | **Package extensions (npm-installed)** — `pi install npm:<pkg>`; recorded in `packages[]` as `npm:<pkg>`. | `pi-atelier` (status rail + sidebar TUI) |
| `/opt/<pkg>/` — **pi-devbox containers only** | **Vendored package extensions** — cloned into an image layer at build time with `node_modules` baked, then registered at container start by `entrypoint-user.sh` via `pi install /opt/<pkg>`. Recorded in `packages[]` as a **relative** path (`../../../../opt/pi-fork`) that resolves out of `~/.pi/agent` into the image layer, so it survives volume recreate. | `/opt/pi-fork`, `/opt/pi-observational-memory`, `/opt/pi-studio` | | `/opt/<pkg>/` — **pi-devbox containers only** | **Vendored package extensions** — cloned into an image layer at build time with `node_modules` baked, then registered at container start by `entrypoint-user.sh` via `pi install /opt/<pkg>`. Recorded in `packages[]` as a **relative** path (`../../../../opt/pi-fork`) that resolves out of `~/.pi/agent` into the image layer, so it survives volume recreate. | `/opt/pi-fork`, `/opt/pi-observational-memory`, `/opt/pi-studio` |
**MCP servers are not an extension here.** pi's built-in MCP support (`builtin:mcp`) reads `~/.pi/agent/mcp.json`; manage it with `/mcp` or `pi mcp add|list`. Its tools are named `mcp__<server>__<tool>` and default to `codemode` exposure (not declared to the model) unless the server sets `"exposure": "direct"`. The `mempalace_*` tools are different: `mempalace.ts` registers them itself, so exposure never applies. The old `mcp-loader.ts` (an `mcp` block in `settings.json`) was retired on 2026-10-12, because an extension that registers `/mcp` replaces the built-in.
When the user asks how to use "the X extension", **check all of these** — `find ~/.pi/agent -maxdepth 4 -name "*X*"` covers the first three, and `ls -d /opt/*X*` the fourth. The `/ext` slash command shows the local-extensions list with enable/disable state. There is also a distinct skill-bundled-script category (e.g. `ci-release-watcher`'s `ssh-control-master-setup.sh`) which is **not** a pi extension at all — it's a helper script inside a skill. Don't conflate the three. When the user asks how to use "the X extension", **check all of these** — `find ~/.pi/agent -maxdepth 4 -name "*X*"` covers the first three, and `ls -d /opt/*X*` the fourth. The `/ext` slash command shows the local-extensions list with enable/disable state. There is also a distinct skill-bundled-script category (e.g. `ci-release-watcher`'s `ssh-control-master-setup.sh`) which is **not** a pi extension at all — it's a helper script inside a skill. Don't conflate the three.
**In a pi-devbox container, do not conclude "pi-fork isn't installed" because `~/.pi/agent/git/` is empty.** It is deliberately absent: `Dockerfile.variant` vendors to `/opt` and installs by local path, because a build-time `pi install git:...` would write into `~/.pi/agent`, which the named volume then shadows on first run. **In a pi-devbox container, do not conclude "pi-fork isn't installed" because `~/.pi/agent/git/` is empty.** It is deliberately absent: `Dockerfile.variant` vendors to `/opt` and installs by local path, because a build-time `pi install git:...` would write into `~/.pi/agent`, which the named volume then shadows on first run.
+18
View File
@@ -303,6 +303,24 @@ else
fail "only $EXT_COUNT extensions deployed (expected ≥4)" fail "only $EXT_COUNT extensions deployed (expected ≥4)"
fi fi
# no dangling extension links. The count above includes them (ls lists a link
# whether or not its target exists) but they load nothing, and pi skips them
# without a word. That is exactly how a retired extension outlives its image on
# a preserved volume: mcp-loader.ts (retired after v1.11.0) left
# ~/.pi/agent/extensions/mcp-loader.ts -> /opt/pi-extensions/... behind. The
# pi-extensions install.sh run by the entrypoint now prunes links into ITS repo;
# anything this still finds points somewhere else, hence WARN, not FAIL.
_dangling=""
for _e in "$HOME"/.pi/agent/extensions/*.ts "$HOME"/.pi/agent/extensions/*.ts.off; do
if [ -L "$_e" ] && [ ! -e "$_e" ]; then _dangling="$_dangling $(basename "$_e")"; fi
done
if [ -z "$_dangling" ]; then
pass "no dangling extension links in ~/.pi/agent/extensions"
else
warn "dangling extension links (target gone, pi ignores them):$_dangling"
fi
unset _dangling _e
# mempalace.ts bridge symlink # mempalace.ts bridge symlink
if [ -L "$HOME/.pi/agent/extensions/mempalace.ts" ]; then if [ -L "$HOME/.pi/agent/extensions/mempalace.ts" ]; then
pass "~/.pi/agent/extensions/mempalace.ts bridge symlink" pass "~/.pi/agent/extensions/mempalace.ts bridge symlink"