pi-session-mcp 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.pi-session-mcp-cli/config.js +11 -1
  2. package/.pi-session-mcp-cli/doctor.js +22 -1
  3. package/.pi-session-mcp-cli/execution-profile.js +1 -0
  4. package/.pi-session-mcp-cli/extension-spec.js +21 -0
  5. package/.pi-session-mcp-cli/pi-extensions.js +67 -0
  6. package/CHANGELOG.md +37 -0
  7. package/CONTRIBUTING.md +1 -1
  8. package/README.md +2 -2
  9. package/SECURITY.md +1 -1
  10. package/dist/capabilities.d.ts +1 -0
  11. package/dist/capabilities.js +2 -1
  12. package/dist/client-adapters/mcp-verifier.js +5 -2
  13. package/dist/config.d.ts +1 -0
  14. package/dist/config.js +11 -1
  15. package/dist/doctor.d.ts +4 -1
  16. package/dist/doctor.js +22 -1
  17. package/dist/execution-profile.d.ts +3 -0
  18. package/dist/execution-profile.js +1 -0
  19. package/dist/extension-spec.d.ts +15 -0
  20. package/dist/extension-spec.js +21 -0
  21. package/dist/main.js +6 -2
  22. package/dist/pi-adapter.d.ts +1 -1
  23. package/dist/pi-adapter.js +4 -0
  24. package/dist/pi-extensions.d.ts +17 -0
  25. package/dist/pi-extensions.js +67 -0
  26. package/dist/sdk-pi-adapter.d.ts +24 -6
  27. package/dist/sdk-pi-adapter.js +253 -86
  28. package/dist/server.js +1 -0
  29. package/dist/session-tools.d.ts +9 -0
  30. package/dist/session-tools.js +25 -0
  31. package/dist/setup-command-guardian.js +29 -73
  32. package/dist/setup-main.js +2 -1
  33. package/dist/setup-ownership.js +18 -28
  34. package/dist/setup-platform.d.ts +57 -0
  35. package/dist/setup-platform.js +195 -0
  36. package/dist/setup-process.d.ts +6 -3
  37. package/dist/setup-process.js +85 -93
  38. package/dist/setup.js +2 -1
  39. package/dist/stdout-guard-install.d.ts +5 -0
  40. package/dist/stdout-guard-install.js +6 -0
  41. package/dist/stdout-guard.d.ts +20 -0
  42. package/dist/stdout-guard.js +72 -0
  43. package/docs/acceptance.md +216 -0
  44. package/docs/adr/0005-macos-setup-platform.md +84 -0
  45. package/docs/adr/0006-pi-extension-allowlist.md +58 -0
  46. package/docs/architecture.md +1 -1
  47. package/docs/client-setup-design.md +21 -9
  48. package/docs/client-setup.md +25 -10
  49. package/docs/doctor-design.md +7 -2
  50. package/docs/installation.md +122 -4
  51. package/docs/macos-setup-design.md +386 -0
  52. package/docs/pi-extensions-design.md +421 -0
  53. package/docs/releasing.md +1 -1
  54. package/docs/superpowers/plans/2026-10-10-macos-setup.md +1725 -0
  55. package/docs/superpowers/plans/2026-10-11-pi-extensions.md +2450 -0
  56. package/docs/threat-model.md +54 -18
  57. package/docs/tool-contracts.md +8 -3
  58. package/package.json +1 -1
