@herbertgao/pi-extensions 2026.9.11 → 2026.9.12
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/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
- package/node_modules/pi-mcp-adapter/README.md +75 -7
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
- package/node_modules/pi-mcp-adapter/cli.js +56 -5
- package/node_modules/pi-mcp-adapter/commands.ts +96 -5
- package/node_modules/pi-mcp-adapter/config.ts +73 -6
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
- package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
- package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
- package/node_modules/pi-mcp-adapter/index.ts +129 -6
- package/node_modules/pi-mcp-adapter/init.ts +6 -19
- package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
- package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
- package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +17 -9
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
- package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
- package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
- package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
- package/node_modules/pi-mcp-adapter/types.ts +53 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
- package/node_modules/pi-mcp-adapter/utils.ts +16 -0
- package/package.json +7 -6
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"x-upstream": {
|
|
71
71
|
"package": "pi-cc-extensions",
|
|
72
72
|
"version": "0.8.71",
|
|
73
|
-
"reviewedVersion": "0.9.
|
|
73
|
+
"reviewedVersion": "0.9.2",
|
|
74
74
|
"repository": "https://github.com/minuque/pi-cc-extensions",
|
|
75
75
|
"commit": "e43e0041b59f5d7f03be9b9d103a5f9e954e4c11"
|
|
76
76
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juicesharp/rpiv-ask-user-question",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Pi extension. A structured questionnaire the model can put to you when it would otherwise guess, with typed options instead of free-form replies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@juicesharp/rpiv-config": "^2.
|
|
98
|
+
"@juicesharp/rpiv-config": "^2.11.0",
|
|
99
99
|
"typebox": "^1.1.24"
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.36.0] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
### Highlights
|
|
13
|
+
|
|
14
|
+
- Set up TypeSafe semantic search from Pi with `/mcp jev setup`.
|
|
15
|
+
- Search every enabled MCP tool automatically when a TypeSafe key is available.
|
|
16
|
+
- Use regex safety checks reliably on Windows with both native and Java backends.
|
|
17
|
+
- Get clearer, non-duplicated guidance when tool catalogs are large or semantic search finds no match.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `/mcp jev setup` checks for a TypeSafe credential, lets you restrict which MCP servers may share semantic-search data, saves the project policy, and reloads Pi automatically.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- A valid TypeSafe key now enables semantic search across every enabled MCP tool by default, while script evaluation remains opt-in. Search now explains when an allowlist permits no servers, when permitted servers have no cached tools, and when none of the available tools match the request.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Regex safety checks on Windows resolve recheck's native executable and JAR fallback correctly. `recheck` is intentionally pinned to `4.6.0-beta.3` until a stable fixed release is available. Thanks to [@LCubero](https://github.com/LCubero) for reporting [#623](https://github.com/nicobailon/pi-mcp-adapter/issues/623), and [@Kristinita](https://github.com/Kristinita) and [@makenowjust](https://github.com/makenowjust) for the upstream reproduction and fix.
|
|
30
|
+
- Large direct-tool advisories now use Pi's renderer in interactive sessions, avoiding raw console output and duplicate warnings. Thanks to [@grivper](https://github.com/grivper) for issue [#633](https://github.com/nicobailon/pi-mcp-adapter/issues/633).
|
|
31
|
+
- Windows contributors can run `npm test` again; the runner now launches npm through `cross-spawn` so hardened Node versions can execute `npm.cmd`. Thanks @insuffer for the fix.
|
|
32
|
+
|
|
33
|
+
## [2.35.0] - 2026-09-20
|
|
34
|
+
|
|
35
|
+
### Highlights
|
|
36
|
+
|
|
37
|
+
- Describe what you want to do and let Jev find the MCP tools that best match your request.
|
|
38
|
+
- Run long-lived MCP Tasks with progress polling, interactive input, and cancellation.
|
|
39
|
+
- Edit shared MCP configuration without leaving Pi and approve a server for the rest of the session.
|
|
40
|
+
- Reconnect to OAuth and bearer-token servers more reliably, including after expired credentials or authorization failures.
|
|
41
|
+
- Find and use tools more accurately across CJK queries, namespaced catalogs, structured results, and ambiguous names.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Opt-in TypeSafe Jev support can understand a request, rank MCP tools by how well they match, and evaluate intermediate `mcpScript` results. Credentials stay in the OS keyring or environment, sharing MCP data requires an explicit server allowlist, and requests have configurable limits. In a live test across 95 local tools and resources, Jev chose the expected result first in 10 of 11 answerable cases and second once, compared with 5 first-place matches from regular text search. Part of [#611](https://github.com/nicobailon/pi-mcp-adapter/issues/611).
|
|
46
|
+
- `/mcp edit [project|global]` opens the shared MCP config in an editor (Ctrl+G opens `$EDITOR`), refuses text that is not a JSONC object, and reloads after a save. Closes #593. Thanks to [@turisanapo](https://github.com/turisanapo) for PR #594.
|
|
47
|
+
- Support for MCP Tasks. Long-running tool calls are polled to completion, interactive questions use the normal Pi interface, cancellation is forwarded to the server, and failures are reported like ordinary tool-call errors. Task support activates only when the server advertises it and can be disabled per server with `tasks: false`. Thanks to [@rgarcia](https://github.com/rgarcia) for PR #620.
|
|
48
|
+
- Users can grant runtime-only approval for all tools and arguments on a server for the current session. Thanks to [@derdossi](https://github.com/derdossi) for PR #618.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Development and peer dependency coverage now includes Pi 0.86.
|
|
53
|
+
- Self-namespaced MCP tools no longer receive duplicate prefixes, and proxy calls now resolve unique canonical-name candidates while failing closed on collisions and ambiguity. Fixes [#609](https://github.com/nicobailon/pi-mcp-adapter/issues/609). Thanks to [@elkaix](https://github.com/elkaix) for the report.
|
|
54
|
+
- Namespace proxy tools can now be disabled with `settings.namespaceProxyTools: false`. Thanks to [@k03mad](https://github.com/k03mad) for PR #592.
|
|
55
|
+
- The bundled `mcp-scripting` skill is now discovered alongside the default-on `mcpScript` tool and hidden with it when `settings.scriptMode` is `false`. Thanks to [@zhangyoufu](https://github.com/zhangyoufu) for PR #583.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- HTTP streams authenticated with `requestHeadersCommand` remain cancellable after garbage collection. Thanks to [@zaini](https://github.com/zaini) for PR #619.
|
|
60
|
+
- Bearer-token and TypeSafe key storage now retry revoked Linux session-keyring operations through the packaged `keyctl` helper, without special launch commands or plaintext fallback. Set `PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY=1` to disable recovery. Thanks to [@magoz](https://github.com/magoz) for PR #621.
|
|
61
|
+
- OAuth-enabled MCP servers now reconnect reliably after explicit OAuth, stored-token, and 401 authentication paths. Thanks to [@jaresty](https://github.com/jaresty) for PR #624.
|
|
62
|
+
- Direct tools now recover stringified array and object arguments declared through type arrays and schema unions without coercing values that are valid strings. Thanks to [@sashkachan](https://github.com/sashkachan) for issue [#606](https://github.com/nicobailon/pi-mcp-adapter/issues/606).
|
|
63
|
+
- Default tool search now supports CJK text, including unseparated mixed-script queries and configured search keywords, while retaining bounded lexical matching. Thanks to [@wjunhere](https://github.com/wjunhere) for issue [#607](https://github.com/nicobailon/pi-mcp-adapter/issues/607).
|
|
64
|
+
- Command-backed bearer tokens now refresh through a TTL cache, and keep-alive bearer connections reconnect after a 401. Thanks to [@kesor](https://github.com/kesor) for PR #608.
|
|
65
|
+
- Tool results now preserve `structuredContent` alongside ordinary content in direct and proxy calls. Thanks to [@civcode](https://github.com/civcode) for issue #588.
|
|
66
|
+
- Restored the MCP footer status during cache-backed deferred startup without eagerly loading or connecting the runtime. Thanks to [@pkulyn](https://github.com/pkulyn) for issue #586.
|
|
67
|
+
- Oversized object `structuredContent` summaries now identify themselves as omitted and account for preserved and dropped fields, so extension consumers do not mistake a partial preview for an empty payload. Thanks to [@Batchputz](https://github.com/Batchputz) for issue #585.
|
|
68
|
+
- Server-scoped tool describe and call requests now fail closed when a name exactly identifies different displayed and upstream tools. Thanks to [@sheurich](https://github.com/sheurich) for PR #587.
|
|
69
|
+
- Config writes now preserve resolvable existing symlinks by atomically replacing their targets. Thanks to [@peedrr](https://github.com/peedrr) for #597.
|
|
70
|
+
- Server-returned MCP tool errors no longer include misleading input-schema guidance, while invalid proxy arguments are rejected before dispatch. Thanks to [@jaresty](https://github.com/jaresty) for PR #596.
|
|
71
|
+
|
|
10
72
|
## [2.34.0] - 2026-09-14
|
|
11
73
|
|
|
12
74
|
### Highlights
|
|
@@ -320,6 +320,7 @@ In the configuration examples below, `30000` is illustrative only. If `requestTi
|
|
|
320
320
|
| `idleTimeout` | Minutes before idle disconnect (overrides global) |
|
|
321
321
|
| `requestTimeoutMs` | Request timeout in milliseconds for live MCP calls (overrides global; if omitted or `<= 0`, the MCP SDK default timeout is used) |
|
|
322
322
|
| `protocolVersion` | `"legacy"` (default), `"auto"`, or `"2026-07-28"`; modern negotiation is opt-in |
|
|
323
|
+
| `tasks` | MCP Tasks extension support on 2026-07-28 connections (default: true; set `false` to opt out); see [Task-augmented tool calls](#task-augmented-tool-calls) |
|
|
323
324
|
| `exposeResources` | Expose MCP resources as tools (default: true) |
|
|
324
325
|
| `directTools` | `true`, `string[]`, or `false` — register tools individually instead of through proxy |
|
|
325
326
|
| `toolPrefix` | Override global `settings.toolPrefix` for this server (`"server"`, `"short"`, `"none"`, or `"mcp"`) |
|
|
@@ -348,6 +349,21 @@ Use `"auto"` to probe for MCP 2026-07-28 and conservatively fall back to the cla
|
|
|
348
349
|
|
|
349
350
|
Use `"2026-07-28"` to pin that revision. Pinning has no legacy or SSE fallback and fails if the server does not offer the requested version.
|
|
350
351
|
|
|
352
|
+
#### Task-augmented tool calls
|
|
353
|
+
|
|
354
|
+
The adapter supports the [MCP Tasks extension](https://modelcontextprotocol.io/extensions/tasks/overview) (`io.modelcontextprotocol/tasks`, SEP-2663), which lets long-running tools return a durable task handle instead of blocking the connection. Support is negotiated per connection and needs no configuration: the task session only activates when a 2026-07-28 connection's server advertises the extension, so nothing changes for servers without task support. Set `tasks: false` on a server to opt out and keep the plain synchronous call path. Legacy (2025-11-25) experimental tasks are not supported.
|
|
355
|
+
|
|
356
|
+
When active, tool calls keep their normal contract from the model's point of view:
|
|
357
|
+
|
|
358
|
+
- A tool that returns a task handle is transparently polled to completion, honoring the server's suggested poll interval; the final result is returned as if the call had been synchronous.
|
|
359
|
+
- If the task pauses for input (`input_required`), elicitation requests are routed through the same interactive elicitation UI as direct `elicitation/create` requests, and answers are delivered back via `tasks/update`.
|
|
360
|
+
- Cancelling the Pi tool call sends a cooperative `tasks/cancel` to the server.
|
|
361
|
+
- A task that fails with a JSON-RPC error surfaces as the same error a synchronous call would have produced; a tool result with `isError: true` is returned as a normal tool error.
|
|
362
|
+
|
|
363
|
+
Task traffic is dispatched on a dedicated raw channel below the SDK client (the published MCP SDK does not yet decode task result shapes itself), built on the official `@modelcontextprotocol/ext-tasks` requester package. The channel chains onto the connected transport's handlers without replacing the transport, and raw task frames appear in `/mcp-trace` in both directions. Task status notifications (`notifications/tasks`) are not consumed; polling is used exclusively. `requestTimeoutMs` applies per task request (the initiating call and each poll), not to the overall task duration — a task that runs for hours holds the Pi tool call for as long as the model waits for it.
|
|
364
|
+
|
|
365
|
+
One trade-off while tasks are active: every `tools/call` on that connection is dispatched through the task-aware path instead of `Client.callTool`, so the SDK's client-side output-schema validation of `structuredContent` and SEP-2243 `Mcp-Param-*` header mirroring do not run for those calls. Servers still validate their own results; only the client-side double-check is skipped.
|
|
366
|
+
|
|
351
367
|
The stable SDK handles era-specific request envelopes, result decoding, list-changed subscriptions, cancellation, and multi-round-trip sampling/elicitation. The SDK's embedded-input progress callback does not expose the originating tool or resource identity, so the adapter cannot maintain a durable per-tool waiting status row; interactive sessions keep the existing input dialog visible, and proxy calls show request progress when UI is available. The adapter keeps strict OAuth issuer validation in every mode. Adapter-level roots support, standard MCP logging presentation, and configuration/UI for protocol cache hints are not yet implemented.
|
|
352
368
|
|
|
353
369
|
If an internal authorization server publishes mismatched OAuth metadata and cannot be fixed immediately, set `oauth.skipIssuerMetadataValidation: true` on that server only. This is security-weakening. It disables the RFC 8414 issuer echo check and should not be used for public or untrusted servers.
|
|
@@ -378,6 +394,8 @@ Secret values in `headers`, `bearerToken`, `oauth.clientSecret`, and stdio `env`
|
|
|
378
394
|
|
|
379
395
|
For local desktop bearer tokens, `bearerTokenStore: true` can opt in to the adapter-owned credential-store namespace. It never falls back to plaintext if the store is unavailable, if the stored record is malformed, or if the stored URL differs from the effective server URL. Literal tokens, command tokens, and environment tokens keep precedence so existing configs do not change. Create or rotate a stored token with `pi-mcp-adapter token set <server>` (masked prompt on a terminal, or piped stdin such as `security find-generic-password -s my-token -w | pi-mcp-adapter token set <server>`); the record binds to the effective configured URL at write time. Token commands need Node 22.18+.
|
|
380
396
|
|
|
397
|
+
On Linux, bearer-token and TypeSafe key storage also recover automatically when a native operation fails with `KeyRevoked`, including wrapped errors from a revoked inherited session keyring. Each failed read/write/remove is retried once through `keyctl session - <current runtime> <packaged helper>`, with a 10-second timeout and no plaintext fallback. This requires `keyctl` on `PATH` and a working credential store in the fresh session; other storage errors still fail closed. Set `PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY=1` to disable this recovery. Normal Pi and token CLI launches need no special wrapper.
|
|
398
|
+
|
|
381
399
|
### Shared MCP processes with rmcp-mux
|
|
382
400
|
|
|
383
401
|
To share one stdio MCP server across Pi sessions, run it under [`rmcp-mux`](https://github.com/VetCoders/rmcp-mux) and point each session at the service socket:
|
|
@@ -491,11 +509,13 @@ When any enabled server uses `eager` or `keep-alive`, initialization also starts
|
|
|
491
509
|
| `oauthCredentialStore` | Set explicitly to `"encrypted-file"` for externally keyed AES-256-GCM storage (notably Windows OpenSSH network logons). Requires `PI_MCP_ADAPTER_OAUTH_FILE_KEY`; absent uses the OS credential store. |
|
|
492
510
|
| `mcpServers.<name>.oauth.authorizationParams` | Extra authorization URL parameters for provider-specific OAuth extensions. Flow-owned parameters such as `client_id`, `redirect_uri`, `scope`, `state`, `code_challenge`, `response_type`, and `resource` cannot be overridden. |
|
|
493
511
|
| `directTools` | Global default for all servers (default: false). `true`, `false`, or `"search"`. Per-server overrides this. |
|
|
512
|
+
| `namespaceProxyTools` | Register per-server `mcp__<server>` wrappers (default: true). Set to `false` to omit them from the model's tool list; `mcp`, `mcpScript`, and direct tools are unaffected. References such as `mcp:<server>` that rely on a wrapper will no longer resolve. Run `/reload` after changing this setting. |
|
|
494
513
|
| `strictDirectToolArguments` | Validate direct-tool inputs against their advertised schemas and recover one JSON string layer for object and array properties (default: false). |
|
|
495
514
|
| `directToolResultDetails` | Direct-tool result details: `"lean"` (default) or `"bounded"` to retain the guarded raw MCP result. |
|
|
496
515
|
| `warnOnLargeDirectTools` | Show the advisory when 75 or more direct tools resolve (default: `true`). Set to `false` to suppress only this advisory. |
|
|
497
516
|
| `freezeDirectTools` | Keep direct-tool registration stable after the initial sync so metadata updates and explicit reconnects do not rebuild the system prompt. Proxy/search/cache metadata still refreshes. Default: false. |
|
|
498
517
|
| `scriptMode` | Register the MCP-only `mcpScript` plain-JavaScript tool (default: true). Set to `false` to hide it. |
|
|
518
|
+
| `jev` | Optional TypeSafe Jev settings. A valid TypeSafe key enables semantic search across every enabled MCP server by default; `semanticSearch: false` disables it. `scriptEvaluation` remains disabled by default and requires an `allowedServers` source allowlist when enabled. Run `/mcp jev setup` for guided configuration. |
|
|
499
519
|
| `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. Ignored while any server uses `directTools: "search"`, whose tools are registered inactive and can only be activated through `mcp({ search })`. |
|
|
500
520
|
| `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
|
|
501
521
|
| `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
|
|
@@ -522,7 +542,11 @@ Use `approveTools` when a tool should stay visible but not run without confirmat
|
|
|
522
542
|
}
|
|
523
543
|
```
|
|
524
544
|
|
|
525
|
-
When a matching tool is called from the proxy tool, a direct MCP tool, a resource call, or an MCP UI iframe, Pi asks: **Allow once**, **Allow for session**, or **Deny**. **Allow for session** tool grants and MCP UI iframe consent decisions (including denials) persist as non-LLM custom entries on the active Pi session branch and restore on resume or branch navigation. Entries store only server/tool names and deterministic definition/argument hashes; raw arguments, results, and secrets never persist. Tool grants and iframe consent remain separate gates. In headless sessions, matching calls fail closed with an `approval_required` result; denials, abstentions, **Allow once**, and approval-required paths do not create tool grant records. `excludeTools` still removes tools entirely; `approveTools` only gates visible tools at call time.
|
|
545
|
+
When a matching tool is called from the proxy tool, a direct MCP tool, a resource call, or an MCP UI iframe, Pi asks: **Allow once**, **Allow for session**, **Allow server for this session**, or **Deny**. **Allow for session** tool grants and MCP UI iframe consent decisions (including denials) persist as non-LLM custom entries on the active Pi session branch and restore on resume or branch navigation. Entries store only server/tool names and deterministic definition/argument hashes; raw arguments, results, and secrets never persist. Tool grants and iframe consent remain separate gates. In headless sessions, matching calls fail closed with an `approval_required` result; denials, abstentions, **Allow once**, and approval-required paths do not create tool grant records. `excludeTools` still removes tools entirely; `approveTools` only gates visible tools at call time.
|
|
546
|
+
|
|
547
|
+
**Allow server for this session** permits all tools and argument combinations on the selected server, including tools discovered later. It does not approve other servers. This broad grant stays in memory only: reload, session replacement, resume, and branch navigation clear it. A changed or replaced server configuration also invalidates it. It is never saved to session entries or configuration. Broker denials, tool exclusions, host security guards, and the separate MCP UI iframe consent gate still apply. Use **Allow for session** instead to approve only the displayed tool definition and arguments.
|
|
548
|
+
|
|
549
|
+
`pi-mcp-adapter/status/v1` is the documented, versioned public channel for cross-extension status. By contrast, `mcp-approval-v1` entries are adapter-owned persistence state, not a supported cross-extension contract; consumers should use documented package exports and event APIs instead.
|
|
526
550
|
|
|
527
551
|
Permission extensions can broker these decisions by listening on `pi-mcp-adapter:tool-approval-request` and claiming the request synchronously:
|
|
528
552
|
|
|
@@ -550,6 +574,8 @@ Oversized MCP tool/resource results are guarded by default so a single huge resp
|
|
|
550
574
|
- Binary resource blobs up to **10 MiB** are decoded to private temp files and replaced with file references. Each session is limited to **100 MiB** and **10,000 files**. The files are removed at session teardown.
|
|
551
575
|
- In proxy mode, `details.mcpResult` is kept raw when its JSON is **≤ 16 KiB**; larger results are replaced with a compact summary (block counts, sizes, key previews) and the raw JSON is saved to a temp file. Direct tools keep lean details unless `settings.directToolResultDetails` is set to `"bounded"`, which applies the same guarded `mcpResult` limit.
|
|
552
576
|
|
|
577
|
+
Extensions consuming `details.mcpResult` must check for `omitted === true` on both the result and its `structuredContent` before treating either value as an original payload. For omitted object `structuredContent`, `preservedFields` is only a partial preview; `summary.keyCount` is the original cardinality, while `preservedCount` and `droppedCount` account for retention. Under tiny limits, the whole result may compact to an omission marker without spill metadata.
|
|
578
|
+
|
|
553
579
|
Tune the text and details limits with the object form:
|
|
554
580
|
|
|
555
581
|
```json
|
|
@@ -564,19 +590,59 @@ Set `"outputGuard": false` — or the env kill switch `MCP_OUTPUT_GUARD=0` — t
|
|
|
564
590
|
|
|
565
591
|
### MCP Scripting
|
|
566
592
|
|
|
567
|
-
|
|
593
|
+
#### Jev semantic search and opt-in script evaluation
|
|
594
|
+
|
|
595
|
+
A valid TypeSafe key makes semantic search available across every enabled MCP server; it does not run Jev searches automatically. A search uses Jev only when `searchMode: "semantic"` is explicitly requested. Jev ranks matching tools but never executes them. Script evaluation remains disabled until `scriptEvaluation: true` is configured. Requests use pinned model `jev-1.13.0` at the fixed origin `https://api.typesafe.ai`. Review TypeSafe's current [legal terms](https://docs.typesafe.ai/legal), including privacy and retention; a no-training commitment does not mean zero retention.
|
|
596
|
+
|
|
597
|
+
```text
|
|
598
|
+
Normal search
|
|
599
|
+
mcp({ search: "calendar" })
|
|
600
|
+
│
|
|
601
|
+
└── local lexical search
|
|
602
|
+
no Jev request
|
|
603
|
+
|
|
604
|
+
Explicit semantic search
|
|
605
|
+
mcp({ search: "calendar", searchMode: "semantic" })
|
|
606
|
+
│
|
|
607
|
+
└── Jev ranks matching tools
|
|
608
|
+
no tool is executed
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
The quickest desktop setup is:
|
|
612
|
+
|
|
613
|
+
```sh
|
|
614
|
+
pi-mcp-adapter key set typesafe
|
|
615
|
+
```
|
|
568
616
|
|
|
569
|
-
|
|
617
|
+
That is enough to use semantic search across all enabled MCP tools. Run `/mcp jev setup` in Pi when you want to restrict which enabled servers may share semantic-search data. The command saves a project-scoped allowlist and reloads Pi automatically. Verify the stored credential at any time with `pi-mcp-adapter key status typesafe`.
|
|
618
|
+
|
|
619
|
+
`TYPESAFE_API_KEY` is for CI/headless use and overrides the keyring. Stdio MCP subprocesses inherit the host environment by default, so set `inheritEnv: false` where they must not receive it. The script worker receives no key, SDK, endpoint, headers, or environment.
|
|
620
|
+
|
|
621
|
+
Semantic search sends the query text, server names, normalized and original tool names, tool paths, and descriptions to TypeSafe. It does not send tool results. `allowedServers` restricts semantic search to named servers. `scriptEvaluation` is a separate opt-in that may send the state and MCP-derived results declared in each evaluation; when enabled, it requires an explicit source allowlist.
|
|
570
622
|
|
|
571
623
|
```json
|
|
572
624
|
{
|
|
573
|
-
"
|
|
574
|
-
|
|
575
|
-
|
|
625
|
+
"settings": {
|
|
626
|
+
"jev": {
|
|
627
|
+
"scriptEvaluation": true,
|
|
628
|
+
"allowedServers": ["github"],
|
|
629
|
+
"maxEvaluationTokensPerScript": 32768
|
|
630
|
+
}
|
|
631
|
+
}
|
|
576
632
|
}
|
|
577
633
|
```
|
|
578
634
|
|
|
579
|
-
|
|
635
|
+
Request semantic discovery explicitly with `mcp({ search: "triage customer reports", searchMode: "semantic" })` or `tools.search({ query: "triage customer reports", searchMode: "semantic" })`. Regex is incompatible. Timeout, rate-limit, and service failures return marked lexical fallback; credential, policy, configuration, and response failures do not. If no allowed server has cached tools, search explains how to connect a server or update the allowlist; if Jev decides no tool fits, the result says that Jev abstained.
|
|
636
|
+
|
|
637
|
+
Optional `jev` controls bound timeout/retries, request and script budgets, semantic candidates (at most 127), and minimum probability. The cumulative token budget uses provider-reported input plus output usage. Exact pre-response admission is unavailable without the provider tokenizer, so byte/question/state limits bound requests before dispatch; a response that exceeds the remaining token budget is discarded and exhausts it. The endpoint, headers, and SDK logging are not configurable.
|
|
638
|
+
|
|
639
|
+
`await jev.evaluate({ state, questions, sources })` returns `{ ok, data }` or `{ ok: false, error }`. `sources` must name every MCP server represented in `state`. The host also conservatively taints the whole script with every server-attributed MCP call result or error: declared and observed sources must all be enabled and in `allowedServers`, so copying data or omitting/mislabeling `sources` cannot bypass policy. The taint remains for later direct evaluations and semantic searches even when the script did not retain the call result. Direct and semantic provider attempts share the per-script count, UTF-8 request-byte, token, and deadline budgets; later `tools.call` operations still require normal authentication and approval. See `examples/jev-semantic-filter.mjs` and `examples/jev-accessibility-loop.mjs`.
|
|
640
|
+
|
|
641
|
+
Semantic search sends your request and the available tool descriptions to Jev, which works out which tools best match what you’re trying to do. In a live test with 12 everyday requests and 95 tools and resources, Jev chose the expected result first in 10 of 11 answerable cases and placed it second once. Regular text search found the expected result first in 5 cases. Jev also correctly returned no result for an unrelated request. This was a small test using one local setup, so results will vary with different tools and queries.
|
|
642
|
+
|
|
643
|
+
For multi-call MCP work, write ordinary JavaScript: discover, inspect, call, loop, filter, chain, or fan out, then return one result. Run that code with the default-on `mcpScript` tool. For a single MCP call, search, describe, status check, or auth action, use `mcp` instead. Set `settings.scriptMode` to `false` to hide both the scripting tool and its bundled skill.
|
|
644
|
+
|
|
645
|
+
The bundled `mcp-scripting` skill is manual-only by default, so its description is not added to the model's automatic skill context. Use `/skill:mcp-scripting` when you want its detailed workflow.
|
|
580
646
|
|
|
581
647
|
For example, this is the JavaScript passed as the `code` argument to `mcpScript`:
|
|
582
648
|
|
|
@@ -879,6 +945,8 @@ Servers that provide usage guidance via the MCP `instructions` field surface it
|
|
|
879
945
|
| `/mcp` | Interactive panel and first-run onboarding surface |
|
|
880
946
|
| `/pi-mcp` | Alias for `/mcp` when the host reserves `/mcp` |
|
|
881
947
|
| `/mcp setup` | Guided setup for imports, a minimal `.mcp.json`, curated known servers, RepoPrompt quick-add, and config-path inspection |
|
|
948
|
+
| `/mcp jev setup` | Restrict which servers may share semantic-search data, save the project policy, and reload Pi |
|
|
949
|
+
| `/mcp edit [project\|global]` | Open `.mcp.json` (default) or `~/.config/mcp/mcp.json` in an editor; Ctrl+G opens `$EDITOR`; saves a valid JSONC object and reloads |
|
|
882
950
|
| `/mcp tools` | List all tools |
|
|
883
951
|
| `/mcp prompts` | List all MCP prompts registered as slash commands |
|
|
884
952
|
| `/mcp reconnect` | Reconnect all servers |
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TTL_MS = 5 * 60_000;
|
|
4
|
+
const ENV_TTL_MS = "PI_MCP_ADAPTER_BEARER_COMMAND_TTL_MS";
|
|
5
|
+
const COMMAND_TIMEOUT_MS = 10_000;
|
|
6
|
+
const COMMAND_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
7
|
+
const USE_PROCESS_GROUP = process.platform !== "win32";
|
|
8
|
+
|
|
9
|
+
function resolveDefaultTtlMs(): number {
|
|
10
|
+
const parsed = Number(process.env[ENV_TTL_MS] || DEFAULT_TTL_MS);
|
|
11
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_TTL_MS;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function abortReason(signal: AbortSignal): unknown {
|
|
15
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runCommand(command: string, context: string, signal: AbortSignal): Promise<string> {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
let output = Buffer.alloc(0);
|
|
21
|
+
let settled = false;
|
|
22
|
+
let terminating = false;
|
|
23
|
+
const child = spawn(command.slice(1), {
|
|
24
|
+
shell: true,
|
|
25
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
26
|
+
windowsHide: true,
|
|
27
|
+
detached: USE_PROCESS_GROUP,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const kill = async (): Promise<void> => {
|
|
31
|
+
if (child.pid === undefined) return;
|
|
32
|
+
if (!USE_PROCESS_GROUP) {
|
|
33
|
+
await new Promise<void>((resolveKill, rejectKill) => {
|
|
34
|
+
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
35
|
+
stdio: "ignore",
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
});
|
|
38
|
+
killer.on("error", () => rejectKill(new Error("Failed to stop bearer token command")));
|
|
39
|
+
killer.on("close", code => {
|
|
40
|
+
if (code === 0 || code === 128) resolveKill();
|
|
41
|
+
else rejectKill(new Error(`Failed to stop bearer token command: taskkill exited with code ${code ?? "unknown"}`));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
process.kill(-child.pid, "SIGKILL");
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if ((error as NodeJS.ErrnoException).code !== "ESRCH") throw error;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const finish = (error: unknown, token?: string) => {
|
|
53
|
+
if (settled) return;
|
|
54
|
+
settled = true;
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
signal.removeEventListener("abort", onAbort);
|
|
57
|
+
if (error !== undefined) reject(error);
|
|
58
|
+
else resolve(token!);
|
|
59
|
+
};
|
|
60
|
+
const terminate = async (error: unknown) => {
|
|
61
|
+
if (settled || terminating) return;
|
|
62
|
+
terminating = true;
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
signal.removeEventListener("abort", onAbort);
|
|
65
|
+
try {
|
|
66
|
+
await kill();
|
|
67
|
+
finish(error);
|
|
68
|
+
} catch (cleanupError) {
|
|
69
|
+
finish(cleanupError);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const onAbort = () => {
|
|
73
|
+
void terminate(abortReason(signal));
|
|
74
|
+
};
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
void terminate(new Error(`Failed to resolve ${context}: command timed out after ${COMMAND_TIMEOUT_MS}ms`));
|
|
77
|
+
}, COMMAND_TIMEOUT_MS);
|
|
78
|
+
|
|
79
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
80
|
+
if (signal.aborted) {
|
|
81
|
+
onAbort();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
child.on("error", () => {
|
|
85
|
+
if (!terminating) finish(new Error(`Failed to resolve ${context}: command failed to start`));
|
|
86
|
+
});
|
|
87
|
+
child.stdout.on("data", (chunk: Buffer | string) => {
|
|
88
|
+
if (settled || terminating) return;
|
|
89
|
+
output = Buffer.concat([output, Buffer.from(chunk)]);
|
|
90
|
+
if (output.byteLength > COMMAND_MAX_OUTPUT_BYTES) {
|
|
91
|
+
void terminate(new Error(`Failed to resolve ${context}: command output exceeded 1 MiB`));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
child.on("close", code => {
|
|
95
|
+
if (settled || terminating) return;
|
|
96
|
+
if (code !== 0) {
|
|
97
|
+
finish(new Error(`Failed to resolve ${context}: command exited with code ${code ?? "unknown"}`));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const token = output.toString("utf8").trim();
|
|
101
|
+
if (!token) {
|
|
102
|
+
finish(new Error(`Failed to resolve ${context}: command returned empty output`));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
finish(undefined, token);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type Inflight = {
|
|
111
|
+
controller: AbortController;
|
|
112
|
+
promise: Promise<string>;
|
|
113
|
+
waiters: number;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Resolve and periodically refresh one command-backed bearer token. */
|
|
117
|
+
export class BearerCommandResolver {
|
|
118
|
+
readonly #command: string;
|
|
119
|
+
readonly #context: string;
|
|
120
|
+
readonly #ttlMs: number;
|
|
121
|
+
#cached: { token: string; expiresAt: number } | undefined;
|
|
122
|
+
#failure: { error: unknown; retryAt: number } | undefined;
|
|
123
|
+
#inflight: Inflight | undefined;
|
|
124
|
+
|
|
125
|
+
constructor(command: string, context: string, ttlMs: number = resolveDefaultTtlMs()) {
|
|
126
|
+
this.#command = command;
|
|
127
|
+
this.#context = context;
|
|
128
|
+
this.#ttlMs = ttlMs;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
resolve(signal?: AbortSignal): Promise<string> {
|
|
132
|
+
if (signal?.aborted) return Promise.reject(abortReason(signal));
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
if (this.#cached !== undefined && now < this.#cached.expiresAt) {
|
|
135
|
+
return Promise.resolve(this.#cached.token);
|
|
136
|
+
}
|
|
137
|
+
if (this.#failure !== undefined && now < this.#failure.retryAt) {
|
|
138
|
+
return this.#cached !== undefined
|
|
139
|
+
? Promise.resolve(this.#cached.token)
|
|
140
|
+
: Promise.reject(this.#failure.error);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let inflight = this.#inflight;
|
|
144
|
+
if (inflight === undefined) {
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
let current: Inflight;
|
|
147
|
+
const promise = runCommand(this.#command, this.#context, controller.signal)
|
|
148
|
+
.then(token => {
|
|
149
|
+
this.#cached = { token, expiresAt: Date.now() + this.#ttlMs };
|
|
150
|
+
this.#failure = undefined;
|
|
151
|
+
return token;
|
|
152
|
+
})
|
|
153
|
+
.catch(error => {
|
|
154
|
+
// Request cancellation is not a helper outage. Do not let one
|
|
155
|
+
// cancelled request suppress refresh attempts for the next caller.
|
|
156
|
+
if (!controller.signal.aborted) {
|
|
157
|
+
this.#failure = { error, retryAt: Date.now() + this.#ttlMs };
|
|
158
|
+
}
|
|
159
|
+
if (this.#cached !== undefined) return this.#cached.token;
|
|
160
|
+
throw error;
|
|
161
|
+
})
|
|
162
|
+
.finally(() => {
|
|
163
|
+
if (this.#inflight === current) this.#inflight = undefined;
|
|
164
|
+
});
|
|
165
|
+
current = { controller, promise, waiters: 0 };
|
|
166
|
+
this.#inflight = current;
|
|
167
|
+
inflight = current;
|
|
168
|
+
}
|
|
169
|
+
return this.#wait(inflight, signal);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#wait(inflight: Inflight, signal?: AbortSignal): Promise<string> {
|
|
173
|
+
inflight.waiters++;
|
|
174
|
+
let onAbort: (() => void) | undefined;
|
|
175
|
+
const result = signal === undefined
|
|
176
|
+
? inflight.promise
|
|
177
|
+
: Promise.race([
|
|
178
|
+
inflight.promise,
|
|
179
|
+
new Promise<string>((_resolve, reject) => {
|
|
180
|
+
onAbort = () => reject(abortReason(signal));
|
|
181
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
182
|
+
}),
|
|
183
|
+
]);
|
|
184
|
+
return result.finally(() => {
|
|
185
|
+
if (onAbort) signal!.removeEventListener("abort", onAbort);
|
|
186
|
+
inflight.waiters--;
|
|
187
|
+
if (this.#inflight === inflight && inflight.waiters === 0) {
|
|
188
|
+
this.#inflight = undefined;
|
|
189
|
+
inflight.controller.abort(signal?.reason);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -78,6 +78,11 @@ function printHelp(log = console.log) {
|
|
|
78
78
|
log(" pi-mcp-adapter token set <server> Store a token read from stdin (masked prompt or pipe; never argv)");
|
|
79
79
|
log(" pi-mcp-adapter token status <server> Report whether a stored token matches the configured URL");
|
|
80
80
|
log(" pi-mcp-adapter token remove <server> Remove the stored token");
|
|
81
|
+
log("");
|
|
82
|
+
log("TypeSafe API key storage:");
|
|
83
|
+
log(" pi-mcp-adapter key set typesafe Store a key read from stdin (masked prompt or pipe; never argv)");
|
|
84
|
+
log(" pi-mcp-adapter key status typesafe Report the effective credential source without revealing it");
|
|
85
|
+
log(" pi-mcp-adapter key remove typesafe Remove the stored key");
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
function readJsonFile(filePath) {
|
|
@@ -214,8 +219,8 @@ async function importTokenModules(error) {
|
|
|
214
219
|
}
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
function
|
|
218
|
-
if (stdin.isTTY) return
|
|
222
|
+
function readSecretFromStdin(stdin, prompt = "Enter bearer token (input hidden): ") {
|
|
223
|
+
if (stdin.isTTY) return readSecretMasked(stdin, prompt);
|
|
219
224
|
return new Promise((resolve, reject) => {
|
|
220
225
|
let data = "";
|
|
221
226
|
stdin.setEncoding("utf8");
|
|
@@ -228,9 +233,9 @@ function readTokenFromStdin(stdin) {
|
|
|
228
233
|
}
|
|
229
234
|
|
|
230
235
|
// Raw-mode masked prompt: the token never echoes to the terminal.
|
|
231
|
-
function
|
|
236
|
+
function readSecretMasked(stdin, prompt) {
|
|
232
237
|
return new Promise((resolve, reject) => {
|
|
233
|
-
process.stderr.write(
|
|
238
|
+
process.stderr.write(prompt);
|
|
234
239
|
stdin.setRawMode(true);
|
|
235
240
|
stdin.resume();
|
|
236
241
|
stdin.setEncoding("utf8");
|
|
@@ -306,7 +311,7 @@ async function runToken(argv, log, error, stdin) {
|
|
|
306
311
|
}
|
|
307
312
|
|
|
308
313
|
if (action === "set") {
|
|
309
|
-
const token = await
|
|
314
|
+
const token = await readSecretFromStdin(stdin);
|
|
310
315
|
if (!token) {
|
|
311
316
|
error("No token provided on stdin.");
|
|
312
317
|
return 1;
|
|
@@ -352,6 +357,48 @@ async function runToken(argv, log, error, stdin) {
|
|
|
352
357
|
return 0;
|
|
353
358
|
}
|
|
354
359
|
|
|
360
|
+
async function runKey(argv, log, error, stdin) {
|
|
361
|
+
const [action, provider, ...extra] = argv;
|
|
362
|
+
if (!["set", "status", "remove"].includes(action) || provider !== "typesafe") {
|
|
363
|
+
error("Usage: pi-mcp-adapter key <set|status|remove> typesafe");
|
|
364
|
+
error("`key set` reads the API key from stdin only. Never pass the key as an argument.");
|
|
365
|
+
return 1;
|
|
366
|
+
}
|
|
367
|
+
if (extra.length > 0) {
|
|
368
|
+
error("Unexpected extra arguments. The API key must not be passed on the command line; pipe it on stdin or use the interactive prompt.");
|
|
369
|
+
return 1;
|
|
370
|
+
}
|
|
371
|
+
let store;
|
|
372
|
+
try { store = await import("./dist/jev-key-store.js"); }
|
|
373
|
+
catch (err) {
|
|
374
|
+
error("Unable to load TypeSafe key command module.");
|
|
375
|
+
error(`Import failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
376
|
+
return 1;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (action === "set") {
|
|
380
|
+
const apiKey = await readSecretFromStdin(stdin, "Enter TypeSafe API key (input hidden): ");
|
|
381
|
+
if (!apiKey) { error("No API key provided on stdin."); return 1; }
|
|
382
|
+
try { store.saveJevApiKey(apiKey); }
|
|
383
|
+
catch (err) { error(err instanceof Error ? err.message : "TypeSafe API key could not be stored."); return 1; }
|
|
384
|
+
log("TypeSafe API key stored in the OS secure credential store.");
|
|
385
|
+
if (Object.hasOwn(process.env, "TYPESAFE_API_KEY")) log("Note: TYPESAFE_API_KEY is present and overrides the stored key.");
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
if (action === "status") {
|
|
389
|
+
const status = store.resolveJevCredential();
|
|
390
|
+
if (status.status === "present") { log(`source=${status.source}`); return 0; }
|
|
391
|
+
if (status.status === "unavailable") { error(`unavailable: ${status.message}`); return 1; }
|
|
392
|
+
log("missing");
|
|
393
|
+
return 1;
|
|
394
|
+
}
|
|
395
|
+
try { store.removeJevApiKey(); }
|
|
396
|
+
catch (err) { error(err instanceof Error ? err.message : "TypeSafe API key could not be removed."); return 1; }
|
|
397
|
+
log("TypeSafe API key removed from the OS secure credential store.");
|
|
398
|
+
if (Object.hasOwn(process.env, "TYPESAFE_API_KEY")) log("TYPESAFE_API_KEY is still present and overrides the stored key.");
|
|
399
|
+
return 0;
|
|
400
|
+
}
|
|
401
|
+
|
|
355
402
|
export async function main(argv = process.argv.slice(2), log = console.log, error = console.error, stdin = process.stdin) {
|
|
356
403
|
const [command, ...rest] = argv;
|
|
357
404
|
|
|
@@ -364,6 +411,10 @@ export async function main(argv = process.argv.slice(2), log = console.log, erro
|
|
|
364
411
|
return runToken(rest, log, error, stdin);
|
|
365
412
|
}
|
|
366
413
|
|
|
414
|
+
if (command === "key") {
|
|
415
|
+
return runKey(rest, log, error, stdin);
|
|
416
|
+
}
|
|
417
|
+
|
|
367
418
|
if (command === "install") {
|
|
368
419
|
error("The custom downloader has been retired.");
|
|
369
420
|
error("Use `pi install npm:pi-mcp-adapter` instead, then optionally run `pi-mcp-adapter init`.");
|