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.
- package/.pi-session-mcp-cli/extension-spec.js +7 -2
- package/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/dist/extension-spec.js +7 -2
- package/dist/sdk-pi-adapter.js +3 -0
- package/dist/session-tools.d.ts +1 -1
- package/dist/session-tools.js +20 -4
- package/docs/acceptance.md +129 -2
- package/docs/adr/0006-pi-extension-allowlist.md +2 -2
- package/docs/architecture.md +1 -1
- package/docs/installation.md +7 -4
- package/docs/pi-extensions-design.md +24 -8
- package/docs/releasing.md +15 -6
- package/docs/threat-model.md +2 -2
- package/docs/tool-contracts.md +2 -2
- package/package.json +2 -1
- package/docs/superpowers/plans/2026-10-10-macos-setup.md +0 -1725
- package/docs/superpowers/plans/2026-10-11-pi-extensions.md +0 -2450
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export const MAX_PROFILE_EXTENSIONS = 8;
|
|
2
2
|
const MAX_SPEC_LENGTH = 256;
|
|
3
|
-
/**
|
|
4
|
-
|
|
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.
|
|
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`.
|
package/dist/extension-spec.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export const MAX_PROFILE_EXTENSIONS = 8;
|
|
2
2
|
const MAX_SPEC_LENGTH = 256;
|
|
3
|
-
/**
|
|
4
|
-
|
|
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/dist/sdk-pi-adapter.js
CHANGED
|
@@ -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
|
}
|
package/dist/session-tools.d.ts
CHANGED
|
@@ -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;
|
package/dist/session-tools.js
CHANGED
|
@@ -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
|
-
/**
|
|
6
|
-
|
|
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 (!
|
|
34
|
+
if (!isAcceptableExtensionToolName(name) || reserved.has(name))
|
|
19
35
|
return undefined;
|
|
20
36
|
reserved.add(name);
|
|
21
37
|
extensionTools.push(name);
|
package/docs/acceptance.md
CHANGED
|
@@ -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
|
|
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
|
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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.
|
package/docs/installation.md
CHANGED
|
@@ -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
|
|
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
|
|
383
|
-
|
|
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
|
|
119
|
-
|
|
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_
|
|
214
|
-
in an allowlist as a glob and a leading `+` or `-` as a modifier,
|
|
215
|
-
|
|
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
|
|
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.
|
|
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).
|
package/docs/threat-model.md
CHANGED
|
@@ -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.
|
package/docs/tool-contracts.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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"
|