@@ -0,0 +1,84 @@
1
+ # ADR 0005: setup supports macOS with the same fence as on Linux
2
+
3
+ **Status:** Accepted (2026-10-10). macOS counts as accepted only after
4
+ Level-2 setup acceptance on real hardware; see [Acceptance](../acceptance.md).
5
+ That acceptance is recorded for Claude Code (2026-10-11); Codex is still pending.
6
+
7
+ ## Context
8
+
9
+ `setup` refused every operation on macOS with `platform_unsupported`. #28
10
+ reported this from a Mac: Doctor and a manual registration worked, but
11
+ `--verify` and the ownership lifecycle were unavailable. ADR 0003 names
12
+ repeated setup friction beyond version drift as a trigger to revisit this
13
+ area.
14
+
15
+ The setup fence relied on three Linux facts:
16
+
17
+ - util-linux `flock` locks the open file description of the ownership lock
18
+ file, and the transaction guardian inherits it;
19
+ - a `/proc` scan proves that no non-zombie member of a client command's
20
+ process group remains before the fence is released;
21
+ - `statfs` magic numbers identify the local filesystems on which lock and
22
+ fsync semantics are accepted.
23
+
24
+ None of them carries over: macOS ships no `flock(1)` and no `/proc`, and its
25
+ `statfs` `f_type` is a dynamically assigned VFS index rather than a stable
26
+ magic number.
27
+
28
+ ## Decision
29
+
30
+ `setup` supports Linux and macOS. Every other platform still fails closed with
31
+ `platform_unsupported` before any client command is spawned.
32
+
33
+ `src/setup-platform.ts` owns the operating-system facts behind the fence. Its
34
+ macOS primitives are:
35
+
36
+ - **Lock.** The lock file is opened with `O_EXLOCK | O_NONBLOCK`. XNU takes a
37
+ flock(2)-semantics lock on the open file description at open time. A busy
38
+ lock is retried every 50 ms within the existing acquisition bound, and the
39
+ guardian inherits the locked description as on Linux.
40
+ - **Process-group proof.** The trusted system `/bin/ps -A -o pid=,pgid=,stat=`
41
+ is parsed strictly. A scan that fails, times out, has an unexpected line or
42
+ does not list the caller is `unknown`, and `unknown` retains the fence.
43
+ - **Filesystem.** The ownership directory must have the same `statfs` type
44
+ as `/`, which is the APFS system volume on every macOS that Node.js 22
45
+ supports. Any other type fails closed.
46
+
47
+ Public result codes, statuses, exit codes and the result schema do not
48
+ change. CI runs on Linux, macOS 15 and macOS 26. A `verify` job aggregates
49
+ the matrix, so the required status check keeps its name.
50
+
51
+ ## Consequences
52
+
53
+ - Operators on macOS get the same lifecycle, ownership records and
54
+ verification as on Linux, including `--verify` for a registration that
55
+ `setup` made.
56
+ - On macOS, the proof that no live group member remains rests on `/bin/ps`,
57
+ which sits on the sealed system volume, plus the uid and mode check that
58
+ already guarded `flock`. As on Linux, it does not cover descendants that
59
+ escape the process group, and it does not promise PID reaping.
60
+ - An ownership directory on HFS+, exFAT, a network volume or any other
61
+ non-APFS filesystem fails closed on macOS.
62
+ - Intel Macs run the same code path but are not in the CI matrix.
63
+ - The guardian and the process runner load the platform module with a
64
+ dynamic import, because source-mode tests run them through Node type
65
+ stripping. `setup-platform.js` is therefore a mandatory release artifact
66
+ next to `main.js` and `setup-command-guardian.js`.
67
+
68
+ ## Alternatives considered
69
+
70
+ - **A `lockf(1)` helper and `/sbin/mount` parsing.** This would have mirrored
71
+ the Linux shape. Rejected: the descriptor mode of `lockf` on macOS 15 and
72
+ the `mount` output format are unverified.
73
+ - **A weaker macOS profile without the kernel fence or the process-group
74
+ proof.** Rejected: it would break the guarantee that a later setup
75
+ transaction cannot race an orphaned client command.
76
+ - **`kill(-pgid, 0)` as the process-group proof.** Rejected: the XNU source
77
+ handles groups that contain only zombies inconsistently.
78
+
79
+ ## Revisit triggers
80
+
81
+ - The `ps` output format or its `stat` letters change.
82
+ - The `O_EXLOCK` semantics of open(2) change.
83
+ - The minimum macOS version that Node.js supports changes.
84
+ - A request to add Intel Macs to CI, or to support another platform.
@@ -0,0 +1,58 @@
1
+ # ADR 0006: Execution profiles may load pinned Pi extensions
2
+
3
+ **Status:** Accepted (2026-10-11). Issue #34. Design:
4
+ [Pi extensions design](../pi-extensions-design.md).
5
+
6
+ ## Context
7
+
8
+ Embedded sessions loaded no Pi extensions. Providers that exist only as Pi
9
+ extensions, such as `cursor` from `pi-cursor-sdk`, were unusable through Pi
10
+ Session MCP although Pi had them installed and authenticated.
11
+
12
+ ADR 0003 chose maintenance without a new feature phase. Unlike ADR 0005,
13
+ which followed a fired ADR 0003 revisit trigger, this is a scope expansion
14
+ the owner explicitly approved (2026-10-11, issue #34).
15
+
16
+ ## Decision
17
+
18
+ An execution profile may name Pi extensions as
19
+ `npm:<name>@<exact version>`.
20
+ - The server resolves each one from Pi's own user install, normally
21
+ `~/.pi/agent/npm/node_modules/<name>` from `pi install npm:<name>`, and never
22
+ installs anything. A package installed anywhere else, for example with
23
+ `npm i -g`, counts as not installed.
24
+ - A missing package or another installed version fails closed before a session
25
+ exists.
26
+ - An extension loads with its full capabilities: providers, tools, hooks and
27
+ system-prompt changes.
28
+ - In `read-only` profiles the model's tools stay `read`, `grep`, `find` and
29
+ `ls`.
30
+ - In `coding` profiles every extension tool may be active.
31
+ - An extension tool name that collides with another tool fails closed in both
32
+ profiles.
33
+
34
+ A profile with extensions uses the Pi SDK session-lifecycle API with its own
35
+ model runtime per session:
36
+ - `createAgentSessionServices`, then the existing verification, then
37
+ `createAgentSessionFromServices` and `bindExtensions`.
38
+ - Close goes through `AgentSessionRuntime.dispose()`, which emits
39
+ `session_shutdown`.
40
+
41
+ Profiles without extensions keep the previous path. Client prompts are plain
42
+ text for every profile: they never dispatch extension commands, skills or
43
+ templates. An always-on stdout guard gives the MCP transport the only real
44
+ stdout writer.
45
+
46
+ ## Consequences
47
+
48
+ - An allowlisted extension is trusted operator code with the user's full
49
+ rights inside the server process. Version pinning prevents silent updates,
50
+ not tampering with the installed package.
51
+ - Extensions may persist their own state; Pi Session MCP itself stays
52
+ in-memory.
53
+ - A child process that an extension starts with inherited stdio can still
54
+ write to the MCP stream directly. This is a documented residual risk.
55
+ - Extension tool names are checked for conflicts after loading and again after
56
+ binding. A tool that an extension registers later, during a turn, is not
57
+ re-checked, because allowlisted extensions are trusted code.
58
+ - Doctor checks configured extensions offline by reading their manifests.
@@ -79,4 +79,4 @@ Public errors are stable and sanitized. They omit caller values, complete prompt
79
79
 
80
80
  ## Scope
81
81
 
82
- The product boundary is local stdio and in-memory-only operation for one trusted local MCP peer. A controller keeps one immutable configuration/profile snapshot for its lifetime; file edits require a genuinely new controller process and do not hot-reload, mark stale, or mutate running sessions. Each new session performs external catalog discovery within the loaded grants, so catalog changes do not require a controller restart, but a running session's active tools remain fixed. Restarting the controller loses all sessions; reconnecting a client reloads configuration only when it actually starts a new controller process. Capability discovery reports captured metadata only and does not prove current-file freshness, server reachability, provider availability, or catalog state. It excludes HTTP, a web UI, persistence/resume, remote access, multi-user behavior, transcript export, streaming, live provider/model discovery, availability probes during capability discovery, and provider credential management. A coding execution profile is an application-level tool selection, not an OS sandbox.
82
+ The product boundary is local stdio and in-memory-only operation for one trusted local MCP peer. Allowlisted Pi extensions may persist state of their own outside this boundary; Pi Session MCP itself keeps no state beyond process memory. A controller keeps one immutable configuration/profile snapshot for its lifetime; file edits require a genuinely new controller process and do not hot-reload, mark stale, or mutate running sessions. Each new session performs external catalog discovery within the loaded grants, so catalog changes do not require a controller restart, but a running session's active tools remain fixed. Restarting the controller loses all sessions; reconnecting a client reloads configuration only when it actually starts a new controller process. Capability discovery reports captured metadata only and does not prove current-file freshness, server reachability, provider availability, or catalog state. It excludes HTTP, a web UI, persistence/resume, remote access, multi-user behavior, transcript export, streaming, live provider/model discovery, availability probes during capability discovery, and provider credential management. A coding execution profile is an application-level tool selection, not an OS sandbox.
@@ -22,22 +22,34 @@ Codex supports only `codex:user:pi-session-mcp`. Claude Code supports `user`,
22
22
  registration output, or scope outside the recorded contracts is `unsupported`,
23
23
  and the client version is reported for information only. The adapters do not
24
24
  parse undocumented client config files.
25
- Setup is currently supported on Linux with a root-owned, non-writable
26
- util-linux `flock` at `/usr/bin/flock` or `/bin/flock`, readable `/proc`, and an
27
- ownership directory on ext2/3/4, XFS, Btrfs, tmpfs, overlayfs, ZFS, F2FS,
28
- UBIFS, or bcachefs. Other filesystems fail closed before lock acquisition.
29
- Every non-Linux platform fails closed with `platform_unsupported` before any
30
- client command is spawned.
25
+ Setup is supported on Linux and macOS
26
+ ([ADR 0005](adr/0005-macos-setup-platform.md)); `src/setup-platform.ts` owns
27
+ the operating-system facts behind the fence.
28
+
29
+ - **Linux:**
30
+ - a root-owned, non-writable util-linux `flock` at `/usr/bin/flock` or
31
+ `/bin/flock`;
32
+ - readable `/proc`;
33
+ - an ownership directory on ext2/3/4, XFS, Btrfs, tmpfs, overlayfs, ZFS,
34
+ F2FS, UBIFS, or bcachefs.
35
+ - **macOS:**
36
+ - the root-owned, non-writable system `/bin/ps`;
37
+ - an ownership directory with the same `statfs` type as `/`, which is the
38
+ APFS system volume on every macOS that Node.js 22 supports. macOS assigns
39
+ `f_type` dynamically, so no fixed number is compared.
40
+
41
+ Other filesystems fail closed before lock acquisition. Every other platform
42
+ fails closed with `platform_unsupported` before any client command is spawned.
31
43
 
32
44
  ## Ownership and transactions
33
45
 
34
46
  Each adapter exposes inspection, apply, verify, and remove operations. Apply first inspects and refuses divergence; equivalent registrations are left unchanged. Before adding an absent registration, setup records a path-free `pending` transaction containing only the target, an opaque fingerprint, the previous `absent` state, and a transaction identifier. Post-apply inspection promotes it to `owned`. Rollback/remove require owned metadata and the same effective fingerprint. Any mismatch fails closed and leaves the registration untouched.
35
47
 
36
- The production bridge derives Node from `process.execPath` and the entry point from the package root; configuration comes only from `PI_SESSION_MCP_CONFIG`. These values are internal command arguments and never CLI arguments, reports, or metadata. The opaque ownership fingerprint also binds `package.json`, the lockfile when the installation has one, and every built runtime JavaScript module. Both `main.js` and the regular, non-symlink `setup-command-guardian.js` are mandatory release artifacts; any runtime symlink fails closed. A package/lock mismatch or later build-content drift therefore fails closed instead of silently reusing ownership. Public client commands run through a bounded `CommandRunner` (timeout, process-group termination, and output cap); undocumented config files are not parsed.
48
+ The production bridge derives Node from `process.execPath` and the entry point from the package root; configuration comes only from `PI_SESSION_MCP_CONFIG`. These values are internal command arguments and never CLI arguments, reports, or metadata. The opaque ownership fingerprint also binds `package.json`, the lockfile when the installation has one, and every built runtime JavaScript module. `main.js` and the regular, non-symlink `setup-command-guardian.js` and `setup-platform.js` are mandatory release artifacts; any runtime symlink fails closed. A package/lock mismatch or later build-content drift therefore fails closed instead of silently reusing ownership. Public client commands run through a bounded `CommandRunner` (timeout, process-group termination, and output cap); undocumented config files are not parsed.
37
49
 
38
- The production CLI uses an atomic durable store with restrictive filesystem modes; the in-memory store exists only for library tests. Neither form persists paths, environment values, secrets, prompts, SDK errors, or auth metadata. On Linux, a stable, never-renamed lock file is held through a kernel `flock` for the complete inspect → pending → client mutation → owned transaction. Newly created ownership directories and every atomic state rename are fsynced through their parent directory before success. There is no clock-based stale recovery: the kernel releases the lock only when every holder of the locked open-file description exits. Missing or untrusted `flock`, an unsupported filesystem, a symlink/non-regular lock file, or an inode mismatch fails closed. The trusted same-UID operator must not unlink or replace the stable lock path while setup is running; setup checks its inode before acquisition, after acquisition, and before exposing transaction access, but same-UID filesystem tampering is outside the fence's trust boundary. A later run reconciles `pending` state: a matching equivalent registration is promoted to owned, an absent registration is safely retried, and any divergence fails closed.
50
+ The production CLI uses an atomic durable store with restrictive filesystem modes; the in-memory store exists only for library tests. Neither form persists paths, environment values, secrets, prompts, SDK errors, or auth metadata. A stable, never-renamed lock file is held through a kernel lock for the complete inspect → pending → client mutation → owned transaction. On Linux, the trusted util-linux `flock` locks the open file description that setup opened. On macOS, setup opens the lock file with `O_EXLOCK | O_NONBLOCK`, which takes a lock with flock(2) semantics on the open file description at open time; a busy lock is retried every 50 ms within the same acquisition bound. Newly created ownership directories and every atomic state rename are fsynced through their parent directory before success. There is no clock-based stale recovery: the kernel releases the lock only when every holder of the locked open-file description exits. A missing or untrusted `flock` or `ps`, an unsupported filesystem, a symlink/non-regular lock file, or an inode mismatch fails closed. The trusted same-UID operator must not unlink or replace the stable lock path while setup is running; setup compares the inode of the open lock file with the path after opening it, again once the fence is held, and before exposing transaction access, but same-UID filesystem tampering is outside the fence's trust boundary. A later run reconciles `pending` state: a matching equivalent registration is promoted to owned, an absent registration is safely retried, and any divergence fails closed.
39
51
 
40
- Mutating public client commands run under a transaction guardian that inherits the locked file description, starts the client in its own process group, and watches both the parent PID and IPC channel. Parent EOF, `SIGINT`, `SIGTERM`, or command timeout causes TERM then KILL. The guardian releases the fence only after `/proc` confirms that no non-zombie group member remains. Indeterminate cleanup returns a stable failure to the caller while the detached guardian retains the fence and continues cleanup, preventing a later setup transaction from racing the orphan. A mutation already accepted by an external client CLI or configuration backend cannot be rolled back automatically; ownership remains `pending` for reconciliation.
52
+ Mutating public client commands run under a transaction guardian that inherits the locked file description, starts the client in its own process group, and watches both the parent PID and IPC channel. Parent EOF, `SIGINT`, `SIGTERM`, or command timeout causes TERM then KILL. The guardian releases the fence only after a determinate process-table scan confirms that no non-zombie group member remains: `/proc` on Linux, and on macOS a strictly parsed `/bin/ps -A -o pid=,pgid=,stat=` that must list the scanning process itself. Indeterminate cleanup returns a stable failure to the caller while the detached guardian retains the fence and continues cleanup, preventing a later setup transaction from racing the orphan. A mutation already accepted by an external client CLI or configuration backend cannot be rolled back automatically; ownership remains `pending` for reconciliation.
41
53
 
42
54
  Here “gone” means no group member can still execute: a zombie has terminated and
43
55
  released its file descriptors, even when its PID awaits an external reaper. The
@@ -7,8 +7,14 @@ installation with upgrade and removal, start with [Installation](installation.md
7
7
  ## Prerequisites
8
8
 
9
9
  - Node.js **22.19.0 or newer**
10
- - Linux with a root-owned util-linux `flock` at `/usr/bin/flock` or `/bin/flock`,
11
- readable `/proc`, and a supported local ownership filesystem
10
+ - Linux or macOS. For `setup` also:
11
+ - on Linux, a root-owned util-linux `flock` at `/usr/bin/flock` or
12
+ `/bin/flock`, readable `/proc`, and a supported local ownership filesystem;
13
+ - on macOS, the system `/bin/ps` and an ownership directory on the APFS
14
+ volume type of `/`.
15
+
16
+ A manual registration does not need these; see
17
+ [Register without setup](installation.md#register-without-setup).
12
18
  - A local checkout of this repository
13
19
  - Existing local Pi authentication only when sending a real prompt
14
20
 
@@ -64,11 +70,20 @@ divergent existing registration is reported and never automatically replaced.
64
70
  Ownership is durable and contains only path-free, secret-free pending/owned
65
71
  state. A kernel-fenced guardian retains transaction ownership until every
66
72
  mutating client process-group member is gone, including after parent signals or
67
- hard parent exit. Non-Linux setup fails closed before client spawn.
68
- Supported ownership filesystems are ext2/3/4, XFS, Btrfs, tmpfs, overlayfs,
69
- ZFS, F2FS, UBIFS, and bcachefs. Network, FUSE, unreadable `/proc`, an untrusted
70
- `flock` binary, or same-UID replacement of the stable lock path are outside the
71
- accepted setup boundary and fail closed where they can be detected.
73
+ hard parent exit. Setup on any platform other than Linux and macOS fails
74
+ closed before client spawn.
75
+
76
+ Supported ownership filesystems:
77
+ - **Linux:** ext2/3/4, XFS, Btrfs, tmpfs, overlayfs, ZFS, F2FS, UBIFS, and
78
+ bcachefs.
79
+ - **macOS:** the APFS volume type of `/`.
80
+
81
+ The following are outside the accepted setup boundary and fail closed where
82
+ they can be detected:
83
+ - network or FUSE filesystems;
84
+ - unreadable `/proc`;
85
+ - an untrusted `flock` or `ps` binary;
86
+ - same-UID replacement of the stable lock path.
72
87
 
73
88
  Verify runs Doctor, checks exact registration equality, and then performs a
74
89
  bounded direct MCP `initialize`, `tools/list`, and
@@ -103,7 +118,7 @@ Create an unversioned JSON configuration file such as `pi-session-mcp.json`:
103
118
  }
104
119
  ```
105
120
 
106
- Configuration is strict. A profile has exactly `default`, `permissionProfile`, `provider`, `model`, and `thinkingLevel`; aliases follow the configured alias naming rules. At least one profile and exactly one default are required, and that default must be `read-only`. `thinkingLevel` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. Workspace paths resolve relative to the configuration file. Clients can select only configured aliases, never an arbitrary working directory.
121
+ Configuration is strict. A profile has exactly `default`, `permissionProfile`, `provider`, `model`, and `thinkingLevel`, plus the optional `mcpServers` and `extensions` described in [Installation](installation.md); aliases follow the configured alias naming rules. At least one profile and exactly one default are required, and that default must be `read-only`. `thinkingLevel` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. Workspace paths resolve relative to the configuration file. Clients can select only configured aliases, never an arbitrary working directory.
107
122
 
108
123
  Use placeholders until a locally available provider/model pair is known. Do not put keys, tokens, credential objects, headers, base URLs, or authentication paths in this file.
109
124
 
@@ -186,8 +201,8 @@ Server deadlines are profile-specific authentication/catalog work 5,000 ms per o
186
201
 
187
202
  There are eight tools: `pi_capabilities_get`, `pi_session_start`, `pi_session_list`, `pi_session_get`, `pi_session_prompt`, `pi_session_abort`, `pi_session_close`, and `pi_turn_get`. Capability discovery accepts only `{}`. `pi_session_start` rejects the old `profile` input, raw `provider`, `model`, `thinkingLevel`, credential-like fields, and all unknown fields. Its safe session metadata includes the configured execution-profile alias and post-verified provider/model/effective thinking level; the existing `profile` output remains derived permission metadata.
188
203
 
189
- The returned `thinkingLevel` records the effective Pi session setting. Pi
190
- Control compares it with the configured profile selection before admitting the
204
+ The returned `thinkingLevel` records the effective Pi session setting. The
205
+ server compares it with the configured profile selection before admitting the
191
206
  session; it does not verify provider-side reasoning or measure or cap its
192
207
  reasoning-token budget: a provider may use reasoning tokens even when the session
193
208
  selects `off`. Pi Session MCP does not project thinking content or provider-side
@@ -9,8 +9,9 @@ directory: an npm executable can run from any directory.
9
9
  `runDoctor` returns one frozen result model (`schemaVersion: 1`, `ok`, a
10
10
  sanitized semver `packageVersion` when metadata is available, ordered `checks`,
11
11
  and `exitCode`). Human and JSON output are projections of that same model.
12
- Subjects are only fixed component names or validated workspace aliases; paths,
13
- configuration values, errors, credentials, and provider data are never output.
12
+ Subjects are only fixed component names, validated workspace aliases, or
13
+ validated `<profile>:<package>` pairs; paths, configuration values, errors,
14
+ credentials, and provider data are never output.
14
15
 
15
16
  Each check is `{ id, severity, subject, remediation? }`. Severity is `ok`,
16
17
  `warning`, or `error`; remediation is a fixed safe sentence. The initial IDs
@@ -31,6 +32,10 @@ and owners are:
31
32
  | `workspace_not_directory` | A resolved workspace is not a directory | validated workspace alias |
32
33
  | `workspace_unreadable` | A resolved workspace cannot be read | validated workspace alias |
33
34
  | `workspace_not_writable` | A workspace is not writable while a coding profile can target it | validated workspace alias |
35
+ | `extension_missing` | Pi has no user install of a package that a profile's `extensions` names | `<profile>:<package>` |
36
+ | `extension_version_mismatch` | The installed manifest's version differs from the pinned version, or the manifest or its `pi.extensions` entries are invalid | `<profile>:<package>` |
37
+
38
+ Doctor reads extension manifests from Pi's user install location only; it never installs packages and never imports extension code.
34
39
 
35
40
  The Doctor reuses `loadConfig`; it does not duplicate alias, profile, thinking
36
41
  level, or read-only-default rules. Workspace values are used only for local
@@ -35,19 +35,25 @@ the same arguments.
35
35
 
36
36
  ## Requirements
37
37
 
38
- - Linux. `setup` fails closed on every other platform, and the server has only
39
- been accepted on Linux.
38
+ - Linux or macOS. `setup` fails closed on every other platform. macOS is tested
39
+ in CI on macOS 15 and 26 on Apple silicon. Setup acceptance on a real Mac is
40
+ recorded for Claude Code only; Codex is still pending. See
41
+ [Acceptance](acceptance.md#status).
40
42
  - Node.js **22.19.0 or newer**. `setup` registers the absolute path of the Node
41
43
  executable that runs it, so run it with the Node installation that clients
42
44
  should use.
43
- - For `setup`: a root-owned util-linux `flock` at `/usr/bin/flock` or
45
+ - For `setup` on Linux: a root-owned util-linux `flock` at `/usr/bin/flock` or
44
46
  `/bin/flock`, readable `/proc`, and the ownership state directory
45
47
  `~/.local/state/pi-session-mcp` on ext2/3/4, XFS, Btrfs, tmpfs, overlayfs, ZFS,
46
48
  F2FS, UBIFS or bcachefs. Network and FUSE filesystems fail closed.
49
+ - For `setup` on macOS: the system `/bin/ps`, and the ownership state directory
50
+ `~/.local/state/pi-session-mcp` on the same filesystem type as `/`, which is
51
+ APFS. A home directory on HFS+, exFAT, a network volume or any other
52
+ filesystem fails closed.
47
53
  - Codex CLI or Claude Code. `setup` does not pin client versions. It accepts a
48
54
  client whose public `mcp` command output matches the expected contract and
49
55
  reports `unsupported` otherwise. The contract was last recorded with Codex CLI
50
- `0.162.0` and Claude Code `2.1.296`. Manual registration, described in the
56
+ `0.162.1` and Claude Code `2.1.296`. Manual registration, described in the
51
57
  [client setup reference](client-setup.md), works independently of `setup`.
52
58
  - For real prompts only: local Pi authentication and a provider and model pair
53
59
  that the local Pi configuration knows. Installation, Doctor and `setup` need
@@ -146,6 +152,10 @@ Rules:
146
152
  `azure`; a profile that names the old key must be changed.
147
153
  - A profile may grant tools from local stdio MCP servers through `mcpServers`.
148
154
  See [External MCP tools in embedded sessions](session-mcp.md).
155
+ - A profile may load Pi extensions that Pi has installed, through
156
+ `extensions`: a list of at most 8 entries of the form
157
+ `npm:<name>@<exact version>`, each package once. Ranges, tags, `git:`
158
+ sources and paths are rejected. See [Pi extensions](#pi-extensions).
149
159
  - Never put keys, tokens, credential objects, headers, base URLs or
150
160
  authentication paths into this file.
151
161
 
@@ -245,6 +255,67 @@ If a registration already exists, `setup` never replaces it:
245
255
  Start a new client session after registering. The client launches the server on
246
256
  demand; there is nothing to start by hand.
247
257
 
258
+ ### Register without setup
259
+
260
+ `setup` runs on Linux and macOS. On every other platform, Windows included, it
261
+ refuses every operation, `--verify` included, with `platform_unsupported`
262
+ before it starts a client command. On Linux and macOS, `--apply`, `--verify`,
263
+ `--rollback` and `--remove` also fail closed when the platform prerequisites in
264
+ [Requirements](#requirements) are not met. A manual registration needs none of
265
+ this: it uses the client's own commands and writes no ownership state. Use it
266
+ when `setup` cannot run, or when you manage client configuration yourself. No
267
+ acceptance level covers this path.
268
+
269
+ 1. Install as in section 1, configure as in section 2, and run Doctor as in
270
+ section 3. Doctor has no platform-specific check and needs neither the lock
271
+ nor the process-table prerequisites of `setup`.
272
+ 2. Register the server with the client's own command, as the
273
+ [Codex](client-setup.md#register-with-codex) and
274
+ [Claude Code](client-setup.md#register-with-claude-code) sections of the
275
+ client setup reference show. Use absolute paths and the Node executable that
276
+ clients should use. The entry point is `dist/main.js` inside the package
277
+ directory: in an npm installation `echo "$(npm root -g)/pi-session-mcp"`
278
+ prints that directory, in a release checkout it is the checkout. Write the
279
+ resulting absolute path into the command; a substitution inside the single
280
+ quotes of the Codex example would not be expanded.
281
+ 3. Check the registration. These checks are not a replacement for
282
+ `setup --verify`: it also performs a bounded direct MCP handshake, and
283
+ outside `setup` nothing does that yet. The last check below depends on the
284
+ client, which can hang or hide a handshake problem, so treat the result as
285
+ weaker evidence than `--verify`.
286
+ - Doctor passed in item 1.
287
+ - Compare the stored registration with what you meant to register.
288
+ `claude mcp get pi-session-mcp` and `codex mcp get pi-session-mcp` print
289
+ the Node executable, the entry point and the configuration path that the
290
+ client stored, and Claude Code also prints the scope. Each must equal the
291
+ value you passed. `--verify` makes this comparison for `setup`. Without
292
+ it, a registration that still points at an old release directory passes
293
+ every other check.
294
+ - `claude mcp list` and `claude mcp get pi-session-mcp` report `Connected`
295
+ when the client could start the server and complete the MCP connection.
296
+ A registration with `-s project` reports `Pending approval` instead,
297
+ because Claude Code connects to a project server only after you approve it
298
+ in a session in that project. Approve it, then expect `Connected`.
299
+ - Start a new client session and call `pi_capabilities_get` with `{}`. The
300
+ client lists the eight tools, and the call returns the configured aliases.
301
+
302
+ Like `--verify`, none of these starts a Pi session or contacts a provider.
303
+ 4. Upgrade and remove with the client's own commands. Nothing owns the
304
+ registration and it is not bound to the build.
305
+ - An npm upgrade replaces the package in place and keeps the entry point.
306
+ Close the client sessions, install the new version as in section 1, run
307
+ Doctor as in section 3, and repeat the checks of item 3. Do not run the
308
+ `cp` of section 2 again: it replaces your configuration with the packaged
309
+ example.
310
+ - A new release directory needs a new registration. Run
311
+ `codex mcp remove pi-session-mcp` or
312
+ `claude mcp remove pi-session-mcp -s <scope>`, add the server again with
313
+ the new paths, run Doctor, and repeat the checks of item 3.
314
+ - To remove it entirely, run the client's remove command, then uninstall the
315
+ package or delete the release directory and the configuration file. A
316
+ manual registration leaves no ownership state, so the `--remove` of the
317
+ Remove section does not apply.
318
+
248
319
  ## 5. Run a first session
249
320
 
250
321
  From the client, call the tools in this order:
@@ -273,6 +344,53 @@ configuration or credentials.
273
344
  The configuration path is part of the registration. Editing the file in place
274
345
  needs no new registration; moving it does.
275
346
 
347
+ ## Pi extensions
348
+
349
+ An execution profile can load Pi extensions that Pi has installed. Install them
350
+ with Pi (`pi install npm:<name>`; update with `pi update --extensions`), then
351
+ pin the installed version in the profile:
352
+
353
+ ```json
354
+ "code-cursor": {
355
+ "default": false,
356
+ "permissionProfile": "coding",
357
+ "provider": "cursor",
358
+ "model": "<model id from Pi's cursor catalog>",
359
+ "thinkingLevel": "high",
360
+ "extensions": ["npm:pi-cursor-sdk@0.5.3"]
361
+ }
362
+ ```
363
+
364
+ - **Install location.** Only Pi's own user install counts:
365
+ `<Pi agent dir>/npm/node_modules/<name>`, normally `~/.pi/agent/npm/…`,
366
+ created by `pi install npm:<name>`. A package installed with `npm i -g` is
367
+ reported as not installed: sessions report `extension_not_installed`, Doctor
368
+ reports `extension_missing`.
369
+ - **Version pin.** The server never installs or updates extensions. If Pi has
370
+ another version installed, sessions of that profile fail with
371
+ `extension_version_mismatch` and Doctor reports it. After `pi update
372
+ --extensions`, update the pinned version and restart the client.
373
+ - **Full capabilities.** An extension loads with its providers, tools, hooks
374
+ and system-prompt changes. In `read-only` profiles the model still gets only
375
+ `read`, `grep`, `find` and `ls`; in `coding` profiles every extension tool
376
+ may be active. Extension commands never run from client prompts, but
377
+ extension input hooks can still see, rewrite or consume a client prompt.
378
+ - **Fail-closed start.** A session start fails with `extension_load_failed` if
379
+ an extension reports a hook error while binding. It fails the same way if an
380
+ extension registers an MCP server while loading or starting, because Pi's
381
+ built-in MCP extension is not loaded. An extension tool whose name collides
382
+ with another tool, or whose name is not a plain identifier, fails with
383
+ `extension_tool_conflict`.
384
+ - **Cursor runtime.** `pi-cursor-sdk` runs Cursor's local agent runtime. That
385
+ runtime starts its own processes (for example git and the MCP servers
386
+ configured for Cursor) and may act through Cursor's own tooling, so a
387
+ `read-only` profile limits only Pi's tools, not what the Cursor runtime does.
388
+ - **Trust.** Only list extensions you trust like any other program you run:
389
+ they execute inside the server process with your rights.
390
+ - **Cursor sign-in limit.** Without a stored Cursor key, `pi-cursor-sdk`
391
+ reports a placeholder key. Session start succeeds and the first prompt fails
392
+ with `turn_failed`. Store the key with Pi first.
393
+
276
394
  ## Upgrade
277
395
 
278
396
  `setup` binds each owned registration to the package manifests and to every