pi-session-mcp 0.7.0 → 0.7.1

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.
@@ -1,7 +1,12 @@
1
1
  export const MAX_PROFILE_EXTENSIONS = 8;
2
2
  const MAX_SPEC_LENGTH = 256;
3
- /** `npm:<name>@<exact semver>`; the name may be scoped. Ranges, tags, git sources and paths never match. */
4
- const EXTENSION_SPEC_PATTERN = /^npm:((?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*)@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/;
3
+ /**
4
+ * `npm:<name>@<exact semver>` with an optional prerelease; the name may be scoped. Ranges, tags,
5
+ * git sources and paths never match. Build metadata (`+build`) never matches either: npm does not
6
+ * keep it in a published version, so such a pin could never equal the installed manifest. Rejecting
7
+ * it here fails the configuration instead of every session with `extension_version_mismatch`.
8
+ */
9
+ const EXTENSION_SPEC_PATTERN = /^npm:((?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*)@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/;
5
10
  export function parseExtensionSpec(spec) {
6
11
  if (spec.length > MAX_SPEC_LENGTH)
7
12
  return undefined;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.7.1 — 2026-10-11
6
+
7
+ The public MCP contract is unchanged.
8
+
9
+ - Extension tool names are now limited to 1 to 64 letters, digits, `_` and
10
+ `-`, not starting with `-`, so `.` and `:` no longer pass and every name fits
11
+ the common provider function-name limit. Names that Pi SDK 1.1.0 treats as
12
+ MCP tools (the `mcp__` prefix, `list_mcp_resources`,
13
+ `list_mcp_resource_templates`, `read_mcp_resource`) are rejected too, because
14
+ the SDK would keep them outside the allowlist. Both cases fail with
15
+ `extension_tool_conflict`. Fixes #41.
16
+ - Extension pins with build metadata, such as `npm:<name>@1.2.3+build.1`, are
17
+ now rejected when the configuration loads. npm does not keep build metadata
18
+ in a published version, so such a pin could never match the installed
19
+ manifest. Such a configuration is now invalid as a whole: the server does not
20
+ start and Doctor reports `config_invalid`, instead of every session of the
21
+ profile failing with `extension_version_mismatch`. Fixes #42.
22
+ - Client prompts now reach Pi with `source: "rpc"`, the marker Pi's own RPC
23
+ mode uses for input from a program, instead of the SDK default
24
+ `interactive`. Extension `input` hooks that read the source now see that a
25
+ program, not a human at the terminal, sent the prompt. Fixes #43.
26
+ - The npm package no longer ships the implementation plans under
27
+ `docs/superpowers/`; the operator documentation stays included. A test
28
+ checks the packed file list. Fixes #39.
29
+
5
30
  ## 0.7.0 — 2026-10-11
6
31
 
7
32
  This is a minor release because the public contract changes, as ADR 0003
package/README.md CHANGED
@@ -29,7 +29,7 @@ It is not the right tool when:
29
29
 
30
30
  ## Status
31
31
 
32
- The package version is **0.7.0**.
32
+ The package version is **0.7.1**.
33
33
 
34
34
  - **Pre-1.0.** The public tool contract is stable within a minor line: a change to tools, fields, states or error codes needs a new minor release.
35
35
  - **Linux and macOS**, Node.js 22.19.0 or newer. macOS is tested in CI on macOS 15 and 26 on Apple silicon; Intel Macs run the same code but are untested. Setup acceptance on a real Mac is recorded for Claude Code only; Codex is still pending. See [Acceptance](docs/acceptance.md#status). On other platforms `setup` refuses with `platform_unsupported`.
@@ -1,7 +1,12 @@
1
1
  export const MAX_PROFILE_EXTENSIONS = 8;
2
2
  const MAX_SPEC_LENGTH = 256;
3
- /** `npm:<name>@<exact semver>`; the name may be scoped. Ranges, tags, git sources and paths never match. */
4
- const EXTENSION_SPEC_PATTERN = /^npm:((?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*)@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/;
3
+ /**
4
+ * `npm:<name>@<exact semver>` with an optional prerelease; the name may be scoped. Ranges, tags,
5
+ * git sources and paths never match. Build metadata (`+build`) never matches either: npm does not
6
+ * keep it in a published version, so such a pin could never equal the installed manifest. Rejecting
7
+ * it here fails the configuration instead of every session with `extension_version_mismatch`.
8
+ */
9
+ const EXTENSION_SPEC_PATTERN = /^npm:((?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*)@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/;
5
10
  export function parseExtensionSpec(spec) {
6
11
  if (spec.length > MAX_SPEC_LENGTH)
7
12
  return undefined;
@@ -223,6 +223,9 @@ class SdkHandle {
223
223
  promptResult = this.session.prompt(text, {
224
224
  // Client text is content, never a command: no extension command, skill or template dispatch.
225
225
  expandPromptTemplates: false,
226
+ // Pi's own RPC mode uses `rpc` for input from a program over stdio, which matches an MCP
227
+ // client. `interactive`, the SDK default, means a human at the terminal or the CLI's print mode.
228
+ source: "rpc",
226
229
  preflightResult: (disposition) => signalPreflight(admitsTurn(disposition)),
227
230
  });
228
231
  }
@@ -4,6 +4,6 @@ export declare const PROFILE_TOOLS: Readonly<Record<PermissionProfile, readonly
4
4
  * The tool allowlist of a session, or undefined when an extension tool name conflicts.
5
5
  * Extension tools are active only in coding profiles, but a conflict fails closed in both,
6
6
  * so an extension can never shadow a built-in or granted tool. A name outside the safe
7
- * character set also fails closed.
7
+ * character set, or one the SDK treats as an MCP tool, also fails closed.
8
8
  */
9
9
  export declare function composeSessionTools(permissionProfile: PermissionProfile, mcpToolNames: readonly string[], extensionToolNames: readonly (readonly string[])[]): string[] | undefined;
@@ -2,20 +2,36 @@ export const PROFILE_TOOLS = {
2
2
  "read-only": ["read", "grep", "find", "ls"],
3
3
  coding: ["read", "bash", "edit", "write", "grep", "find", "ls"],
4
4
  };
5
- /** The SDK reads `*` in an allowlist as a glob and a leading `+` or `-` as a modifier, so those names are rejected: the first character may not be `-`. */
6
- const EXTENSION_TOOL_NAME = /^[A-Za-z0-9_.:][A-Za-z0-9_.:-]*$/;
5
+ /**
6
+ * Letters, digits, `_` and `-`, at most 64 characters, which fits the common provider
7
+ * function-name limit `^[a-zA-Z0-9_-]{1,64}$`. The SDK reads `*` in an allowlist as a glob
8
+ * and a leading `+` or `-` as a modifier, so the first character may not be `-`.
9
+ */
10
+ const EXTENSION_TOOL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
11
+ /**
12
+ * These mirror `isMcpToolName` of Pi SDK 1.1.0 (core/mcp-servers.js), which is not a root
13
+ * export. When the allowlist names no `mcp__` entry, the SDK's `_isAllowedTool` keeps such
14
+ * tools outside the allowlist for codemode and tool_search, so an extension tool with one
15
+ * of these names could be active without being granted. This server's own MCP bridge
16
+ * tools are named `mcp_<server>_<tool>` with a single underscore, so they never collide.
17
+ */
18
+ const MCP_RESERVED_PREFIX = "mcp__";
19
+ const MCP_RESERVED_NAMES = new Set(["list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"]);
20
+ function isAcceptableExtensionToolName(name) {
21
+ return EXTENSION_TOOL_NAME.test(name) && !name.startsWith(MCP_RESERVED_PREFIX) && !MCP_RESERVED_NAMES.has(name);
22
+ }
7
23
  /**
8
24
  * The tool allowlist of a session, or undefined when an extension tool name conflicts.
9
25
  * Extension tools are active only in coding profiles, but a conflict fails closed in both,
10
26
  * so an extension can never shadow a built-in or granted tool. A name outside the safe
11
- * character set also fails closed.
27
+ * character set, or one the SDK treats as an MCP tool, also fails closed.
12
28
  */
13
29
  export function composeSessionTools(permissionProfile, mcpToolNames, extensionToolNames) {
14
30
  const reserved = new Set([...PROFILE_TOOLS.coding, ...PROFILE_TOOLS["read-only"], ...mcpToolNames]);
15
31
  const extensionTools = [];
16
32
  for (const names of extensionToolNames) {
17
33
  for (const name of names) {
18
- if (!EXTENSION_TOOL_NAME.test(name) || reserved.has(name))
34
+ if (!isAcceptableExtensionToolName(name) || reserved.has(name))
19
35
  return undefined;
20
36
  reserved.add(name);
21
37
  extensionTools.push(name);
@@ -124,6 +124,55 @@ that checkout with Doctor reporting the release version, and confirms that the
124
124
  manifests and built runtime files are byte-identical before and after the
125
125
  client cycles.
126
126
 
127
+ ### Tooling
128
+
129
+ `scripts/setup-acceptance.mjs` runs the isolation, the sequence and the pass
130
+ criteria above with the real clients. It uses disposable homes below its work
131
+ directory (default `~/.cache/pi-session-mcp-acceptance`, never below `/tmp`,
132
+ `/var/tmp`, `/dev/shm` or `$TMPDIR`), removes them afterwards (also after Ctrl-C
133
+ or SIGTERM, with exit `130` or `143`) and writes a sanitized JSON result without
134
+ paths or environment values. It exits with `0` when every check passed, `1` when
135
+ a check failed and `2` on a usage, environment or harness error.
136
+
137
+ `--setup` is split on whitespace, so paths in it must not contain spaces. Its
138
+ first token must be an absolute path to an existing file or a command found on
139
+ `PATH`; otherwise the script exits with `2`.
140
+
141
+ The script only reads the real `~/.codex/config.toml` and `~/.claude.json` to
142
+ prove that the MCP registrations in them did not change. A file that does not
143
+ exist counts as the constant digest `absent`; one that exists but cannot be read
144
+ fails the check. A real client session that changes its own MCP registrations
145
+ during the run makes the digest check fail (a false fail), so close or idle real
146
+ clients first.
147
+
148
+ Working tree, after `npm run build` and `npm run build:cli`:
149
+
150
+ ```bash
151
+ node scripts/setup-acceptance.mjs \
152
+ --setup "node $PWD/dist/setup-main.js" --build-dir "$PWD" \
153
+ --label working-tree --out level2-result.json
154
+ ```
155
+
156
+ Published package: install it into a temporary prefix outside `/tmp`, then pass
157
+ the installed executable and package directory.
158
+
159
+ ```bash
160
+ npm install -g --prefix "$PREFIX" pi-session-mcp@<version>
161
+ node scripts/setup-acceptance.mjs \
162
+ --setup "$PREFIX/bin/pi-session-mcp-setup" \
163
+ --build-dir "$PREFIX/lib/node_modules/pi-session-mcp" \
164
+ --label npm-<version> --out level2-result.json
165
+ ```
166
+
167
+ Tag clone: build a fresh clone of the tag (`npm ci`, `npm run build`,
168
+ `npm run build:cli`) and use it like the working tree, with `--setup` and
169
+ `--build-dir` pointing at the clone.
170
+
171
+ `--clients codex` or `--clients claude-code` limits a run to one client, for
172
+ example on a macOS machine without Codex. The script does not cover the macOS
173
+ lock-contention and interruption steps, Level 1, or the tag identity checks
174
+ (tag object, peeled commit, `git describe`). Run those by hand.
175
+
127
176
  ### macOS
128
177
 
129
178
  macOS uses the same isolation, sequence and pass criteria, with the additions
@@ -154,7 +203,7 @@ CI-tested, and it has not passed Level 2 for Codex.
154
203
  - **Interruption.** Start `--apply` for one target and press Ctrl-C while it
155
204
  runs.
156
205
  - The run exits with `130` and reports `operation_interrupted`.
157
- - Afterwards `ps -A -o pid=,command=` lists no `claude mcp` or `codex mcp`
206
+ - Afterwards `ps -ww -A -o pid=,command=` lists no `claude mcp` or `codex mcp`
158
207
  command and no `setup-main.js` or `setup-command-guardian.js` process of
159
208
  the build under test.
160
209
  - A following `--apply` reports one of three outcomes, depending on where
@@ -166,7 +215,8 @@ CI-tested, and it has not passed Level 2 for Codex.
166
215
  - A following `--remove` reports `ok (removed)`.
167
216
  - If the operation completes before the interrupt arrives, record that and
168
217
  repeat. An interrupt that never lands is not a pass.
169
- - **Processes.** Check that no process remains with `ps` instead of `/proc`.
218
+ - **Processes.** Check that no process remains with `ps -ww` instead of `/proc`;
219
+ without `-ww`, `ps` truncates long commands when it has no terminal.
170
220
 
171
221
  The evidence record names the build (commit or tag), the environment above,
172
222
  every command with its exit status, and the sanitized JSON results. It
@@ -485,3 +535,80 @@ Claude Code `2.1.296`, on 2026-10-11:
485
535
  The Pi extension live check above ran on `eeaa597`, before the final fixes to
486
536
  extension tool names and the `read-only` contract test. It has not been
487
537
  repeated on the release candidate.
538
+
539
+ After its release, `v0.7.0` passed Level 1 and Level 2 on a fresh clone of its
540
+ tag, and Level 2 as published npm package, on 2026-10-11, on Linux with Node.js
541
+ `26.11.1`, Codex CLI `0.162.1` and Claude Code `2.1.296`:
542
+ - Tag clone: `git describe --tags --exact-match` reported `v0.7.0`. The tag
543
+ object is `a5516ce6e4e159ef1e20ee38aba06672379e9cb7` and the peeled commit
544
+ `6783631d2ae1813707cf798360a0a0f4c58ca66d`.
545
+ - Level 1 on the tag clone:
546
+ - every command exited with `0`;
547
+ - the test run reported 37 files, 660 passed and 5 skipped;
548
+ - `npm audit` and `npm audit --omit=dev` reported 0 vulnerabilities;
549
+ - Doctor reported `packageVersion` `0.7.0` and no finding;
550
+ - `dist/main.js` has the SHA-256
551
+ `47875d38886528ace6b242df2c6ad79d624c51e330e63c2f401576eadc449ed7`.
552
+ - Level 2 passed 96 of 96 checks on the tag clone, and 96 of 96 with the npm
553
+ package installed into a temporary prefix through
554
+ `pi-session-mcp-setup`. In both runs the real client registrations were
555
+ unchanged, no process remained, and the built files were byte-identical
556
+ before and after the client cycles.
557
+ - These runs, and the release candidate's runs above, used the harness that
558
+ #40 later checked in as `scripts/setup-acceptance.mjs`.
559
+ - The checked-in script, after its review fixes, also passed 96 of 96 on the
560
+ tag clone.
561
+ - The npm package has the shasum `c8d73311dbde1bdebd78e58b1f01c7cf3bebf374`.
562
+ The tarball downloaded from the registry had the same SHA-1 and SHA-512
563
+ integrity.
564
+ - Installed package:
565
+ - `pi-session-mcp-doctor` reported a healthy package `0.7.0` when run from an
566
+ unrelated working directory;
567
+ - with a configuration that pins `pi-cursor-sdk@0.5.3` on three profiles,
568
+ `setup --apply` and `--verify` for `claude-code:user:pi-session-mcp`
569
+ reported `applied` and `mcp_verified`.
570
+
571
+ On 2026-10-11 the installed `v0.7.0` package also passed the Pi extension live
572
+ check on Linux:
573
+ - Method: the same scripted JSON-RPC client as above drove the installed
574
+ server. It used the operator's configuration with three `pi-cursor-sdk@0.5.3`
575
+ profiles: one `read-only` and one `coding` profile with
576
+ `claude-opus-5-5@1m` and `claude-opus-5-5@300k` at thinking level `high`, and
577
+ one `coding` profile with `composer-2.5` at thinking level `off`.
578
+ - Each of the three profiles started, completed a prompt with the expected
579
+ one-word answer, and closed. One `coding` and one `read-only` Cursor session started in parallel
580
+ and both completed.
581
+ - Profiles without extensions on three other providers (xAI, Mistral and
582
+ DeepSeek) each completed a prompt.
583
+ - After close no descendant process of the server remained. No stdout line
584
+ failed to parse as an MCP frame; stderr carried four
585
+ `foreign_stdout_suppressed` reports for 12 suppressed writes and no other
586
+ event.
587
+ - The two OpenAI profiles ended `failed` with `turn_failed`. A direct Pi CLI
588
+ call outside the server showed that the account had reached its usage limit,
589
+ so they are not evidence for or against the release.
590
+
591
+ This check does not cover macOS, an MCP client in the loop, or a session that
592
+ uses Cursor's own tools.
593
+
594
+ Before its release, `v0.7.1` passed Level 1 and the working-tree Level 2 on its
595
+ release candidate `82dff20` on Linux, with Node.js `26.11.1`, Codex CLI
596
+ `0.162.1` and Claude Code `2.1.296`, on 2026-10-11:
597
+ - Level 1: every command exited with `0`.
598
+ - The test run reported 38 files, 684 passed and 5 skipped.
599
+ - `npm audit` and `npm audit --omit=dev` reported 0 vulnerabilities.
600
+ - Doctor reported `packageVersion` `0.7.1` and no finding.
601
+ - Level 2 ran through `scripts/setup-acceptance.mjs` on a fresh clone of the
602
+ candidate.
603
+ - All nine steps passed on all four targets, 96 of 96 checks.
604
+ - The real client registrations were unchanged, and no process remained.
605
+ - The built files were byte-identical before and after the client cycles.
606
+ - `dist/main.js` is byte-identical to that of `v0.7.0`. The changed modules are
607
+ the extension tool-name rule, the pin format and the prompt source.
608
+
609
+ The branch build of `8d5b069`, whose sources match this candidate, passed the
610
+ Pi extension live check with the operator's configuration on 2026-10-11:
611
+ - Three `pi-cursor-sdk@0.5.3` profiles and one xAI profile each completed a
612
+ prompt.
613
+ - Two Cursor sessions completed in parallel.
614
+ - No process remained, and no foreign line reached the MCP stdout.
@@ -40,8 +40,8 @@ model runtime per session:
40
40
 
41
41
  Profiles without extensions keep the previous path. Client prompts are plain
42
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.
43
+ templates, and extension input hooks see them with `source: "rpc"`. An
44
+ always-on stdout guard gives the MCP transport the only real stdout writer.
45
45
 
46
46
  ## Consequences
47
47
 
@@ -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. 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.
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, and their input hooks see every client prompt as `rpc` input, never `interactive`; 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.
@@ -155,7 +155,7 @@ Rules:
155
155
  - A profile may load Pi extensions that Pi has installed, through
156
156
  `extensions`: a list of at most 8 entries of the form
157
157
  `npm:<name>@<exact version>`, each package once. Ranges, tags, `git:`
158
- sources and paths are rejected. See [Pi extensions](#pi-extensions).
158
+ sources, paths and build metadata (`+build`) are rejected. See [Pi extensions](#pi-extensions).
159
159
  - Never put keys, tokens, credential objects, headers, base URLs or
160
160
  authentication paths into this file.
161
161
 
@@ -374,13 +374,16 @@ pin the installed version in the profile:
374
374
  and system-prompt changes. In `read-only` profiles the model still gets only
375
375
  `read`, `grep`, `find` and `ls`; in `coding` profiles every extension tool
376
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.
377
+ extension input hooks can still see, rewrite or consume a client prompt;
378
+ they receive it with `source: "rpc"`, never `interactive`.
378
379
  - **Fail-closed start.** A session start fails with `extension_load_failed` if
379
380
  an extension reports a hook error while binding. It fails the same way if an
380
381
  extension registers an MCP server while loading or starting, because Pi's
381
382
  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`.
383
+ with another tool, or whose name is not 1 to 64 letters, digits, `_` or `-`
384
+ (not starting with `-`), or whose name Pi treats as an MCP tool (starting
385
+ with `mcp__`, or `list_mcp_resources`, `list_mcp_resource_templates`,
386
+ `read_mcp_resource`), fails with `extension_tool_conflict`.
384
387
  - **Cursor runtime.** `pi-cursor-sdk` runs Cursor's local agent runtime. That
385
388
  runtime starts its own processes (for example git and the MCP servers
386
389
  configured for Cursor) and may act through Cursor's own tooling, so a
@@ -115,8 +115,12 @@ Rules, enforced by `configSchema`:
115
115
 
116
116
  - Each entry matches `npm:<name>@<version>`. `<name>` is an npm package name,
117
117
  optionally scoped (`@scope/name`), lowercase. `<version>` is an exact semver
118
- version with optional prerelease and build metadata. Ranges, tags, `git:`,
119
- paths and URLs are rejected.
118
+ version with an optional prerelease. Build metadata (`+build`) is rejected
119
+ because npm does not keep it in a published version, so such a pin could
120
+ never match the installed manifest; the configuration fails with
121
+ `config_invalid` instead of every session failing with
122
+ `extension_version_mismatch`. Ranges, tags, `git:`, paths and URLs are
123
+ rejected.
120
124
  - At most 8 entries per profile. A package name appears at most once per
121
125
  profile.
122
126
  - The field is allowed on `read-only` and `coding` profiles, including the
@@ -210,9 +214,17 @@ and step 6 leaves no session to dispose.
210
214
  granted MCP tool or a tool of another extension fails with
211
215
  `extension_tool_conflict`. This also stops an extension from replacing a
212
216
  built-in tool inside a `read-only` profile.
213
- A name must also match `[A-Za-z0-9_.:][A-Za-z0-9_.:-]*`: the SDK reads `*`
214
- in an allowlist as a glob and a leading `+` or `-` as a modifier, so any
215
- other name fails the same way, in both profiles.
217
+ A name must also match `[A-Za-z0-9_][A-Za-z0-9_-]{0,63}`: the SDK reads `*`
218
+ in an allowlist as a glob and a leading `+` or `-` as a modifier, and 64
219
+ characters of letters, digits, `_` and `-` fit the common provider
220
+ function-name limit, so any other name fails the same way, in both
221
+ profiles. Names that Pi SDK 1.1.0 treats as MCP tools fail too: any name
222
+ starting with `mcp__`, and exactly `list_mcp_resources`,
223
+ `list_mcp_resource_templates` and `read_mcp_resource`. When the allowlist
224
+ names no `mcp__` entry, the SDK keeps such tools outside the allowlist for
225
+ codemode and tool_search, so they could otherwise be active without a grant.
226
+ This server's own MCP bridge tools use a single underscore (`mcp_<server>_<tool>`)
227
+ and never collide.
216
228
  The check runs after load and again after binding, because a `session_start`
217
229
  handler can register a tool during `bindExtensions`. If the registered tool
218
230
  set then differs from the one the session was created with, the start fails
@@ -230,7 +242,11 @@ and step 6 leaves no session to dispose.
230
242
  `before_provider_request`, `before_provider_headers` and `context`.
231
243
  - **Prompts.** Every `session.prompt()` call, for every profile, passes
232
244
  `expandPromptTemplates: false`. Client prompt text never dispatches an
233
- extension command, a skill command or a prompt template.
245
+ extension command, a skill command or a prompt template. The call also
246
+ passes `source: "rpc"`, which is what Pi's own RPC mode uses for input from
247
+ a program over stdio and so matches an MCP client. The SDK default,
248
+ `interactive`, means a human at the terminal or the CLI's print mode, which
249
+ a client prompt is not. Extension `input` hooks therefore see `rpc`.
234
250
  - **UI and flags.** No UI context is bound; extension UI calls return the
235
251
  SDK's no-op results. `extensionFlagValues` is empty, so extension flags keep
236
252
  their defaults.
@@ -311,7 +327,7 @@ admitted:
311
327
  | `extension_not_installed` | Configured Pi extension is not installed | Pi has no user install of the package. |
312
328
  | `extension_version_mismatch` | Configured Pi extension version is not installed | The installed version differs from the pinned version. |
313
329
  | `extension_load_failed` | Configured Pi extension could not be loaded | Invalid manifest, entry outside the package, load or bind error, services diagnostic of type `error`. |
314
- | `extension_tool_conflict` | Configured Pi extension tools conflict | An extension tool name collides with a built-in, MCP or other extension tool. |
330
+ | `extension_tool_conflict` | Configured Pi extension tools conflict | An extension tool name collides with a built-in, MCP or other extension tool, or is unsafe (see *Name conflicts*). |
315
331
 
316
332
  Existing codes keep their meaning. An extension provider that is unknown after
317
333
  loading is `unknown_provider`.
@@ -358,7 +374,7 @@ Automated tests use no credentials and no network.
358
374
  - `session_shutdown` on close, on a failure after services, and on a late
359
375
  start after the deadline;
360
376
  - tool lists for read-only and coding, and `extension_tool_conflict`;
361
- - `expandPromptTemplates: false` on every prompt;
377
+ - `expandPromptTemplates: false` and `source: "rpc"` on every prompt;
362
378
  - profiles without extensions taking the unchanged path.
363
379
  - **stdout guard** (`test/stdout-guard.test.ts`): foreign `write` and
364
380
  `console.*` output does not reach stdout, transport writes do, and the
package/docs/releasing.md CHANGED
@@ -20,7 +20,8 @@ error codes, as defined in the [tool contracts](tool-contracts.md).
20
20
 
21
21
  A release is distributed twice: as the GitHub release with its source archive,
22
22
  and as the npm package `pi-session-mcp`. The npm package contains the built
23
- files, the executables, the documentation and the example configuration, but
23
+ files, the executables, the operator documentation and the example
24
+ configuration. It contains no implementation plans (`docs/superpowers/`), and
24
25
  no lockfile, because npm does not ship lockfiles inside packages.
25
26
 
26
27
  ## Update the Pi SDK
@@ -47,7 +48,10 @@ update is possible. It is not an update and not an acceptance.
47
48
  ships in the package as
48
49
  `node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md`. Look for changes
49
50
  to stop reasons, prompt acceptance, the tools declared to the model,
50
- provider keys, MCP naming and packaging.
51
+ provider keys, MCP naming and packaging. For MCP naming, compare the SDK's
52
+ `isMcpToolName` (`dist/core/mcp-servers.js`) with the reserved names in
53
+ `src/session-tools.ts`. That function is not a root export, so no test
54
+ notices when the SDK reserves another name.
51
55
 
52
56
  3. Move the pin:
53
57
 
@@ -100,7 +104,8 @@ release, record the contract again:
100
104
  5. Update the versions named in [Installation](installation.md).
101
105
  6. Run the working-tree
102
106
  [Level 2 lifecycle](acceptance.md#level-2-setup-acceptance) with the real
103
- client for every scope.
107
+ client for every scope (`scripts/setup-acceptance.mjs`, see
108
+ [Tooling](acceptance.md#tooling)).
104
109
 
105
110
  ## Patch audit advisories
106
111
 
@@ -151,7 +156,10 @@ Steps:
151
156
  `## Unreleased`, so that the unreleased entries become the release entries.
152
157
  4. Run the Level 1 checks. `npm run --silent doctor -- --json` must report the
153
158
  new `packageVersion`.
154
- 5. Open one pull request named `chore: prepare v<x.y.z> release`.
159
+ 5. Run the working-tree
160
+ [Level 2 acceptance](acceptance.md#tooling) on the release candidate with
161
+ `scripts/setup-acceptance.mjs`, unless the release notes state its absence.
162
+ 6. Open one pull request named `chore: prepare v<x.y.z> release`.
155
163
 
156
164
  ## Publish a release
157
165
 
@@ -171,7 +179,7 @@ package contains exactly the tagged sources:
171
179
  git clone --branch v<x.y.z> --depth 1 https://github.com/plagor44-ux/pi-session-mcp.git /tmp/pi-session-mcp-release
172
180
  cd /tmp/pi-session-mcp-release
173
181
  npm ci
174
- npm publish --dry-run # review the file list: dist/, .pi-session-mcp-cli/, scripts/doctor.mjs, docs/
182
+ npm publish --dry-run # review the file list: dist/, .pi-session-mcp-cli/, scripts/doctor.mjs, docs/ without docs/superpowers/
175
183
  npm stage publish # the prepack script builds the server, setup and Doctor
176
184
  ```
177
185
 
@@ -210,7 +218,8 @@ After publishing:
210
218
  1. Run the published-release
211
219
  [Level 2 acceptance](acceptance.md#working-tree-and-published-release-variants)
212
220
  from a fresh clone of the tag and from the published npm package, and
213
- record it.
221
+ record it. `scripts/setup-acceptance.mjs` runs both
222
+ ([Tooling](acceptance.md#tooling)).
214
223
  2. Upgrade the installation as described in
215
224
  [Installation](installation.md#upgrade).
216
225
  3. Update the status section in [Acceptance](acceptance.md).
@@ -78,7 +78,7 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
78
78
  - Only exact `npm:<name>@<version>` specs from the operator's configuration load; the MCP client cannot name an extension.
79
79
  - Resolution never installs, and a different installed version fails closed. Only Pi's own user install (`<Pi agent dir>/npm/node_modules/<name>`, normally below `~/.pi/agent`) counts; a package installed elsewhere, for example with `npm i -g`, is reported as not installed.
80
80
  - `read-only` profiles never activate extension tools. At session start, after load and again after binding, no extension tool may shadow a built-in or granted tool; later registrations are covered in the residual risks.
81
- - Client prompts never dispatch extension commands, but extension input hooks can still see, rewrite or consume a prompt.
81
+ - Client prompts never dispatch extension commands, but extension input hooks can still see, rewrite or consume a prompt. They receive it with `source: "rpc"`, Pi's marker for input from a program, so a hook that reads the source sees that no human typed it. A hook that ignores the source treats it like any other input.
82
82
  - Each extension session gets its own model runtime, and close emits `session_shutdown`.
83
83
  - **stdout corruption:** every write through `process.stdout` or `console.log`, `console.info` and `console.debug` other than the MCP transport's is dropped. The stdout guard intercepts `process.stdout.write`, `process.stdout.end` and `console.log`, `console.info` and `console.debug`, which covers output from extensions and the SDK in the server process. stderr gets only `foreign_stdout_suppressed` with byte and write counts, never the content. Anything that writes file descriptor 1 directly is not covered; see the residual risks.
84
84
  - **Misleading annotations:** annotations are client hints only and never grant access.
@@ -119,7 +119,7 @@ behind `pi-cursor-sdk` starts processes of its own during turns (observed with
119
119
  Cursor, in a `read-only` profile too); none inherited the server's stdout in
120
120
  that run. Extensions may persist their own state outside Pi
121
121
  Session MCP's in-memory boundary. Extension tool names are checked for
122
- conflicts after loading and again after binding; a tool that an extension
122
+ conflicts and for unsafe or MCP-reserved names after loading and again after binding; a tool that an extension
123
123
  registers later, during a turn, is not re-checked and may replace a built-in
124
124
  tool, `read` included, because allowlisted extensions are trusted code. An extension may deactivate built-in or granted
125
125
  tools for its own session.
@@ -22,7 +22,7 @@ There are exactly eight tools. No `pi_turn_wait` tool or live provider/model inv
22
22
  ```json
23
23
  {
24
24
  "ok": true,
25
- "server": { "name": "pi-session-mcp", "version": "0.7.0" },
25
+ "server": { "name": "pi-session-mcp", "version": "0.7.1" },
26
26
  "configuration": {
27
27
  "fingerprint": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
28
28
  "reloadPolicy": "restart-required"
@@ -71,7 +71,7 @@ Public MCP errors are stable and sanitized. Their messages do not include inputs
71
71
  | `extension_not_installed` | Pi has no installed package for a configured extension. |
72
72
  | `extension_version_mismatch` | Pi's installed extension package has another version than the configured one. |
73
73
  | `extension_load_failed` | A configured extension has an invalid manifest, failed to load or failed to bind. |
74
- | `extension_tool_conflict` | An extension tool name collides with a built-in, MCP or other extension tool. |
74
+ | `extension_tool_conflict` | An extension tool name collides with a built-in, MCP or other extension tool, or is unsafe: not 1 to 64 letters, digits, `_` or `-` (not starting with `-`), or reserved for MCP (`mcp__` prefix, `list_mcp_resources`, `list_mcp_resource_templates`, `read_mcp_resource`). |
75
75
  | `pi_session_creation_failed` | Pi session construction failed without a more specific safe classification. |
76
76
  | `server_stopping` | Shutdown has begun, so a new session cannot be admitted. |
77
77
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-session-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Local stdio MCP server for controlled Pi coding-agent sessions",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -30,6 +30,7 @@
30
30
  "scripts/doctor.mjs",
31
31
  "pi-session-mcp.example.json",
32
32
  "docs/",
33
+ "!docs/superpowers/",
33
34
  "CHANGELOG.md",
34
35
  "SECURITY.md",
35
36
  "CONTRIBUTING.md"