pi-session-mcp 0.6.4 → 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/config.js +11 -1
- package/.pi-session-mcp-cli/doctor.js +22 -1
- package/.pi-session-mcp-cli/execution-profile.js +1 -0
- package/.pi-session-mcp-cli/extension-spec.js +26 -0
- package/.pi-session-mcp-cli/pi-extensions.js +67 -0
- package/CHANGELOG.md +62 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +2 -2
- package/SECURITY.md +1 -1
- package/dist/capabilities.d.ts +1 -0
- package/dist/capabilities.js +2 -1
- package/dist/client-adapters/mcp-verifier.js +5 -2
- package/dist/config.d.ts +1 -0
- package/dist/config.js +11 -1
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.js +22 -1
- package/dist/execution-profile.d.ts +3 -0
- package/dist/execution-profile.js +1 -0
- package/dist/extension-spec.d.ts +15 -0
- package/dist/extension-spec.js +26 -0
- package/dist/main.js +6 -2
- package/dist/pi-adapter.d.ts +1 -1
- package/dist/pi-adapter.js +4 -0
- package/dist/pi-extensions.d.ts +17 -0
- package/dist/pi-extensions.js +67 -0
- package/dist/sdk-pi-adapter.d.ts +24 -6
- package/dist/sdk-pi-adapter.js +256 -86
- package/dist/server.js +1 -0
- package/dist/session-tools.d.ts +9 -0
- package/dist/session-tools.js +41 -0
- package/dist/setup-command-guardian.js +29 -73
- package/dist/setup-main.js +2 -1
- package/dist/setup-ownership.js +18 -28
- package/dist/setup-platform.d.ts +57 -0
- package/dist/setup-platform.js +195 -0
- package/dist/setup-process.d.ts +6 -3
- package/dist/setup-process.js +85 -93
- package/dist/setup.js +2 -1
- package/dist/stdout-guard-install.d.ts +5 -0
- package/dist/stdout-guard-install.js +6 -0
- package/dist/stdout-guard.d.ts +20 -0
- package/dist/stdout-guard.js +72 -0
- package/docs/acceptance.md +343 -0
- package/docs/adr/0005-macos-setup-platform.md +84 -0
- package/docs/adr/0006-pi-extension-allowlist.md +58 -0
- package/docs/architecture.md +1 -1
- package/docs/client-setup-design.md +21 -9
- package/docs/client-setup.md +25 -10
- package/docs/doctor-design.md +7 -2
- package/docs/installation.md +125 -4
- package/docs/macos-setup-design.md +386 -0
- package/docs/pi-extensions-design.md +437 -0
- package/docs/releasing.md +16 -7
- package/docs/threat-model.md +54 -18
- package/docs/tool-contracts.md +8 -3
- package/package.json +2 -1
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# Pi extensions design
|
|
2
|
+
|
|
3
|
+
Status: approved by the owner on 2026-10-11 for implementation. Tracking issue:
|
|
4
|
+
[#34](https://github.com/plagor44-ux/pi-session-mcp/issues/34).
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
An operator can allowlist Pi extensions per execution profile. A profile that
|
|
9
|
+
names an extension gets an embedded Pi session in which that extension is
|
|
10
|
+
loaded with its full capabilities: providers, tools, hooks and system-prompt
|
|
11
|
+
changes. The first target is `pi-cursor-sdk`, which registers the `cursor`
|
|
12
|
+
provider; other provider extensions follow the same path.
|
|
13
|
+
|
|
14
|
+
Success means:
|
|
15
|
+
|
|
16
|
+
- a `cursor` profile works end to end through `pi_session_start`,
|
|
17
|
+
`pi_session_prompt`, `pi_turn_get` and `pi_session_close`, including two
|
|
18
|
+
parallel `cursor` sessions;
|
|
19
|
+
- Doctor reports a missing or mismatched extension offline, without running
|
|
20
|
+
extension code;
|
|
21
|
+
- profiles without `extensions` keep their session-creation path; their only
|
|
22
|
+
change is that prompts pass `expandPromptTemplates: false`, which has no
|
|
23
|
+
effect without templates, skills or extensions;
|
|
24
|
+
- stdout carries only MCP frames even when an extension writes to it;
|
|
25
|
+
- automated tests need no credentials and no network.
|
|
26
|
+
|
|
27
|
+
## Non-goals
|
|
28
|
+
|
|
29
|
+
- Extension sources other than npm packages installed by Pi (`git:`, local
|
|
30
|
+
paths, URLs).
|
|
31
|
+
- Installing or updating extensions. Pi owns that (`pi update --extensions`).
|
|
32
|
+
- Version ranges or "latest". A version is pinned exactly.
|
|
33
|
+
- Pi's built-in extensions (`mcp`, `codemode`, `tool-search`, `llama.cpp`).
|
|
34
|
+
pi-session-mcp keeps its own `mcpServers` bridge.
|
|
35
|
+
- Extension CLI flags (for example `--cursor-runtime`) and extension UI. Flags
|
|
36
|
+
keep their defaults; UI calls go to the SDK's no-op UI.
|
|
37
|
+
- Extension slash commands triggered by client prompt text.
|
|
38
|
+
- Process isolation of extension code. Extensions run in the server process.
|
|
39
|
+
|
|
40
|
+
## Owner decisions
|
|
41
|
+
|
|
42
|
+
| Question | Decision |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| Capability scope | Full: providers, tools, hooks, system-prompt changes. |
|
|
45
|
+
| Naming and pinning | `npm:<name>@<exact version>`, resolved from Pi's user install; any other installed version fails closed. |
|
|
46
|
+
| Granularity | Per execution profile. |
|
|
47
|
+
| `read-only` profiles | Extensions allowed; the model's tools stay read/grep/find/ls. |
|
|
48
|
+
| Integration approach | Pi SDK session lifecycle API (`createAgentSessionServices`, `createAgentSessionFromServices`, `AgentSessionRuntime`). |
|
|
49
|
+
|
|
50
|
+
## Current behavior and constraints
|
|
51
|
+
|
|
52
|
+
`createPhase0ResourceLoader` (`src/sdk-pi-adapter.ts`) builds a
|
|
53
|
+
`DefaultResourceLoader` with `noExtensions: true`, no skills, templates or
|
|
54
|
+
themes, and a disabled system prompt. `SdkPiSessionAdapter.create` resolves
|
|
55
|
+
provider, model, authentication and availability against one `ModelRuntime`
|
|
56
|
+
shared by all sessions, and only then builds the resource loader and calls
|
|
57
|
+
`createAgentSession`.
|
|
58
|
+
|
|
59
|
+
Facts about Pi SDK 1.1.0 that shape this design, verified against the
|
|
60
|
+
installed package:
|
|
61
|
+
|
|
62
|
+
1. An extension's `registerProvider` calls are queued at load time and applied
|
|
63
|
+
to the `ModelRuntime` when the session's extension runner binds its core,
|
|
64
|
+
which happens inside session construction. A provider check that runs before
|
|
65
|
+
the session exists does not see an extension provider.
|
|
66
|
+
`createAgentSessionServices` applies the queued registrations to the
|
|
67
|
+
services' runtime, so the provider is visible before the session exists.
|
|
68
|
+
2. Provider registrations are global to a `ModelRuntime` instance; the last
|
|
69
|
+
registration for an id wins. `pi-cursor-sdk` fails sibling sessions that
|
|
70
|
+
share a runtime with `CURSOR_PROVIDER_OWNERSHIP_ERROR`. An extension session
|
|
71
|
+
therefore needs its own runtime.
|
|
72
|
+
3. `createAgentSession` does not emit `session_start`; only
|
|
73
|
+
`session.bindExtensions()` does. `pi-cursor-sdk` refuses to stream until it
|
|
74
|
+
has seen `session_start`.
|
|
75
|
+
4. `AgentSession.dispose()` does not emit `session_shutdown`;
|
|
76
|
+
`AgentSessionRuntime.dispose()` does. `pi-cursor-sdk` registers cleanup on
|
|
77
|
+
`session_shutdown`.
|
|
78
|
+
5. `session.prompt(text)` dispatches extension slash commands unless it is
|
|
79
|
+
called with `expandPromptTemplates: false`.
|
|
80
|
+
6. With an explicit `tools` list, every tool, extension tools included, is
|
|
81
|
+
filtered by the session's allowlist.
|
|
82
|
+
7. `noExtensions: true` suppresses settings and auto-discovered extensions but
|
|
83
|
+
still loads `additionalExtensionPaths`.
|
|
84
|
+
8. `DefaultPackageManager.resolve()` runs `npm install` for missing or
|
|
85
|
+
mismatched npm packages, and `getInstalledPath(source, "user")` falls back
|
|
86
|
+
to a synchronous `npm root -g` on a miss and accepts legacy global
|
|
87
|
+
installs; neither is side-effect free.
|
|
88
|
+
9. `pi-cursor-sdk` and `@cursor/sdk` call `console.log`. In a stdio MCP server
|
|
89
|
+
such output corrupts the protocol stream.
|
|
90
|
+
|
|
91
|
+
The relevant SDK functions are root exports of
|
|
92
|
+
`@earendil-works/pi-coding-agent`: `createAgentSessionServices`,
|
|
93
|
+
`createAgentSessionFromServices`, `createAgentSessionRuntime`,
|
|
94
|
+
`AgentSessionRuntime`, `getAgentDir` and
|
|
95
|
+
`ModelRuntime`. No internal module is imported.
|
|
96
|
+
|
|
97
|
+
## Design
|
|
98
|
+
|
|
99
|
+
### Configuration
|
|
100
|
+
|
|
101
|
+
Every execution profile accepts an optional `extensions` array:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
"code-cursor": {
|
|
105
|
+
"default": false,
|
|
106
|
+
"permissionProfile": "coding",
|
|
107
|
+
"provider": "cursor",
|
|
108
|
+
"model": "<cursor-model-id>",
|
|
109
|
+
"thinkingLevel": "high",
|
|
110
|
+
"extensions": ["npm:pi-cursor-sdk@0.5.3"]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Rules, enforced by `configSchema`:
|
|
115
|
+
|
|
116
|
+
- Each entry matches `npm:<name>@<version>`. `<name>` is an npm package name,
|
|
117
|
+
optionally scoped (`@scope/name`), lowercase. `<version>` is an exact semver
|
|
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.
|
|
124
|
+
- At most 8 entries per profile. A package name appears at most once per
|
|
125
|
+
profile.
|
|
126
|
+
- The field is allowed on `read-only` and `coding` profiles, including the
|
|
127
|
+
default profile.
|
|
128
|
+
- An invalid entry fails the whole configuration with
|
|
129
|
+
`invalid_execution_profile_config`, like every other schema violation.
|
|
130
|
+
|
|
131
|
+
The parsed profile keeps `extensions` as a frozen array of
|
|
132
|
+
`{ name, version }` pairs plus the original spec string. The configuration
|
|
133
|
+
fingerprint already covers the field because it hashes the file bytes.
|
|
134
|
+
|
|
135
|
+
### Extension resolution
|
|
136
|
+
|
|
137
|
+
A new module `src/pi-extensions.ts` owns resolution. It is used by the adapter
|
|
138
|
+
and by Doctor.
|
|
139
|
+
|
|
140
|
+
`resolveExtension(spec, dependencies)`:
|
|
141
|
+
|
|
142
|
+
1. Computes Pi's user install location `<agent dir>/npm/node_modules/<name>`,
|
|
143
|
+
with the agent directory from `getAgentDir()`. It never calls the SDK
|
|
144
|
+
package manager, never spawns npm and never installs. A package installed
|
|
145
|
+
anywhere else counts as not installed.
|
|
146
|
+
2. No path: `extension_not_installed`.
|
|
147
|
+
3. Reads `<dir>/package.json`. Unreadable or invalid JSON:
|
|
148
|
+
`extension_load_failed`. A `version` that differs from the pinned version:
|
|
149
|
+
`extension_version_mismatch`.
|
|
150
|
+
4. Reads `pi.extensions` from the manifest. It must be a non-empty array of
|
|
151
|
+
relative paths. Every entry must resolve, after symlink resolution, to a
|
|
152
|
+
file inside the real package directory. Otherwise: `extension_load_failed`.
|
|
153
|
+
`pi.extensions` entries must be plain relative file paths; globs and
|
|
154
|
+
overrides, which Pi accepts, fail with `extension_load_failed`.
|
|
155
|
+
5. Returns the real package directory. The SDK receives that directory, not
|
|
156
|
+
the `npm:` source, so it cannot start a temporary install.
|
|
157
|
+
|
|
158
|
+
Dependencies (install-location lookup, file reads, real-path resolution, stat) are
|
|
159
|
+
injectable for tests.
|
|
160
|
+
|
|
161
|
+
### Session creation
|
|
162
|
+
|
|
163
|
+
Profiles without `extensions` keep today's path unchanged: shared runtime,
|
|
164
|
+
`createPhase0ResourceLoader`, `createAgentSession`.
|
|
165
|
+
|
|
166
|
+
For a profile with `extensions`, `SdkPiSessionAdapter.create` runs:
|
|
167
|
+
|
|
168
|
+
1. **Resolve.** Resolve every extension as above.
|
|
169
|
+
2. **Runtime.** Create a fresh `ModelRuntime` for this session through the
|
|
170
|
+
existing `modelRuntimeFactory`. It reads Pi's normal authentication store.
|
|
171
|
+
3. **Services.** Call `createAgentSessionServices` with that runtime, the
|
|
172
|
+
workspace as `cwd` and `agentDir`, an in-memory untrusted settings manager,
|
|
173
|
+
and resource-loader options identical to `createPhase0ResourceLoader` plus
|
|
174
|
+
`additionalExtensionPaths` set to the resolved package directories. Any
|
|
175
|
+
services diagnostic of type `error`, or an exception, is
|
|
176
|
+
`extension_load_failed`.
|
|
177
|
+
4. **Checks.** Run the existing provider, model, `checkAuth`, `getAuth` and
|
|
178
|
+
`getAvailable` checks against the services' runtime, with the existing
|
|
179
|
+
error codes and deadlines.
|
|
180
|
+
5. **External MCP tools.** Connect configured `mcpServers` as today.
|
|
181
|
+
6. **Session.** Call `createAgentSessionFromServices` with the model, thinking
|
|
182
|
+
level, an in-memory session manager, the tool list (see Tools) and the MCP
|
|
183
|
+
custom tools. The session is wrapped so that it can be disposed through
|
|
184
|
+
`AgentSessionRuntime` (see Lifecycle).
|
|
185
|
+
7. **Bind.** Call `session.bindExtensions({ mode: "print" })` without a UI
|
|
186
|
+
context, so the SDK's no-op UI applies and `session_start` is emitted. A
|
|
187
|
+
failure is `extension_load_failed`.
|
|
188
|
+
8. **Postchecks.** Model, thinking level and active tools. For extension
|
|
189
|
+
sessions no active tool may be outside the composed allowlist, but fewer
|
|
190
|
+
active tools are allowed: an extension may deactivate tools, as
|
|
191
|
+
`pi-cursor-sdk` does with its own tool in `session_start`. Sessions without
|
|
192
|
+
extensions keep the exact-equality check as today.
|
|
193
|
+
|
|
194
|
+
Step 3, step 6 and step 7 are each bounded by the existing session-creation
|
|
195
|
+
deadline (`SESSION_CREATION_TIMEOUT_MS`). A failure after step 6 disposes the
|
|
196
|
+
session through the lifecycle path, which emits `session_shutdown`. A session
|
|
197
|
+
that resolves after its deadline is disposed the same way, never admitted.
|
|
198
|
+
Extension code loaded in step 3 cannot be unloaded; a failure between step 3
|
|
199
|
+
and step 6 leaves no session to dispose.
|
|
200
|
+
|
|
201
|
+
### Tools, hooks and prompts
|
|
202
|
+
|
|
203
|
+
- **`read-only`.** The tool list is `read`, `grep`, `find`, `ls`, exactly as
|
|
204
|
+
today. Extension tools are registered but inactive. An extension that calls
|
|
205
|
+
`setActiveTools` cannot activate a tool outside the list; a contract test
|
|
206
|
+
proves this against the real SDK. If the SDK does not enforce it, the
|
|
207
|
+
adapter adds its own check before the implementation is accepted.
|
|
208
|
+
- **`coding`.** The tool list is the built-in coding tools, the granted MCP
|
|
209
|
+
tools, and every tool registered by the profile's extensions. The adapter
|
|
210
|
+
reads the registered tool names from the services' resource loader after
|
|
211
|
+
step 3.
|
|
212
|
+
- **Name conflicts, both profiles.** An extension tool whose name equals a
|
|
213
|
+
built-in tool (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`), a
|
|
214
|
+
granted MCP tool or a tool of another extension fails with
|
|
215
|
+
`extension_tool_conflict`. This also stops an extension from replacing a
|
|
216
|
+
built-in tool inside a `read-only` profile.
|
|
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.
|
|
228
|
+
The check runs after load and again after binding, because a `session_start`
|
|
229
|
+
handler can register a tool during `bindExtensions`. If the registered tool
|
|
230
|
+
set then differs from the one the session was created with, the start fails
|
|
231
|
+
with `extension_tool_conflict`. Tools an extension registers later, during a
|
|
232
|
+
turn, are not re-checked; allowlisted extensions are trusted code.
|
|
233
|
+
- **Hook errors while binding.** A hook error reported while binding fails the
|
|
234
|
+
start with `extension_load_failed`. Failure cleanup disposes the session
|
|
235
|
+
runtime, bounded by the creation deadline, concurrently with closing the
|
|
236
|
+
granted MCP connections.
|
|
237
|
+
- **MCP servers registered by extensions.** An extension that registers an MCP
|
|
238
|
+
server while loading or starting fails the start with
|
|
239
|
+
`extension_load_failed`, because the built-in MCP extension is not loaded.
|
|
240
|
+
- **Hooks.** All extension hooks run in both permission profiles, including
|
|
241
|
+
`before_agent_start` system-prompt changes, `tool_call`, `tool_result`,
|
|
242
|
+
`before_provider_request`, `before_provider_headers` and `context`.
|
|
243
|
+
- **Prompts.** Every `session.prompt()` call, for every profile, passes
|
|
244
|
+
`expandPromptTemplates: false`. Client prompt text never dispatches an
|
|
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`.
|
|
250
|
+
- **UI and flags.** No UI context is bound; extension UI calls return the
|
|
251
|
+
SDK's no-op results. `extensionFlagValues` is empty, so extension flags keep
|
|
252
|
+
their defaults.
|
|
253
|
+
|
|
254
|
+
### Lifecycle and close
|
|
255
|
+
|
|
256
|
+
The handle of an extension session owns the `AgentSessionRuntime` that wraps
|
|
257
|
+
its services and session. `close`, the close cleanup after an abort deadline,
|
|
258
|
+
registry shutdown and late-start disposal call `AgentSessionRuntime.dispose()`,
|
|
259
|
+
which emits `session_shutdown` and disposes the session. Existing deadlines,
|
|
260
|
+
idempotent close and tombstones do not change. MCP connections close
|
|
261
|
+
concurrently with the runtime, as they close concurrently with the session
|
|
262
|
+
today; the handle's disposal completes when both have settled.
|
|
263
|
+
|
|
264
|
+
How the session is wrapped in an `AgentSessionRuntime` uses only the exported
|
|
265
|
+
`createAgentSessionRuntime` and `AgentSessionRuntime` API. The exact wiring is
|
|
266
|
+
fixed in the implementation plan and proven by a contract test that observes
|
|
267
|
+
`session_shutdown`.
|
|
268
|
+
|
|
269
|
+
### stdout guard
|
|
270
|
+
|
|
271
|
+
A new module `src/stdout-guard.ts` runs first in `src/main.ts`, before any
|
|
272
|
+
other import with side effects:
|
|
273
|
+
|
|
274
|
+
- It captures the original `process.stdout.write`.
|
|
275
|
+
- It builds a write-through `Proxy` over the real stdout: `write` and `end`
|
|
276
|
+
resolve to the original bound methods and every other function is bound to
|
|
277
|
+
the real stream, so queued frames reach stdout synchronously. `main` passes
|
|
278
|
+
`new StdioServerTransport(process.stdin, thatProxy)` to `serveStdio`
|
|
279
|
+
through its `transport` option.
|
|
280
|
+
- It also patches `process.stdout.end` for foreign callers: a foreign `end`
|
|
281
|
+
neither leaks content nor closes the real stdout.
|
|
282
|
+
- It replaces `process.stdout.write` and `console.log`, `console.info` and
|
|
283
|
+
`console.debug`. Their output is dropped. The guard writes at most one
|
|
284
|
+
structured stderr line per second, carrying the bytes and the number of
|
|
285
|
+
writes suppressed since its previous line:
|
|
286
|
+
`{"level":"warning","event":"foreign_stdout_suppressed","bytes":<n>,"writes":<m>}`.
|
|
287
|
+
Pending counts are flushed on shutdown. The content is never written
|
|
288
|
+
anywhere, because it may contain prompts or secrets.
|
|
289
|
+
|
|
290
|
+
The guard is active for every server start, not only with extensions. A child
|
|
291
|
+
process started with inherited stdio writes to file descriptor 1 directly and
|
|
292
|
+
cannot be intercepted in Node.js; this is a documented residual risk.
|
|
293
|
+
|
|
294
|
+
### Capabilities projection
|
|
295
|
+
|
|
296
|
+
`pi_capabilities_get` adds `extensions` to every execution profile: an array of
|
|
297
|
+
the configured spec strings, empty for profiles without extensions. The field
|
|
298
|
+
is path-free and secret-free. `capabilitiesSchema` and
|
|
299
|
+
`docs/tool-contracts.md` change accordingly.
|
|
300
|
+
|
|
301
|
+
### Doctor
|
|
302
|
+
|
|
303
|
+
Doctor gains two checks after the workspace checks, one per profile and
|
|
304
|
+
extension, with the subject `<profile>:<package name>`:
|
|
305
|
+
|
|
306
|
+
- `extension_missing` (error) when the resolver finds no installed package;
|
|
307
|
+
- `extension_version_mismatch` (error) when the installed manifest version
|
|
308
|
+
differs, or the manifest is unreadable.
|
|
309
|
+
|
|
310
|
+
Doctor reads manifests only; it never imports extension code. It stays
|
|
311
|
+
offline. `setup --verify` includes Doctor and therefore these checks.
|
|
312
|
+
|
|
313
|
+
### Diagnostics
|
|
314
|
+
|
|
315
|
+
The adapter's diagnostic sink gains the stages `extension_resolve`,
|
|
316
|
+
`extension_load` and `extension_bind`. A diagnostic carries the profile alias
|
|
317
|
+
and the error code; a resolution failure also carries the failing extension as
|
|
318
|
+
`<name>@<version>`. Disposal stays best effort and reports nothing, as today. It never carries a path, an extension error message, or prompt content.
|
|
319
|
+
|
|
320
|
+
## Failure semantics
|
|
321
|
+
|
|
322
|
+
New `PiSessionCreationErrorCode` values, all fail closed before a session is
|
|
323
|
+
admitted:
|
|
324
|
+
|
|
325
|
+
| Code | Message | When |
|
|
326
|
+
| --- | --- | --- |
|
|
327
|
+
| `extension_not_installed` | Configured Pi extension is not installed | Pi has no user install of the package. |
|
|
328
|
+
| `extension_version_mismatch` | Configured Pi extension version is not installed | The installed version differs from the pinned version. |
|
|
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`. |
|
|
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*). |
|
|
331
|
+
|
|
332
|
+
Existing codes keep their meaning. An extension provider that is unknown after
|
|
333
|
+
loading is `unknown_provider`.
|
|
334
|
+
|
|
335
|
+
Known limit: `pi-cursor-sdk` returns a placeholder key when no Cursor key is
|
|
336
|
+
stored, so `checkAuth` and `getAuth` succeed and the first prompt fails with
|
|
337
|
+
`turn_failed`. Detecting the placeholder would depend on extension internals;
|
|
338
|
+
the limit is documented instead.
|
|
339
|
+
|
|
340
|
+
## Trust boundary and limits
|
|
341
|
+
|
|
342
|
+
- An allowlisted extension is code the operator trusts. It runs in the server
|
|
343
|
+
process with the user's full rights and may start processes, use the
|
|
344
|
+
network, and read and write files. `pi-cursor-sdk` writes below
|
|
345
|
+
`~/.pi/agent`, `~/.cursor/projects/` and `<workspace>/.pi/`.
|
|
346
|
+
- Pinning the version prevents silent updates. It does not protect against a
|
|
347
|
+
modified installed package directory, which is user-writable code like any
|
|
348
|
+
other.
|
|
349
|
+
- `read-only` limits the model's tool set, not the extension's code.
|
|
350
|
+
- pi-session-mcp keeps its own state in memory only. Extensions may persist
|
|
351
|
+
their own state outside its control. AGENTS.md and `docs/architecture.md`
|
|
352
|
+
state this next to the product boundary.
|
|
353
|
+
- The MCP client remains one trusted local peer. It selects profile aliases
|
|
354
|
+
only and cannot name an extension.
|
|
355
|
+
|
|
356
|
+
## Testing
|
|
357
|
+
|
|
358
|
+
Automated tests use no credentials and no network.
|
|
359
|
+
|
|
360
|
+
- **Config** (`test/config.test.ts`): valid scoped and unscoped specs;
|
|
361
|
+
rejection of ranges, tags, `git:`, paths, duplicates and more than 8
|
|
362
|
+
entries; the field on read-only and coding profiles.
|
|
363
|
+
- **Resolution** (`test/pi-extensions.test.ts`), with an injected install-location
|
|
364
|
+
lookup and file system: not installed, version mismatch, invalid manifest,
|
|
365
|
+
missing `pi.extensions`, an entry or symlink escaping the package directory,
|
|
366
|
+
and the guarantee that `resolve()` and install methods are never called.
|
|
367
|
+
- **Doctor** (`test/doctor.test.ts`): both new checks, check order, and that
|
|
368
|
+
no extension module is imported.
|
|
369
|
+
- **Adapter** (`test/sdk-pi-adapter.test.ts`), with injected services and
|
|
370
|
+
session factories:
|
|
371
|
+
- step order, and the provider being visible only after services;
|
|
372
|
+
- two sessions getting two runtimes;
|
|
373
|
+
- `bindExtensions` being called;
|
|
374
|
+
- `session_shutdown` on close, on a failure after services, and on a late
|
|
375
|
+
start after the deadline;
|
|
376
|
+
- tool lists for read-only and coding, and `extension_tool_conflict`;
|
|
377
|
+
- `expandPromptTemplates: false` and `source: "rpc"` on every prompt;
|
|
378
|
+
- profiles without extensions taking the unchanged path.
|
|
379
|
+
- **stdout guard** (`test/stdout-guard.test.ts`): foreign `write` and
|
|
380
|
+
`console.*` output does not reach stdout, transport writes do, and the
|
|
381
|
+
stderr event carries only a byte count. Regressions cover synchronous
|
|
382
|
+
delivery of every queued transport frame and a foreign `end` that must not
|
|
383
|
+
close stdout.
|
|
384
|
+
- **SDK contract with the real SDK** (`test/sdk-extension-contract.test.ts`):
|
|
385
|
+
a fixture extension in `test/fixtures/pi-extension-fixture`, installed into a
|
|
386
|
+
temporary Pi agent directory, registers:
|
|
387
|
+
- a provider with a canned, network-free stream;
|
|
388
|
+
- a tool and a slash command;
|
|
389
|
+
- a `setActiveTools` attempt;
|
|
390
|
+
- `console.log` and `process.stdout.write` calls;
|
|
391
|
+
- `session_start` and `session_shutdown` hooks;
|
|
392
|
+
- a `before_agent_start` hook that repeats the widening attempt (adding
|
|
393
|
+
`bash`, `write`, `edit` and the fixture tool) on every prompt.
|
|
394
|
+
|
|
395
|
+
The test proves that:
|
|
396
|
+
- the provider resolves and a prompt completes;
|
|
397
|
+
- read-only keeps the fixture tool and the built-in write tools inactive,
|
|
398
|
+
including against the mid-turn `before_agent_start` widening attempt;
|
|
399
|
+
- the slash command is not dispatched;
|
|
400
|
+
- both lifecycle events are observed.
|
|
401
|
+
- **stdio end to end** (`test/process-extension.test.ts`): the built server runs
|
|
402
|
+
with a configuration that uses the fixture extension. Every stdout frame
|
|
403
|
+
parses, and stderr contains `foreign_stdout_suppressed`.
|
|
404
|
+
|
|
405
|
+
## Documentation and governance
|
|
406
|
+
|
|
407
|
+
- ADR 0006 records the decision and its trust boundary.
|
|
408
|
+
- `docs/installation.md`: the `extensions` rule in the configuration section.
|
|
409
|
+
- `docs/tool-contracts.md`: the capabilities field and the new error codes.
|
|
410
|
+
- `docs/threat-model.md` and `docs/architecture.md`: the trust boundary, the
|
|
411
|
+
stdout guard and its residual risk, and extension-owned persistence.
|
|
412
|
+
- `docs/doctor-design.md`: the two new checks.
|
|
413
|
+
- A new section "Pi extensions" in `docs/installation.md` with the
|
|
414
|
+
`pi-cursor-sdk` example and the placeholder-key limit.
|
|
415
|
+
- AGENTS.md: the product-boundary sentence names extension-owned persistence
|
|
416
|
+
and the allowlist rule.
|
|
417
|
+
- `docs/acceptance.md`: the manual live check below.
|
|
418
|
+
- CHANGELOG.md: an entry under Unreleased.
|
|
419
|
+
|
|
420
|
+
## Acceptance on real hardware
|
|
421
|
+
|
|
422
|
+
Manual, not in CI, recorded in `docs/acceptance.md`:
|
|
423
|
+
|
|
424
|
+
1. `pi-cursor-sdk@0.5.3` installed by Pi with a stored Cursor key.
|
|
425
|
+
2. A `code-cursor` profile: `pi_session_start`, a "PONG" prompt, `pi_turn_get`
|
|
426
|
+
completed, `pi_session_close`.
|
|
427
|
+
3. Two parallel `cursor` sessions, each completing a prompt.
|
|
428
|
+
4. After close, no Cursor child process remains, and stderr shows no
|
|
429
|
+
`foreign_stdout_suppressed` storm.
|
|
430
|
+
5. Record how the Cursor local runtime starts its child processes (inherited or
|
|
431
|
+
piped stdio).
|
|
432
|
+
|
|
433
|
+
## Delivery
|
|
434
|
+
|
|
435
|
+
One branch (`feat/pi-extensions`) and one pull request for #34, with commits
|
|
436
|
+
grouped by concern. The owner's live configuration gets a `cursor` profile only
|
|
437
|
+
after the acceptance above.
|
package/docs/releasing.md
CHANGED
|
@@ -12,7 +12,7 @@ does not authorize any of them.
|
|
|
12
12
|
[ADR 0003](adr/0003-post-mvp-maintenance.md) sets the rule:
|
|
13
13
|
|
|
14
14
|
- A change that keeps the public MCP contract ships as a patch release
|
|
15
|
-
(`0.
|
|
15
|
+
(for example `0.7.x`). This includes Pi SDK updates.
|
|
16
16
|
- A change to the public contract needs a minor release and its own decision.
|
|
17
17
|
|
|
18
18
|
The public contract is the set of eight tools with their fields, states and
|
|
@@ -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
|
@@ -30,23 +30,36 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
|
|
|
30
30
|
equality with a bounded direct MCP initialize, `tools/list`, and
|
|
31
31
|
`pi_capabilities_get({})` call. It never starts a session or invokes
|
|
32
32
|
a provider; the verifier cleans up its process.
|
|
33
|
-
- **Concurrent setup corruption:**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
- **Concurrent setup corruption:** Durable ownership transactions hold a
|
|
34
|
+
stable, never-renamed regular file through a kernel lock on an explicitly
|
|
35
|
+
supported local filesystem:
|
|
36
|
+
- on Linux, through the trusted util-linux `flock`, on ext2/3/4, XFS, Btrfs,
|
|
37
|
+
tmpfs, overlayfs, ZFS, F2FS, UBIFS, or bcachefs;
|
|
38
|
+
- on macOS, through `O_EXLOCK` at open, on the APFS volume type of `/`.
|
|
39
|
+
|
|
40
|
+
There is no clock-based stale recovery. A transaction guardian inherits that
|
|
41
|
+
locked file description, launches each mutating client command in its own
|
|
42
|
+
process group, and retains the fence across parent death until a determinate
|
|
43
|
+
process-table scan confirms that no non-zombie group member remains. The
|
|
44
|
+
scan reads `/proc` on Linux and the trusted system `/bin/ps` on macOS; a
|
|
45
|
+
`ps` sample that fails, has an unexpected line or does not list the scanning
|
|
46
|
+
process is indeterminate. On macOS the proof rests on `/bin/ps` on the
|
|
47
|
+
sealed, SIP-protected system volume, together with the uid and mode check
|
|
48
|
+
that also guards `flock`. The macOS lock relies on the documented `O_EXLOCK`
|
|
49
|
+
behavior of open(2): flock(2) semantics on the open file description, and
|
|
50
|
+
`EOPNOTSUPP` on a filesystem without locking, which fails closed.
|
|
51
|
+
Indeterminate cleanup retains the fence and fails closed. A missing or
|
|
52
|
+
untrusted `flock` or `ps`, unsupported filesystems, symlinks, inode
|
|
53
|
+
mismatch, and raw lock errors are reduced to stable codes. Directory
|
|
54
|
+
creation and atomic state replacement are fsynced through their parent
|
|
55
|
+
directory before success. A mutation already accepted by an external CLI or
|
|
56
|
+
configuration backend cannot be rolled back automatically and remains
|
|
57
|
+
`pending` for reconciliation.
|
|
58
|
+
- **Unsupported process-group guarantees:** Setup runs on Linux and macOS
|
|
59
|
+
only. Every other platform fails closed before client spawn because no
|
|
60
|
+
tested kernel fence and process-group proof exist for it. On both supported
|
|
61
|
+
platforms, the fence does not cover descendants that leave the process
|
|
62
|
+
group, and it does not promise PID reaping.
|
|
50
63
|
|
|
51
64
|
- **Workspace escape:** callers can request configured workspace aliases only; validated configuration maps aliases to resolved paths. There is no arbitrary `cwd` input.
|
|
52
65
|
- **Execution-selection escalation:** callers cannot supply raw provider, model, thinking level, permission profile, or credentials. A configured execution-profile alias binds all of those values; exactly one configured default is `read-only`. The legacy `profile` input is rejected rather than silently reinterpreted.
|
|
@@ -55,12 +68,19 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
|
|
|
55
68
|
- **Thinking-level clamping:** SDK capability handling can alter a requested level. The adapter compares the effective `session.thinkingLevel` after creation and disposes on a mismatch.
|
|
56
69
|
- **Orphaned SDK session:** a post-creation mismatch or later registry failure could otherwise leave a live Pi handle. The adapter holds it locally until all postconditions pass, best-effort disposes on each later failure, and the registry admits only verified handles.
|
|
57
70
|
- **Excess capability:** the execution profile selects a permission-derived tool allowlist. Read-only permits only `read`, `grep`, `find`, and `ls`; coding remains high impact.
|
|
58
|
-
- **Project resource code execution:** Pi resource loading disables
|
|
71
|
+
- **Project resource code execution:** Pi resource loading disables extension discovery, skills, prompts, and themes; only a profile's allowlisted extensions load (see below). Non-executable `AGENTS.md` context remains untrusted instruction content, never authorization.
|
|
59
72
|
- **Concurrent state corruption:** a per-session queue serializes prompt admission and close; concurrent abort callers share one attempt for the active turn. Closing is terminal, settles an active turn, and disposes once.
|
|
60
73
|
- **Secret/prompt disclosure:** stdout is protocol-only. Stderr diagnostics use stable code and safe context only. Public metadata is whitelisted and omits auth objects/sources, credential paths, headers, base URLs, complete model objects, SDK IDs, raw provider errors, prompts, transcripts, and tool data. Completed `assistantText` can still repeat model-visible data and is potentially sensitive.
|
|
61
74
|
- **Capability-enumeration disclosure:** the trusted local MCP peer can read configured workspace aliases and execution-profile provider/model intent. Discovery omits workspace paths, authentication state, SDK state, sessions, turns, prompts, transcripts, and tool data. Operators must choose aliases that are suitable for this trusted-peer boundary.
|
|
62
75
|
- **Stale capability assumptions:** discovery reads configuration only. It makes no provider, authentication, filesystem, or network probe and cannot prove that a configured provider/model is available. Session creation keeps the existing exact runtime checks.
|
|
63
76
|
- **Availability:** input length is bounded; profile-specific authentication/catalog work and session creation have 5,000 ms and 10,000 ms deadlines. Preflight, abort, close cleanup, registry shutdown, and process shutdown (signal or stdin EOF) have 5,000 ms, 5,000 ms, 5,000 ms, 12,000 ms, and 15,000 ms server-side deadlines. Active sessions retain 20 turns and 64 KiB UTF-8 of final text per turn. The registry rejects starts after shutdown begins and disposes a late-created handle. The process shutdown path explicitly exits the process after its cleanup deadline, but cannot prove graceful termination of a blocked provider operation.
|
|
77
|
+
- **Allowlisted Pi extensions:** a profile's `extensions` load operator-trusted code into the server process with the user's full rights; it may start processes, use the network and write files (for example `pi-cursor-sdk` below `~/.pi/agent`, `~/.cursor/projects/` and `<workspace>/.pi/`). Mitigations:
|
|
78
|
+
- Only exact `npm:<name>@<version>` specs from the operator's configuration load; the MCP client cannot name an extension.
|
|
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
|
+
- `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. 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
|
+
- Each extension session gets its own model runtime, and close emits `session_shutdown`.
|
|
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.
|
|
64
84
|
- **Misleading annotations:** annotations are client hints only and never grant access.
|
|
65
85
|
- **Shutdown orphaning:** `SIGINT`, `SIGTERM`, and stdin EOF invoke the same bounded registry cleanup and transport close, so a client that only closes stdin does not leave sessions or their MCP children running. Handles are disposed at most once; late promises are ignored after removal. Explicit process exit prevents local shutdown from hanging indefinitely, but SDK limitations can leave external work unconfirmed or abruptly cut off.
|
|
66
86
|
|
|
@@ -87,3 +107,19 @@ change public terminal errors; cancellation and close still take precedence.
|
|
|
87
107
|
Aliases and profile definitions are trusted administrator configuration, not a filesystem or provider-security sandbox. A locally available model may later be unreachable; no live provider request is made during selection. Symlinks and Pi tools can reach resources permitted to the OS identity. `AGENTS.md` can influence model behavior. The number of active sessions and the duration of an already accepted turn remain unbounded. Stdio peer authentication, quotas, persistent recovery, provider hard-kill capability, dependency integrity policy, and OS/filesystem isolation remain outside the verified boundary. Coding-profile acceptance checks behavior in a selected fixture; it does not prove OS-level isolation.
|
|
88
108
|
The trusted same-UID operator can unlink or replace the setup lock path between
|
|
89
109
|
checks and must not mutate the ownership directory during a transaction.
|
|
110
|
+
|
|
111
|
+
An allowlisted extension is trusted code; pinning its version does not detect
|
|
112
|
+
modification of the installed package directory. `read-only` limits the
|
|
113
|
+
model's tools, not extension code or hooks. Anything that writes file
|
|
114
|
+
descriptor 1 directly reaches the MCP stream, because Node.js cannot intercept
|
|
115
|
+
it: in-process writes such as `fs.writeSync(1, …)` or native code, and child
|
|
116
|
+
processes that an extension starts with inherited stdio. The Cursor runtime
|
|
117
|
+
behind `pi-cursor-sdk` starts processes of its own during turns (observed with
|
|
118
|
+
`pi-cursor-sdk@0.5.3`: `git`, `ssh`, `gh` and the MCP servers configured for
|
|
119
|
+
Cursor, in a `read-only` profile too); none inherited the server's stdout in
|
|
120
|
+
that run. Extensions may persist their own state outside Pi
|
|
121
|
+
Session MCP's in-memory boundary. Extension tool names are checked for
|
|
122
|
+
conflicts and for unsafe or MCP-reserved names after loading and again after binding; a tool that an extension
|
|
123
|
+
registers later, during a turn, is not re-checked and may replace a built-in
|
|
124
|
+
tool, `read` included, because allowlisted extensions are trusted code. An extension may deactivate built-in or granted
|
|
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.
|
|
25
|
+
"server": { "name": "pi-session-mcp", "version": "0.7.1" },
|
|
26
26
|
"configuration": {
|
|
27
27
|
"fingerprint": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
28
28
|
"reloadPolicy": "restart-required"
|
|
@@ -34,12 +34,13 @@ There are exactly eight tools. No `pi_turn_wait` tool or live provider/model inv
|
|
|
34
34
|
"permissionProfile": "read-only",
|
|
35
35
|
"provider": "configured-provider",
|
|
36
36
|
"model": "configured-model",
|
|
37
|
-
"thinkingLevel": "medium"
|
|
37
|
+
"thinkingLevel": "medium",
|
|
38
|
+
"extensions": []
|
|
38
39
|
}]
|
|
39
40
|
}
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
The server sorts both arrays by alias with a deterministic code-point comparison. It copies only loaded configuration fingerprint/reload policy, configured aliases, and the
|
|
43
|
+
The server sorts both arrays by alias with a deterministic code-point comparison. It copies only loaded configuration fingerprint/reload policy, configured aliases, and the seven documented profile fields. `extensions` lists the configured extension specs (`npm:<name>@<version>`), empty when a profile loads none; it makes no claim that the package is installed. It never copies the configuration path or contents, workspace paths, authentication state, SDK data, session/turn data, prompts, transcripts, or tool arguments/results. The handler reads the in-memory loaded snapshot only and does not call the registry, Pi adapter, provider, filesystem, or network. The fingerprint identifies the exact loaded bytes, not current-file freshness; provider/model fields describe configured intent and make no availability claim.
|
|
43
44
|
|
|
44
45
|
Annotations are `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, and `openWorldHint: false`. Clients may use them for presentation, but the strict input schema and server-owned projection enforce the contract.
|
|
45
46
|
|
|
@@ -67,6 +68,10 @@ Public MCP errors are stable and sanitized. Their messages do not include inputs
|
|
|
67
68
|
| `external_mcp_no_tools` | Complete valid discovery returned no tools while explicit grants were required. |
|
|
68
69
|
| `external_mcp_grant_tool_missing` | Complete valid nonempty discovery omitted an explicitly granted remote tool. |
|
|
69
70
|
| `external_mcp_activation_mismatch` | Pi did not activate exactly the expected post-discovery tool set. |
|
|
71
|
+
| `extension_not_installed` | Pi has no installed package for a configured extension. |
|
|
72
|
+
| `extension_version_mismatch` | Pi's installed extension package has another version than the configured one. |
|
|
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, 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`). |
|
|
70
75
|
| `pi_session_creation_failed` | Pi session construction failed without a more specific safe classification. |
|
|
71
76
|
| `server_stopping` | Shutdown has begun, so a new session cannot be admitted. |
|
|
72
77
|
|