pi-mcp-extension 1.10.2 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -2
- package/package.json +8 -5
- package/src/auth-lock-policy.ts +63 -0
- package/src/auth-lock.ts +341 -265
- package/src/authorize-dialog.ts +244 -0
- package/src/authorize-flow.ts +498 -366
- package/src/callback-server.ts +81 -43
- package/src/callback-url.ts +55 -43
- package/src/commands.ts +9 -5
- package/src/config.ts +111 -35
- package/src/connect-policy.ts +244 -0
- package/src/connection-coordinator.ts +348 -242
- package/src/debug-log-format.ts +194 -0
- package/src/debug-log.ts +25 -177
- package/src/index.ts +120 -163
- package/src/jsonc.ts +92 -58
- package/src/oauth-provider.ts +330 -301
- package/src/oauth-state-store.ts +136 -99
- package/src/oauth-status.ts +80 -0
- package/src/register-commands.ts +177 -0
- package/src/schema-convert.ts +473 -410
- package/src/schema-refs.ts +98 -0
- package/src/server-manager.ts +334 -372
- package/src/server-provenance.ts +78 -0
- package/src/server-status.ts +184 -0
- package/src/startup-auth.ts +80 -0
- package/src/stdio-process.ts +24 -28
- package/src/tool-bridge.ts +290 -258
- package/src/tool-discovery.ts +147 -0
- package/src/transport-factory.ts +133 -100
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/pi-mcp-extension)
|
|
4
4
|
[](https://github.com/irahardianto/pi-mcp-extension/blob/main/LICENSE)
|
|
5
|
+
[](https://github.com/irahardianto/pi-mcp-extension/actions/workflows/ci.yml)
|
|
6
|
+
[](https://qlty.sh/gh/irahardianto/projects/pi-mcp-extension)
|
|
7
|
+
[](https://qlty.sh/gh/irahardianto/projects/pi-mcp-extension)<br>
|
|
5
8
|
[](https://scorecard.dev/viewer/?uri=github.com/irahardianto/pi-mcp-extension)
|
|
6
9
|
[](https://www.bestpractices.dev/projects/15659)
|
|
7
10
|
|
|
@@ -182,13 +185,39 @@ Server-name arguments provide autocomplete suggestions from the configured MCP s
|
|
|
182
185
|
| Command | Description |
|
|
183
186
|
|---|---|
|
|
184
187
|
| `/mcp` | Show status summary of all configured servers |
|
|
185
|
-
| `/mcp <name>` | Show detailed status
|
|
188
|
+
| `/mcp <name>` | Show detailed status for one server: config source, transport/lifecycle, tool activation, OAuth credential status, failure hint, and stderr log |
|
|
186
189
|
| `/mcp:start <name>` | Connect a server and wait for tool discovery |
|
|
187
190
|
| `/mcp:stop <name>` | Cancel a pending connection, startup prompt, or authorization, or stop a running server and deactivate its tools |
|
|
188
191
|
| `/mcp:auth <name>` | Connect with stored OAuth credentials and client registration; request browser authorization only if needed |
|
|
189
192
|
| `/mcp:auth <name> --reset` | Discard stored tokens and client registration (re-running Dynamic Client Registration) and request fresh authorization |
|
|
190
193
|
|
|
191
|
-
Agent tools `mcp_status` and `mcp_connect` let Pi inspect configured servers and connect one named server without routine slash commands. `mcp_status` reports lifecycle, connection state, discovered and active tool counts, credential presence, and safe action hints without returning tokens, headers, or authorization URLs. `mcp_connect` activates discovered tools before returning success. Connection failures return fixed actionable categories instead of raw server response text.
|
|
194
|
+
Agent tools `mcp_status` and `mcp_connect` let Pi inspect configured servers and connect one named server without routine slash commands. `mcp_status` reports lifecycle, connection state, discovered and active tool counts, credential presence, config source, and safe action hints without returning tokens, headers, or authorization URLs. `mcp_connect` activates discovered tools before returning success. Connection failures return fixed actionable categories instead of raw server response text.
|
|
195
|
+
|
|
196
|
+
### Inspecting one server (`/mcp <name>`)
|
|
197
|
+
|
|
198
|
+
`/mcp <name>` answers "why is this server in this state?" without leaving Pi:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
Server: supabase
|
|
202
|
+
State: stopped
|
|
203
|
+
Retries: 5
|
|
204
|
+
Transport: streamable-http
|
|
205
|
+
Lifecycle: lazy
|
|
206
|
+
Source: global
|
|
207
|
+
Tools: 0/0 active
|
|
208
|
+
OAuth: credentials stored
|
|
209
|
+
Last error: Connection timed out after 30000ms
|
|
210
|
+
Hint: run with PI_MCP_DEBUG=1 pi to log redacted diagnostics, or report server issues at https://github.com/irahardianto/pi-mcp-extension/issues
|
|
211
|
+
|
|
212
|
+
Recent output:
|
|
213
|
+
(no stderr output from supabase)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
- `Source` is the entry that won: `global` (`<agent dir>/mcp.json`), `project` (`<cwd>/.pi/mcp.json`), or `extension` (`pi.registerMcpServer()`). `mcp_status` reports the same value as `source` on each row.
|
|
217
|
+
- When the project is untrusted, `Source` never says `project` and a `Project config: not loaded (<path>)` line explains why `mcp.json` in the workspace is not in effect.
|
|
218
|
+
- `Tools` compares discovered tools with the ones Pi has active; a server can be `ready` with `0/N active` when another extension restricts tools.
|
|
219
|
+
- `OAuth` appears only for servers with an `auth` config: `credentials stored`, `authorization may be required`, or `corrupt state file backed up (re-auth required)` (the corrupt file is backed up and the view still renders).
|
|
220
|
+
- A failed server adds one `Hint:` line: `/mcp:auth <name>` (plus `--reset` when stored credentials expired) for a missing/expired authorization, waiting for another Pi process holding the auth lock, or `PI_MCP_DEBUG=1 pi` plus the issue tracker for a connection failure.
|
|
192
221
|
|
|
193
222
|
### OAuth startup, headless/SSH, and concurrency behavior
|
|
194
223
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-extension",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) client extension for the Pi coding agent — connect Pi to any MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@modelcontextprotocol/sdk": "^1.32.
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
37
37
|
"zod": "^4.6.7"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
@@ -41,20 +41,23 @@
|
|
|
41
41
|
"@earendil-works/pi-coding-agent": "*",
|
|
42
42
|
"typebox": "*"
|
|
43
43
|
},
|
|
44
|
+
"overrides": {
|
|
45
|
+
"undici": "^8.12.3"
|
|
46
|
+
},
|
|
44
47
|
"devDependencies": {
|
|
45
48
|
"@eslint/js": "^10.0.1",
|
|
46
49
|
"@types/node": "^26.6.5",
|
|
47
50
|
"eslint": "^10.13.0",
|
|
48
51
|
"fast-check": "^4.10.2",
|
|
49
52
|
"tsx": "^4.19.4",
|
|
50
|
-
"typescript": "^
|
|
51
|
-
"typescript-eslint": "^8.
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"typescript-eslint": "^8.72.0"
|
|
52
55
|
},
|
|
53
56
|
"scripts": {
|
|
54
57
|
"lint": "eslint .",
|
|
55
58
|
"typecheck": "tsc --noEmit",
|
|
56
59
|
"test": "node --import tsx/esm --test ./tests/*.test.ts",
|
|
57
|
-
"test:coverage": "node --import tsx/esm --test --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-lines=95 --test-coverage-branches=85 --test-coverage-functions=83 ./tests/*.test.ts",
|
|
60
|
+
"test:coverage": "node --import tsx/esm --test --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=lcov.info --test-coverage-include='src/**' --test-coverage-lines=95 --test-coverage-branches=85 --test-coverage-functions=83 ./tests/*.test.ts",
|
|
58
61
|
"test:integration": "node --import tsx/esm --test ./tests/server-manager.test.ts"
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure lock-record and staleness policy for pi-mcp's cross-process OAuth
|
|
3
|
+
* authorization lock. No filesystem, clock, or process access.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
// ─── Constants & Schema ───────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export const HEARTBEAT_MS = 30_000;
|
|
11
|
+
export const STALE_MS = 90_000; // 3 × 30s heartbeat
|
|
12
|
+
|
|
13
|
+
export const LockRecordSchema = z.object({
|
|
14
|
+
v: z.literal(1),
|
|
15
|
+
pid: z.number().int().positive(),
|
|
16
|
+
hostname: z.string().min(1),
|
|
17
|
+
nonce: z.string().min(1),
|
|
18
|
+
createdAt: z.number(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type LockRecord = z.output<typeof LockRecordSchema>;
|
|
22
|
+
|
|
23
|
+
// ─── Pure Staleness Policy ────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export function parseLockRecord(raw: string): LockRecord | null {
|
|
26
|
+
if (!raw) return null;
|
|
27
|
+
try {
|
|
28
|
+
const parsed: unknown = JSON.parse(raw);
|
|
29
|
+
const result = LockRecordSchema.safeParse(parsed);
|
|
30
|
+
return result.success ? result.data : null;
|
|
31
|
+
} catch {
|
|
32
|
+
// WHY: JSON.parse on a string throws SyntaxError if the lock file is corrupt or
|
|
33
|
+
// partially written. LockRecordSchema.safeParse does not throw. Unparseable lock
|
|
34
|
+
// files map to null so isLockStale() can fall back to mtime-based staleness.
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pure policy determining whether an existing lock file is stale and safe to reclaim.
|
|
41
|
+
*/
|
|
42
|
+
export function isLockStale(
|
|
43
|
+
record: LockRecord | null,
|
|
44
|
+
mtimeMs: number,
|
|
45
|
+
now: number,
|
|
46
|
+
self: { hostname: string; isPidAlive: (pid: number) => boolean },
|
|
47
|
+
): boolean {
|
|
48
|
+
if (now - mtimeMs > STALE_MS) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (record === null) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (record.hostname === self.hostname && !self.isPidAlive(record.pid)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatHolderDetails(record: LockRecord | null): string | undefined {
|
|
61
|
+
if (!record) return undefined;
|
|
62
|
+
return `pid ${record.pid} on ${record.hostname}`;
|
|
63
|
+
}
|