@fitchmultz/pi-mcp-adapter 5.0.0 → 6.0.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/OAUTH.md +3 -3
  3. package/README.md +153 -85
  4. package/app-bridge.bundle.js +75 -33
  5. package/commands.ts +9 -6
  6. package/direct-tools.ts +25 -358
  7. package/dist/app-bridge.bundle.js +75 -33
  8. package/dist/commands.js +9 -6
  9. package/dist/direct-tools.d.ts +2 -4
  10. package/dist/direct-tools.js +21 -319
  11. package/dist/gateway-arguments.d.ts +33 -0
  12. package/dist/gateway-arguments.js +63 -0
  13. package/dist/index.js +181 -276
  14. package/dist/init.js +6 -20
  15. package/dist/mcp-code.d.ts +2 -1
  16. package/dist/mcp-code.js +87 -41
  17. package/dist/mcp-output-guard.d.ts +57 -2
  18. package/dist/mcp-output-guard.js +129 -24
  19. package/dist/mcp-panel.d.ts +1 -1
  20. package/dist/mcp-panel.js +34 -97
  21. package/dist/mcp-script-worker.mjs +5 -2
  22. package/dist/mcp-status.js +2 -1
  23. package/dist/metadata-cache.d.ts +1 -1
  24. package/dist/metadata-cache.js +25 -88
  25. package/dist/prompts.d.ts +1 -1
  26. package/dist/prompts.js +1 -1
  27. package/dist/proxy-modes.d.ts +40 -6
  28. package/dist/proxy-modes.js +270 -485
  29. package/dist/resource-tools.d.ts +15 -0
  30. package/dist/resource-tools.js +26 -1
  31. package/dist/sampling-handler.js +4 -1
  32. package/dist/search-ranking.d.ts +0 -2
  33. package/dist/search-ranking.js +66 -137
  34. package/dist/state.d.ts +1 -1
  35. package/dist/tool-loader.d.ts +32 -0
  36. package/dist/tool-loader.js +152 -0
  37. package/dist/tool-metadata.d.ts +67 -0
  38. package/dist/tool-metadata.js +23 -17
  39. package/dist/tool-registrar.d.ts +4 -4
  40. package/dist/tool-registrar.js +18 -13
  41. package/dist/tool-result-renderer.d.ts +3 -0
  42. package/dist/tool-result-renderer.js +8 -3
  43. package/dist/types.d.ts +11 -36
  44. package/dist/utils.js +2 -4
  45. package/gateway-arguments.ts +73 -0
  46. package/index.ts +166 -299
  47. package/init.ts +6 -20
  48. package/mcp-code.ts +71 -45
  49. package/mcp-output-guard.ts +145 -27
  50. package/mcp-panel.ts +38 -109
  51. package/mcp-script-worker.mjs +5 -2
  52. package/mcp-status.ts +2 -1
  53. package/metadata-cache.ts +31 -93
  54. package/package.json +14 -9
  55. package/prompts.ts +1 -1
  56. package/proxy-modes.ts +265 -517
  57. package/resource-tools.ts +36 -1
  58. package/sampling-handler.ts +4 -1
  59. package/scripts/build-app-bridge.mjs +15 -0
  60. package/scripts/build.mjs +3 -2
  61. package/scripts/prepare.mjs +1 -0
  62. package/search-ranking.ts +71 -135
  63. package/skills/mcp-scripting/SKILL.md +67 -20
  64. package/state.ts +1 -1
  65. package/tool-loader.ts +153 -0
  66. package/tool-metadata.ts +22 -17
  67. package/tool-registrar.ts +18 -13
  68. package/tool-result-renderer.ts +10 -2
  69. package/types.ts +15 -40
  70. package/utils.ts +2 -4
  71. package/dist/ts-shape.d.ts +0 -2
  72. package/dist/ts-shape.js +0 -147
  73. package/ts-shape.ts +0 -145
package/CHANGELOG.md CHANGED
@@ -5,7 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [6.0.0] - 2026-09-22
9
+
10
+ ### Breaking changes
11
+
12
+ - New gateway calls require an explicit `action` (`status`, `search`, `list`, `describe`, `call`, `connect`, `instructions`, `resources`, `read-resource`, `read-result`, `auth-start`, `auth-complete`, or `ui-messages`) and object arguments. Stored v5 optional-mode calls and JSON-string arguments are normalized only at ingress.
13
+ - `mcp_search` discovers and loads full typed tool schemas for the next request, without executing a hit. `directTools` pins the initial active set; other eligible tools remain available for later discovery. Host tool allowlists and `MCP_DIRECT_TOOLS=__none__` remain binding.
14
+ - Resources are listed and read explicitly by server and URI, not registered as synthetic functions. The panel shows separate resource counts and only real-tool pin choices. Legacy resource aliases remain gateway/script-callable; old resource pins keep the gateway available even with `disableProxyTool` enabled.
15
+ - Script `tools.describe` preserves complete JSON Schema and raw descriptor fields rather than returning `inputTypeScript`. Update consumers to use `inputSchema` and optional `outputSchema`.
16
+ - Cold caches no longer trigger connections to every lazy server. Global search reports partial catalog coverage; selected server discovery, configured pins, and eager/keep-alive startup remain intentional connection paths.
17
+ - Metadata cache format 2 preserves raw tool/resource descriptors and reads legacy format 1. Downgrading to v5 ignores format 2 and requires rediscovery. Config paths and the OAuth namespace are unchanged from v5; see [migration](README.md#upgrading-from-v5).
18
+
19
+ ### Added
20
+
21
+ - Branch-scoped typed-tool selection in native Pi session entries, restored on resume, reload, tree navigation, and working-directory changes. Optional native tool-search hosts use exact namespaced lazy references; official Pi 0.87.0 uses the ordinary loader with the same discover-then-call workflow. Existing pins keep flat names.
22
+ - Conditional native asynchronous execution for typed tools when the host and selected model route both support it, excluding configured approval, MCP App UI, and sequential checkpoint flows.
23
+ - Gateway `resources`, `read-resource`, and `read-result` actions; script `tools.resources`, `tools.readResource`, and `tools.readResult` methods. Resource access retains exposure, filtering, authentication, and approval policy.
24
+ - Model-visible references to retained raw results for structured, media, resource, oversized, and extra-field payloads. Saved-result readback selects JSON Pointers/fields before bounded character paging and never repeats a server call. Artifacts respect the SDK host's `outputDirectory`.
25
+
26
+ ### Changed
27
+
28
+ - Search uses MiniSearch over tool names, descriptions, and parameter guidance. Discovery and descriptions retain canonical descriptors, including output schemas, annotations, and `_meta`; app-only tools stay hidden from the model and panel.
29
+ - Typed, gateway, and script calls share dispatch, authentication, approvals, and call capture. Scripts receive raw MCP results, including error-result data, without rendering unused text artifacts. Tiny plain-text results remain inline.
30
+ - Render `structuredContent` alongside content blocks instead of suppressing it when text or images are present. Existing output byte/line limits still bound model-facing text.
31
+ - MCP Apps uses 2.0.0 alongside the split MCP SDK 2.0.0 runtime, removing the runtime SDK 1 dependency.
32
+ - Panel and command status distinguish real tools, resources, startup pins, and undiscovered catalogs while retaining authentication, reconnect, and error controls.
9
33
 
10
34
  ## [5.0.0] - 2026-09-19
11
35
 
package/OAUTH.md CHANGED
@@ -231,7 +231,7 @@ mcp({
231
231
  })
232
232
  ```
233
233
 
234
- You can also pass only the `code` query parameter with `args: { code: "..." }`. JSON-string args remain supported. Redirect URL completion validates the saved OAuth state; raw code completion is available for providers that display a code directly.
234
+ You can also pass only the `code` query parameter with `args: { code: "..." }`. Use object arguments; JSON-string arguments are accepted only by the stored-v5-call ingress compatibility path. Redirect URL completion validates the saved OAuth state; raw code completion is available for providers that display a code directly.
235
235
 
236
236
  ### Newly required permissions
237
237
 
@@ -248,8 +248,8 @@ Auth-only connection replacement lets accepted native operations finish on the o
248
248
  Once authenticated, use the server normally:
249
249
 
250
250
  ```
251
- mcp({ server: "my-oauth-server" })
252
- mcp({ tool: "my-tool", args: { key: "value" } })
251
+ mcp({ action: "list", server: "my-oauth-server" })
252
+ mcp({ action: "call", server: "my-oauth-server", tool: "my-tool", args: { key: "value" } })
253
253
  ```
254
254
 
255
255
  The SDK automatically:
package/README.md CHANGED
@@ -16,11 +16,17 @@ Mario wrote about [why you might not need MCP](https://mariozechner.at/posts/202
16
16
 
17
17
  His take: skip MCP entirely, write simple CLI tools instead.
18
18
 
19
- But the MCP ecosystem has useful stuff - databases, browsers, APIs. This adapter gives you access without the bloat. One proxy tool (~200 tokens) instead of hundreds. The agent discovers what it needs on-demand. Servers only start when you actually use them.
19
+ The MCP ecosystem has useful databases, browsers, and APIs. This adapter keeps their full tool schemas out of context until needed. `mcp_search` discovers and loads typed tools for the next request; `mcp` handles explicit gateway actions, and `mcp_script` composes MCP calls in JavaScript. Lazy servers stay disconnected until selected discovery or a call needs them.
20
+
21
+ ## Pi release qualification
22
+
23
+ The development baseline is official Pi **0.87.0**; host peers remain wildcard. `npm run check:compat` verifies the installed selected SDK and manifest CLI identity, builds/typechecks, runs the existing Vitest and memory-only OAuth suites, then packs and loads a runtime-only consumer through native SDK startup/reload and the actual bundled RPC CLI. Existing real reload/direct-tool tests retain their local MCP servers and cleanup assertions. The fork lane requires native checkpoint tests for both an idle adapter and a live stdio server (which must block sleep without being stopped).
24
+
25
+ This credential-free Pi gate excludes only the separately built interactive-visualizer example's three tests. To qualify that example, retain the existing CI steps: install its dependencies, run `npm run --prefix examples/interactive-visualizer build`, then run the complete `npm test`. MCP protocol conformance remains a separate `npm run test:conformance` gate. None of these checks should read live server configs, launch an authenticated browser, or use real OAuth credentials. Use an empty HOME/agent directory and disposable working directory.
20
26
 
21
27
  ## Install
22
28
 
23
- Requires Pi 0.84.0 or later and Node.js 22.19.0 or later.
29
+ Requires Pi 0.87.0 or later and Node.js 22.19.0 or later.
24
30
 
25
31
  ```bash
26
32
  pi install npm:@fitchmultz/pi-mcp-adapter
@@ -30,9 +36,19 @@ Or install from Git: `pi install git:github.com/fitchmultz/pi-mcp-adapter`.
30
36
 
31
37
  Restart Pi after installation. Existing v4 users should migrate first.
32
38
 
39
+ ### Upgrading from v5
40
+
41
+ Version 6 keeps v5's package, config paths, and OAuth namespace. No credential migration is needed.
42
+
43
+ - Use `mcp_search({ query: "...", server: "name" })` to discover and load typed tools. It loads matches for the next request; it never executes a search hit. `directTools` now pins tools at startup rather than deciding which tools can ever be loaded.
44
+ - Give every new gateway call an explicit `action`: `mcp({ action: "search", query: "..." })`, `mcp({ action: "describe", tool: "..." })`, or `mcp({ action: "call", tool: "...", args: {} })`. Arguments are objects. Stored v5 calls with optional mode fields or JSON-string arguments are normalized at the ingress compatibility boundary; they are not the advertised v6 interface.
45
+ - Resources use `resources` and `read-resource` actions, or `tools.resources` and `tools.readResource` in scripts. Generated `read_<resource>` aliases remain callable through gateway/script compatibility, but no longer appear as functions or panel checkboxes. Remove resource aliases from `directTools`; legacy resource pins keep the gateway available even with `disableProxyTool: true`.
46
+ - Script descriptions always return complete JSON Schema in `inputSchema`, with `outputSchema` and other descriptor fields when supplied. Replace `inputTypeScript` consumers. Calls keep raw MCP results, including error-result data; use returned result references for bounded readback without another server call.
47
+ - Global search reports partial catalog coverage instead of starting every uncached lazy server. Select a server to discover it. Cache format 2 preserves raw descriptors and still reads format 1; downgrading to v5 ignores format 2 and requires rediscovery.
48
+
33
49
  ### Upgrading from v4
34
50
 
35
- Version 5 uses its own package, state paths and OAuth credential namespace. Remove the previous adapter package entry from Pi settings (for example, `pi remove npm:pi-mcp-adapter` for the unscoped npm source), then install the distribution above. Keep only one adapter loaded in a Pi host.
51
+ The Fitch distribution uses its own package, state paths and OAuth credential namespace. Remove the previous adapter package entry from Pi settings (for example, `pi remove npm:pi-mcp-adapter` for the unscoped npm source), then install the distribution above. Keep only one adapter loaded in a Pi host.
36
52
 
37
53
  After installation, **before restarting Pi**, run from your project directory:
38
54
 
@@ -98,26 +114,23 @@ Project layers and project-local host imports are read only after Pi marks the p
98
114
 
99
115
  `/mcp disable <server>` and `/mcp enable <server>` persist only the `disabled` field in the project-local `.pi/fitch-mcp-adapter/mcp.json`, which is the highest-precedence Pi layer. Enabling removes the project flag when lower layers are enabled, or writes `false` when needed to override a disabled lower source. This applies even when the effective server came from a shared global/project file, an imported host config, or `configPath`; the source file is never rewritten and credentials are never copied. Run `/reload` after changing the flag so registered tool surfaces are refreshed. The manual equivalent is to add `{ "disabled": true }` to a server in any normal MCP config. Supplied in-memory `createMcpAdapter({ config })` configurations are isolated and do not read or write this project override; the commands are unavailable in that mode.
100
116
 
101
- Servers are **lazy by default** — they won't connect until you actually call one of their tools. The adapter caches tool metadata so search and describe work without live connections.
117
+ Servers are **lazy by default**. Cached tool descriptors can be searched without a live connection. To discover an uncached server and load the tools relevant to a task:
102
118
 
119
+ ```js
120
+ mcp_search({ query: "take screenshot", server: "chrome-devtools" })
103
121
  ```
104
- mcp({ search: "screenshot" })
105
- ```
106
- ```
107
- chrome_devtools_take_screenshot
108
- Take a screenshot of the page or element.
109
122
 
110
- Parameters:
111
- format (enum: "png", "jpeg", "webp") [default: "png"]
112
- fullPage (boolean) - Full page instead of viewport
113
- ```
114
- ```
115
- mcp({ tool: "chrome_devtools_take_screenshot", args: { format: "png" } })
116
- ```
123
+ The returned exact tool references become available with their full argument schemas on the next model request. The agent then calls the chosen typed tool normally. Discovery does not run it.
117
124
 
118
- `args` can be a JSON object or a JSON string. Prefer the object form when your model handles it reliably; the string form remains supported for providers that need simpler schemas.
125
+ If the host only permits `mcp`, use explicit gateway actions instead:
119
126
 
120
- Two calls instead of 26 tools cluttering the context.
127
+ ```js
128
+ mcp({ action: "search", query: "take screenshot", server: "chrome-devtools" })
129
+ mcp({ action: "describe", tool: "chrome_devtools_take_screenshot" })
130
+ mcp({ action: "call", tool: "chrome_devtools_take_screenshot", args: { format: "png" } })
131
+ ```
132
+
133
+ Gateway search returns schemas without activating direct tools. All paths preserve the same authentication, approval, and call-capture behavior.
121
134
 
122
135
  ## Config
123
136
 
@@ -223,7 +236,7 @@ Before any asynchronous flush, the adapter fences owned activity and pauses its
223
236
 
224
237
  ### Runtime status snapshots
225
238
 
226
- Extensions can subscribe to the adapter's versioned shared event-bus channel instead of parsing `/mcp` or `mcp({})` output:
239
+ Extensions can subscribe to the adapter's versioned shared event-bus channel instead of parsing `/mcp` or `mcp({ action: "status" })` output:
227
240
 
228
241
  ```ts
229
242
  import { MCP_STATUS_EVENT, type McpStatusSnapshot } from "@fitchmultz/pi-mcp-adapter";
@@ -235,7 +248,7 @@ pi.events.on(MCP_STATUS_EVENT, (snapshot) => {
235
248
  });
236
249
  ```
237
250
 
238
- `MCP_STATUS_EVENT` is `fitch-mcp-adapter/status/v1`. The snapshot is read-only machine-readable data with copied per-server entries. It includes `totalTools`, `totalResources`, `connectedCount`, and `disabledCount`; each server includes `name`, `status`, `toolCount`, and `disabled`, with `resourceCount` when known and `failedAgoSeconds` only for an active failure. Reading status never connects a lazy server, starts authentication, or exposes SDK clients, transports, credentials, or server definitions. An initial snapshot is emitted after initialization, updates are emitted for status and metadata changes, and an empty snapshot is emitted when the session shuts down.
251
+ `MCP_STATUS_EVENT` is `fitch-mcp-adapter/status/v1`. The snapshot is read-only machine-readable data with copied per-server entries. It includes `totalTools`, `totalResources`, `connectedCount`, and `disabledCount`; each server includes `name`, `status`, `toolCount`, and `disabled`, with `resourceCount` when known, `catalogKnown` indicating whether discovery has completed, and `failedAgoSeconds` only for an active failure. An unknown catalog is not a confirmed zero-tool server. Reading status never connects a lazy server, starts authentication, or exposes SDK clients, transports, credentials, or server definitions. An initial snapshot is emitted after initialization, updates are emitted for status and metadata changes, and an empty snapshot is emitted when the session shuts down.
239
252
 
240
253
  In the configuration examples below, `30000` is illustrative only. If `requestTimeoutMs` is omitted or set to `<= 0`, the MCP SDK default timeout is used.
241
254
 
@@ -306,10 +319,10 @@ This transport-failure option does not retry OAuth failures, JSON-RPC errors, to
306
319
  | `lifecycle` | `"lazy"` (default), `"eager"`, `"keep-alive"`, or `"lazy-keep-alive"` |
307
320
  | `idleTimeout` | Minutes before idle disconnect (overrides global) |
308
321
  | `requestTimeoutMs` | Request timeout in milliseconds for live MCP calls (overrides global; if omitted or `<= 0`, the MCP SDK default timeout is used) |
309
- | `exposeResources` | Expose MCP resources as tools (default: true) |
310
- | `directTools` | `true`, `string[]`, or `false` — register tools individually instead of through proxy |
322
+ | `exposeResources` | Make MCP resources available through resource listing/reading (default: true) |
323
+ | `directTools` | `true`, `string[]`, or `false` — pin real tools into the initial active tool set |
311
324
  | `toolPrefix` | Override global `settings.toolPrefix` for this server (`"server"`, `"short"`, `"none"`, or `"mcp"`) |
312
- | `includeTools` | `string[]` of tool names or glob patterns to expose (matches original names like `get_screenshot`, generated resource names like `read_figjam`, and prefixed names like `figma_get_screenshot`) |
325
+ | `includeTools` | `string[]` of tool names or glob patterns to expose (matches original names like `get_screenshot`, legacy resource filter aliases like `read_figjam`, and prefixed names like `figma_get_screenshot`) |
313
326
  | `excludeTools` | `string[]` of tool names or glob patterns to hide (applied after `includeTools`) |
314
327
  | `debug` | Show server stderr (default: false) |
315
328
  | `trace` | Enable metadata-only JSONL protocol tracing for this server; payloads, prompts, tool arguments/results, authorization data, and URLs are never persisted |
@@ -424,10 +437,10 @@ Persistent OAuth is unsupported out of the box on Android/Termux because `@napi-
424
437
 
425
438
  ### Lifecycle Modes
426
439
 
427
- - **`lazy`** (default) — Don't connect at startup. Connect on first tool call. Disconnect after idle timeout. Cached metadata keeps search/list working without connections.
440
+ - **`lazy`** (default) — Connect on selected discovery or first call, then disconnect after idle timeout. Cached metadata keeps search/list working without connections. Configured pins can intentionally discover their server at startup.
428
441
  - **`eager`** — Connect at startup but don't auto-reconnect if the connection drops. No idle timeout by default (set `idleTimeout` explicitly to enable).
429
442
  - **`keep-alive`** — Connect at startup. Auto-reconnect via health checks. No idle timeout. Use for servers you always need available.
430
- - **`lazy-keep-alive`** — Don't connect at startup. Connect on first tool call (like `lazy`). Once spawned, never idle-shut down and auto-reconnect via health checks if the process dies (like `keep-alive`). Use for servers that are expensive to start but should stay resident after their first use.
443
+ - **`lazy-keep-alive`** — Connect on selected discovery or first call (like `lazy`); configured pins can discover it at startup. Once spawned, never idle-shut down and auto-reconnect via health checks if the process dies (like `keep-alive`). Use for servers that are expensive to start but should stay resident after their first use.
431
444
 
432
445
  ### Settings
433
446
 
@@ -465,9 +478,9 @@ Persistent OAuth is unsupported out of the box on Android/Termux because `@napi-
465
478
  | `oauthDir` | Parent directory for legacy OAuth imports. Runtime uses its `fitch-mcp-adapter/` child; explicit v4 migration reads the original directory. Relative paths resolve from the active project cwd. `FITCH_MCP_OAUTH_DIR` overrides the final runtime import path; the old `MCP_OAUTH_DIR` is read only by explicit migration. Persistent credentials live in the OS credential store. |
466
479
  | `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. |
467
480
  | `directTools` | Global default for all servers (default: false). Per-server overrides this. |
468
- | `freezeDirectTools` | Keep direct-tool registration stable after the initial sync so automatic reconnects and list-change notifications do not rebuild the system prompt. Use `mcp({ connect: "server" })` or `/mcp reconnect <server>` to refresh deliberately. Default: false. |
481
+ | `freezeDirectTools` | Keep direct-tool registration stable after the initial sync so automatic reconnects and list-change notifications do not rebuild the system prompt. Use `mcp({ action: "connect", server: "server" })` or `/mcp reconnect <server>` to refresh deliberately. Default: false. |
469
482
  | `scriptMode` | Register the MCP-only `mcp_script` plain-JavaScript tool (default: true). Set to `false` to hide it. |
470
- | `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
483
+ | `disableProxyTool` | Hide `mcp` once configured pinned tools are available. Legacy resource pins keep it available for resource access. |
471
484
  | `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
472
485
  | `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
473
486
  | `samplingAutoApprove` | Skip sampling confirmation prompts. Required for sampling in non-UI sessions (default: false). |
@@ -501,7 +514,8 @@ Oversized MCP tool/resource results are guarded by default so a single huge resp
501
514
 
502
515
  - Inline text output is capped at **50 KiB / 2,000 lines** (matching Pi's built-in `bash` guard). Larger output is truncated to a head preview and the full text is saved to a temp file whose path is included in the result, so the agent can `read`/`grep` it.
503
516
  - **Image content blocks pass through unchanged** — only text output is guarded. Images are delivered to the provider as native image content.
504
- - In proxy and direct modes, `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 file.
517
+ - In gateway and direct modes, `details.mcpResult` is kept raw when its JSON is **≤ 16 KiB**; larger results use a compact summary and retain the full raw JSON in a file. Scripts receive the raw result before model-facing rendering, so they can reduce it without creating an unused text preview.
518
+ - Structured data, media, resources, extra protocol fields, and oversized results include a model-visible **result reference** to the saved raw result. Tiny plain-text results remain inline without an artifact. `structuredContent` is shown even when the server also returns content blocks.
505
519
 
506
520
  Tune the limits with the object form:
507
521
 
@@ -513,11 +527,19 @@ Tune the limits with the object form:
513
527
  }
514
528
  ```
515
529
 
516
- Set `"outputGuard": false` — or the env kill switch `MCP_OUTPUT_GUARD=0` — to disable the guard and restore raw output behavior. Saved files are created with mode `0600` under the system temp directory (or the SDK host's `outputDirectory`) and are not cleaned up automatically; note that spilled MCP output may contain sensitive data.
530
+ Set `"outputGuard": false` — or the env kill switch `MCP_OUTPUT_GUARD=0` — to disable the guard and restore raw output behavior. Recovery notices remain visible even when a configured cap is too small to fit them. Saved files are created with mode `0600` under the system temp directory (or the SDK host's `outputDirectory`) and are not cleaned up automatically; note that spilled MCP output may contain sensitive data.
531
+
532
+ Read a retained result without repeating the MCP operation:
533
+
534
+ ```js
535
+ mcp({ action: "read-result", ref: "/returned/result/path", path: "/structuredContent/rows", fields: ["id", "title"], offset: 0, limit: 12000 })
536
+ ```
537
+
538
+ `path` is an RFC 6901 JSON Pointer; `fields` keeps immediate keys on the selected object or each object in an array. Selection happens before paging. `offset` and `limit` count **characters in the selected, pretty-printed JSON**, starting at zero—not rows, lines, or bytes. Readback defaults to 12,000 characters and remains bounded by the output byte/line caps. Continue with `details.nextOffset` until it is `null`. Readback requires an existing adapter output artifact in this host's output directory; it never calls the server again. The same operation is available as `tools.readResult` in scripts.
517
539
 
518
540
  ### MCP Scripting
519
541
 
520
- 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 `mcp_script` tool. For a single MCP call, search, describe, status check, or auth action, use `mcp` instead. Set `settings.scriptMode` to `false` to hide the scripting tool.
542
+ 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 `mcp_script` tool. Use `mcp_search` for typed discovery, a loaded typed tool for a single call, and `mcp` for explicit gateway actions. Set `settings.scriptMode` to `false` to hide the scripting tool.
521
543
 
522
544
  The bundled `mcp-scripting` skill is a separate Pi package resource. To hide that skill while keeping the adapter extension installed, replace the package entry in Pi settings with the object form and disable package skills:
523
545
 
@@ -531,29 +553,65 @@ The bundled `mcp-scripting` skill is a separate Pi package resource. To hide tha
531
553
 
532
554
  Preserve any version pin in `source` if your existing package entry has one. You can also disable package resources through `pi config`.
533
555
 
534
- For example, this is the JavaScript passed as the `code` argument to `mcp_script`:
556
+ First inspect candidate schemas. This script discovers and describes without executing a search hit:
535
557
 
536
558
  ```js
537
- const { items } = await tools.search({ query: "search issues", server: "github" });
538
- const candidate = items[0];
539
- if (!candidate) return { error: "No matching tool" };
559
+ const found = await tools.search({ query: "search issues", server: "github" });
560
+ if (found.error) return found;
561
+ emit(found.coverage);
562
+ for (const item of found.items) {
563
+ emit(await tools.describe({ path: item.path, server: item.server }));
564
+ }
565
+ ```
540
566
 
541
- const details = await tools.describe({ path: candidate.path });
542
- if (details.error) return details;
567
+ After choosing a tool and checking its schema, pass ordinary JavaScript as `mcp_script`'s `code` argument:
543
568
 
544
- const result = await tools.call(details.path, { query: "is:open label:bug" });
545
- if (!result.ok) return result;
546
- emit({ tool: details.path, completed: true });
547
- return result.data;
569
+ ```js
570
+ const results = [];
571
+ for (const query of ["is:open label:bug", "is:open label:docs"]) {
572
+ const result = await tools.call("github_search_issues", { query });
573
+ if (!result.ok) return result;
574
+ results.push({ query, data: result.data });
575
+ }
576
+ return results;
548
577
  ```
549
578
 
550
- `tools.describe` returns argument information as `inputTypeScript` when representable, otherwise as the original JSON Schema in `inputSchema`. Only one is included; inspect it before constructing arguments.
579
+ The script API is:
580
+
581
+ | Method | Result |
582
+ |--------|--------|
583
+ | `tools.search({ query, server?, limit?, offset? })` | `{ items, total, hasMore, nextOffset, coverage }`; items include `path`, `name`, and `server`; discovery errors include `error` |
584
+ | `tools.describe({ path, server? })` | Complete descriptor with `path`, `server`, `name`, and the server's `inputSchema`, `outputSchema`, `title`, annotations, `_meta`, and other fields when present; failures contain `error` |
585
+ | `tools.call(path, args, server?)` or `tools.exact_flat_name(args)` | `{ ok: true, data, resultRef? }` or `{ ok: false, error: { code, message, ... }, data?, resultRef? }` |
586
+ | `tools.resources({ server, limit?, offset? })` | `{ mode: "resources", server, items, total, hasMore, nextOffset }`; failures contain `error` |
587
+ | `tools.readResource({ server, uri })` | The call envelope above, with raw resource data in `data.contents` |
588
+ | `tools.readResult({ ref, path?, fields?, offset?, limit? })` | `{ content, details }`; readback text and `details.nextOffset`, or `details.error` on failure |
589
+ | `emit(value)` / `console.log(value)` | Captured output before the final return value |
590
+
591
+ For an ambiguous flat name, call `tools.call(descriptor.name, args, descriptor.server)` using the original name and server returned by describe.
592
+
593
+ Search/resource pages default to 12 items, maximum 100. `coverage` contains `complete`, `knownServers`, and `unknownServers`. Selecting an uncached server discovers it; global search does not connect every lazy server. Script discovery does not activate typed tools. Descriptions retain JSON Schema rather than a lossy TypeScript projection.
551
594
 
552
- See the bundled `mcp-scripting` skill for the complete workflow guide. The API is `await tools.search({ query, server?, limit?, offset? })`, `await tools.describe({ path })`, `tools.call(path, args)`, direct flat calls, `emit(value)`, and a captured `console`. Use ordinary JavaScript loops and Promise utilities for composition; fluent helpers such as `tools.find(...).one()`, `tools.parallel(...)`, and `tools.retry(...)` are not provided. MCP calls return `{ ok: true, data }` or `{ ok: false, error: { code, message } }`, so a failed call does not stop the rest of the script. Result details include a concise `calls` trace with each operation, its path or query, outcome, and duration. Emitted values and console output appear before the script's final return value, and the combined result uses the normal MCP output guard. The default timeout is 30 seconds unless the SDK host overrides it with `defaultScriptTimeoutMs`; an explicit per-script `timeoutMs` always wins. Each script runs in a worker thread that is terminated on timeout or cancellation, including for infinite loops.
595
+ `data` is the raw MCP result: tool calls usually return `{ content, structuredContent?, isError?, ... }`; resource reads return `{ contents, ... }`. Error results retain their raw data when available. Handle `ok: false`; ordinary call failures do not automatically stop the script. Capture failures stop dependent work, and ambiguous outcomes require provider readback instead of blindly repeating a call. Result details include a concise `calls` trace and saved result references. Reduce raw results before emitting them; the final output uses the normal output guard.
553
596
 
554
- For a tool-restricted subagent, launch the child Pi with its tool allowlist set to `["mcp_script"]`. Have the parent discover MCP tool names with `mcp({ search: "..." })` and include the relevant prefixed names in the child's task; the child can then loop, filter, and chain those MCP calls without filesystem, shell, or edit tools. The adapter's ordinary lazy connection, authentication, output guard, abort handling, and approval gates still apply to every call.
597
+ Use JavaScript loops and Promise utilities; fluent helpers such as `tools.find(...).one()`, `tools.parallel(...)`, and `tools.retry(...)` are not provided. Await every call before returning. The default timeout is 30 seconds unless the SDK host overrides it with `defaultScriptTimeoutMs`; an explicit per-script `timeoutMs` wins. Each script runs in a local worker that is terminated on timeout or cancellation, including infinite loops.
555
598
 
556
- `mcp_script` is a trusted agent-authored MCP scripting layer, not an isolation boundary. If you need isolation, run Pi in an isolated environment. It is distinct from Pi's code-mode skill: Pi's skill batches general Pi tools, while `mcp_script` exposes MCP calls only and can be the child's sole tool.
599
+ For a tool-restricted subagent, a host can allow only `mcp_script`; include the relevant exact tool names or let the script use its discovery methods. A gateway-only host can use `mcp({ action: "search", query: "..." })` without activating tools outside its allowlist. Authentication, approvals, filters, and cancellation apply to every call.
600
+
601
+ `mcp_script` is a trusted agent-authored MCP scripting layer, not an isolation boundary. The worker exposes no Node, filesystem, or network globals such as `process`, `Buffer`, or `fetch`. Run Pi in an isolated environment if you need isolation. This is local orchestration, not provider-native programmatic tool calling; it does not claim Codex native PTC support. Pi's general code-mode skill is separate and batches general Pi tools.
602
+
603
+ See the bundled `mcp-scripting` skill for the workflow and exact return shapes.
604
+
605
+ ### MCP Resources
606
+
607
+ Resources are catalog entries addressed by URI, separate from tools:
608
+
609
+ ```js
610
+ mcp({ action: "resources", server: "docs", limit: 12, offset: 0 })
611
+ mcp({ action: "read-resource", server: "docs", uri: "docs://guide" })
612
+ ```
613
+
614
+ List first, then use the exact URI. Listings preserve the server's resource descriptor fields, including name, title, MIME type, icons, and `_meta` when supplied. `exposeResources`, include/exclude filters, disabled servers, authentication, and approval policy remain binding. Existing `read_<resource>` filter names still select resources, but these compatibility aliases are not typed functions and cannot be pinned in `/mcp`. Scripts use `tools.resources` and `tools.readResource`.
557
615
 
558
616
  ### MCP Prompts
559
617
 
@@ -573,9 +631,17 @@ When Pi exposes dialog-capable UI, the adapter advertises form elicitation suppo
573
631
 
574
632
  URL mode is advertised only in TUI mode. The adapter displays the requesting server, target host, and full URL, and always requires consent before opening the browser. It also handles URL-required tool errors (`-32042`) and completion notifications; after completing the browser interaction, retry the original tool call.
575
633
 
576
- ### Direct Tools
634
+ ### Typed Discovery and Pinned Tools
635
+
636
+ `mcp_search({ query, server?, limit?, offset? })` searches real tools and loads the matching typed functions for the next request. It defaults to five matches, with a maximum of 100 and zero-based pagination. Use a specific query, or an empty query with `server` to browse that server. It does not execute a hit or rewrite dynamic instructions into the system prompt.
637
+
638
+ Cached eligible schemas register inactive; only pins and tools selected for the current branch are active. The adapter persists canonical `{ server, tool }` selections in native Pi session entries and restores them across resume, reload, branch navigation, and working-directory changes. Host tool allowlists remain binding.
577
639
 
578
- By default, all MCP tools are accessed through the single `mcp` proxy tool. This keeps context small but means the LLM has to discover MCP tools via proxy search. If you want specific tools to show up directly in the agent's tool list — alongside `read`, `bash`, `edit`, etc. — add `directTools` to your config.
640
+ On hosts with native tool-search support, lazy tools use exact references in namespace `mcp_<server>` with the original tool name as the leaf. Existing direct pins retain their flat prefixed names. On official Pi 0.87.0, the ordinary `mcp_search` loader activates flat tools with the same discover-then-call workflow. Always use the exact reference returned by discovery rather than constructing names.
641
+
642
+ Typed tools may opt into native asynchronous execution only when both the host's pending-call API and the selected model route support it. Tools requiring configured approval, an MCP App UI, or the `beforeExecute` sequential checkpoint barrier keep ordinary awaited execution. This is separate from local `mcp_script` orchestration.
643
+
644
+ Add `directTools` to pin frequently used tools into the initial active set:
579
645
 
580
646
  Per-server:
581
647
 
@@ -602,9 +668,9 @@ Per-server:
602
668
 
603
669
  | Value | Behavior |
604
670
  |-------|----------|
605
- | `true` | Register all tools from this server as individual Pi tools |
606
- | `["tool_a", "tool_b"]` | Register only these tools (use original MCP names) |
607
- | Omitted or `false` | Proxy only (default) |
671
+ | `true` | Pin all eligible real tools from this server |
672
+ | `["tool_a", "tool_b"]` | Pin these tools (use original MCP names) |
673
+ | Omitted or `false` | No initial pins; load tools with `mcp_search` when needed (default) |
608
674
 
609
675
  To set a global default for all servers:
610
676
 
@@ -621,11 +687,11 @@ To set a global default for all servers:
621
687
  }
622
688
  ```
623
689
 
624
- Per-server `directTools` overrides the global setting. The example above registers direct tools for every server except `huge-server`.
690
+ Per-server `directTools` overrides the global setting. The example above pins tools for every server except `huge-server`, whose tools remain discoverable.
625
691
 
626
- Set `MCP_DIRECT_TOOLS=__none__` before loading the adapter to suppress all direct-tool registration, including after lazy connections and metadata updates. With default scripting enabled, only `mcp` and `mcp_script` are exposed by the adapter. Configured servers remain available through those tools; normal tool filters and disabled-server settings still apply.
692
+ Set `MCP_DIRECT_TOOLS=__none__` before loading the adapter to suppress all direct-tool registration, including pins and search-driven activation. `mcp_search` can still discover schemas and report that no functions were loaded; use gateway or script calls instead. Normal tool filters, host allowlists, and disabled-server settings still apply.
627
693
 
628
- To expose only a subset of a noisy server, add `includeTools` on the server. Values can be exact original names, generated resource names such as `read_<resource>`, prefixed names, or simple glob patterns:
694
+ To expose only a subset of a server, add `includeTools`. Values can be exact original names, prefixed names, or simple glob patterns. Legacy `read_<resource>` names continue to filter resource access:
629
695
 
630
696
  ```json
631
697
  {
@@ -653,17 +719,15 @@ To hide specific tools while still using `directTools: true`, add `excludeTools`
653
719
  }
654
720
  ```
655
721
 
656
- `includeTools` and `excludeTools` filter direct tools, proxy search/list/describe, and the `/mcp` panel view.
657
-
658
- Each direct tool costs ~150-300 tokens in the system prompt (name + description + schema). Good for targeted sets of 5-20 tools. For servers with 75+ tools, stick with the proxy or pick specific tools with a `string[]`. If 75+ direct tools resolve, the adapter prints a warning but still registers the tools you configured.
722
+ `includeTools` and `excludeTools` filter typed tools, gateway/script discovery, resource access, and the `/mcp` panel. App-only tools stay out of the model catalog and pin choices.
659
723
 
660
- Direct tools register from the metadata cache (`~/.pi/agent/fitch-mcp-adapter/mcp-cache.json` by default, or `$PI_CODING_AGENT_DIR/fitch-mcp-adapter/mcp-cache.json` when set), so no server connections are needed at startup. On the first session after adding `directTools` to a new server, the cache won't exist yet — tools fall back to proxy-only while the cache populates, then the extension hot-loads the refreshed direct tools into the current session. Servers that advertise MCP list-change notifications refresh the current session when their tool or resource list changes. Stale direct tools are deactivated from the active tool set and reactivated if a later refresh restores them. To force a refresh: `/mcp reconnect <server>`.
724
+ Active schemas consume context; inactive cached schemas do not. Pin the tools you routinely need and discover the rest as the task requires.
661
725
 
662
- If prompt-cache stability matters more than automatic direct-tool hot-loading, set `settings.freezeDirectTools` to `true`. The initial direct-tool sync still runs, but later automatic reconnects, lazy-connects, and list-change notifications keep the registered tool surface unchanged. Deliberate refreshes through `mcp({ connect: "server" })` or `/mcp reconnect <server>` still update direct tools.
726
+ Valid cached descriptors avoid startup connections. Missing or stale metadata for configured pins is discovered intentionally at startup; eager and keep-alive servers also connect as configured. An empty cache does not trigger discovery of every lazy server. Search coverage reports which configured servers are still unknown.
663
727
 
664
- When you change direct-tool toggles in `/mcp`, the extension updates direct tool registration in the current session. Broader setup writes from `/mcp setup` still use Pi's normal reload flow because they can add or restructure MCP config files.
728
+ MCP catalog notifications and reconnects refresh eligible metadata. Removed tools leave the active set. `settings.freezeDirectTools: true` keeps the automatic registered surface stable after initial sync; deliberate `mcp({ action: "connect", server: "name" })` or `/mcp reconnect <server>` refreshes remain available.
665
729
 
666
- **Interactive configuration:** Run `/mcp` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. You can reconnect servers and toggle tools between direct and proxy from the same overlay. For OAuth, press Enter on a server that needs auth or `ctrl+a` on any OAuth server.
730
+ Run `/mcp` to see known tools, pinned counts, resource counts, and connection state. Uncached servers say **undiscovered**. Expand a server to pin or unpin real tools; resources have no checkboxes and use the resource actions instead. Pin changes apply to the current session and persist through `directTools`. Discovered session selections are separate from these startup pins: unpinning leaves a tool active if discovery already selected it for the current branch. Press Enter on a server that needs auth or `ctrl+a` on an OAuth server to authenticate; `ctrl+r` reconnects. Broader setup changes still use Pi's normal reload flow.
667
731
 
668
732
  **Guided first-run setup:** Run `/mcp setup` to inspect detected shared MCP files, adopt compatibility imports from other hosts, open discovered config paths, preview exact before/after file diffs for writes, scaffold a minimal project `.mcp.json`, add a curated known server (DeepWiki, Context7, Notion, GitHub, or Chrome DevTools), or quick-add RepoPrompt into a standard/shared MCP file.
669
733
 
@@ -671,7 +735,7 @@ When you change direct-tool toggles in `/mcp`, the extension updates direct tool
671
735
 
672
736
  ### MCP UI Integration
673
737
 
674
- MCP servers can ship interactive UIs via the [MCP UI](https://github.com/MCP-UI-Org/mcp-ui) standard. When you call a tool that has a UI resource, the adapter opens it in a native macOS window via [Glimpse](https://github.com/hazat/glimpse) if available, otherwise falls back to the browser.
738
+ MCP servers can ship interactive UIs via [MCP Apps](https://github.com/modelcontextprotocol/ext-apps). The adapter uses MCP Apps 2.0.0 with the split MCP SDK 2.0.0 runtime. When you call a tool that has a UI resource, the adapter opens it in a native macOS window via [Glimpse](https://github.com/hazat/glimpse) if available, otherwise falls back to the browser.
675
739
 
676
740
  **How it works:**
677
741
 
@@ -715,7 +779,7 @@ Returns accumulated messages from UI sessions. Each message includes `type`, `se
715
779
  - Tool consent gates whether UIs can call MCP tools (never/once-per-server/always)
716
780
  - `_meta.ui.visibility` controls audience: tools marked app-only stay out of the model tool list, and tools marked model-only cannot be called from the UI iframe.
717
781
  - Works with both stdio and HTTP MCP servers
718
- - Uses a local 408KB AppBridge bundle (MCP SDK + Zod) for browser↔server communication
782
+ - Uses a local MCP Apps AppBridge bundle for browser↔server communication
719
783
  - Enforces CSP from standard `_meta.ui.csp` and OpenAI-compatible `_meta["openai/widgetCSP"]` metadata in the response header while preserving provider HTML.
720
784
 
721
785
  ### Local Example: Interactive Visualizer
@@ -751,32 +815,34 @@ Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/fitch-mcp-adapt
751
815
 
752
816
  ## Usage
753
817
 
754
- | Mode | Example |
755
- |------|---------|
756
- | Status | `mcp({ })` |
757
- | List server | `mcp({ server: "name", limit: 12, offset: 0 })` |
758
- | Search | `mcp({ search: "screenshot navigate", limit: 12, offset: 0 })` |
759
- | Describe | `mcp({ describe: "tool_name" })` |
760
- | Instructions | `mcp({ instructions: "name" })` |
761
- | Call | `mcp({ tool: "...", args: { key: "value" } })` |
762
- | Connect | `mcp({ connect: "server-name", limit: 12, offset: 0 })` |
818
+ | Action | Example |
819
+ |--------|---------|
820
+ | Discover and load typed tools | `mcp_search({ query: "screenshot", server: "chrome-devtools", limit: 5 })` |
821
+ | Status | `mcp({ action: "status" })` |
822
+ | List server tools | `mcp({ action: "list", server: "name", limit: 12, offset: 0 })` |
823
+ | Search without activation | `mcp({ action: "search", query: "screenshot navigate", limit: 12 })` |
824
+ | Describe | `mcp({ action: "describe", tool: "tool_name", server: "name" })` |
825
+ | Instructions | `mcp({ action: "instructions", server: "name" })` |
826
+ | Call | `mcp({ action: "call", tool: "tool_name", args: { key: "value" } })` |
827
+ | Connect or refresh | `mcp({ action: "connect", server: "name", limit: 12, offset: 0 })` |
828
+ | List resources | `mcp({ action: "resources", server: "name" })` |
829
+ | Read resource | `mcp({ action: "read-resource", server: "name", uri: "docs://guide" })` |
830
+ | Read saved result | `mcp({ action: "read-result", ref: "/returned/result/path", path: "/structuredContent" })` |
763
831
  | UI messages | `mcp({ action: "ui-messages" })` |
764
832
  | Auth start | `mcp({ action: "auth-start", server: "name" })` |
765
833
  | Auth complete | `mcp({ action: "auth-complete", server: "name" })` after the browser callback, or supply `args: { redirectUrl: "..." }` |
766
834
 
767
- `mcp({ connect: "server-name" })` refreshes an already connected server, so new tools, resources, prompts, and instructions can load without restarting Pi. Its returned server listing accepts the same `limit` and `offset` options as list mode.
768
-
769
- MCP proxy and direct-tool results render compactly by default: long text shows the first three terminal-wrapped lines plus Pi's configured `app.tools.expand` keybinding hint, while the full result remains available when expanded and is still returned unchanged to the model.
835
+ `action` is required. Search requires `query`; describe/call require `tool`; list/connect/instructions/resources/auth require `server`; resource reads require `server` and `uri`; saved-result reads require `ref`. Pass object `args`. Optional `server` disambiguates tool calls and descriptions.
770
836
 
771
- Search covers MCP tools. Space-separated words are ranked by weighted matches across name, server, and description. Search and server listings return one page at a time (`limit` defaults to 12 and is capped at 100); use `details.nextOffset` for the next page.
837
+ `connect` refreshes an already connected server, including tools, resources, prompts, and instructions. Server-scoped discovery connects that server if its catalog is unknown. Global search uses known catalogs and returns `coverage: { complete, knownServers, unknownServers }`; partial coverage is never presented as an exhaustive result. Gateway search provides schemas without activating functions, so it works for gateway-only hosts.
772
838
 
773
- Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_library_id` finds `context7_resolve-library-id`. When `describe` or `tool` cannot resolve a name, the result includes top suggestions so the agent can correct a typo or missing prefix in the same turn.
839
+ Search ranks names, descriptions, and parameter guidance using MiniSearch. Catalog pages use item offsets: gateway/script search and lists default to 12, while `mcp_search` defaults to 5; maximum 100. Follow the returned `nextOffset`. Saved-result readback uses character offsets instead.
774
840
 
775
- When `includeSchemas` is enabled, search and describe render common JSON Schema parameters as compact TypeScript shapes like `{ query: string; limit?: number; }`, with the older schema formatter retained as a fallback for unsupported schemas.
841
+ Describe preserves the complete server descriptor, including JSON Schemas, annotations, `_meta`, and extensions when present. Search and typed-tool discovery exclude app-only tools and resources. Legacy hyphen/underscore matching remains a compatibility convenience; ambiguous names require an explicit server rather than executing an arbitrary match. Use the exact names returned by discovery.
776
842
 
777
- The `mcp` tool description contains usage guidance and configured server names, not changing catalog counts or server-instruction previews. Connecting, refreshing metadata, or renewing credentials therefore does not rewrite that description. Status, search, describe, and connect results still reflect current metadata; explicitly configured direct tools keep their normal refresh behavior.
843
+ The gateway description stays stable as catalogs and credentials change. Server instructions are available through `action: "instructions"` and previews in server listings, rather than injected dynamically into the prompt.
778
844
 
779
- Servers that provide usage guidance via the MCP `instructions` field surface it as a preview at the end of `mcp({ server: "name" })` listings and in full via `mcp({ instructions: "name" })`. Instructions are captured at connect time and cached alongside tool metadata, so they stay available without a live connection. No extra discovery call is required when the needed details are already in the conversation.
845
+ Gateway and typed-tool results render compactly in the terminal: long text shows the first three terminal-wrapped lines with Pi's `app.tools.expand` hint. Expanding reveals the guarded result. Structured data and saved-result references remain visible to the model.
780
846
 
781
847
  ## Commands
782
848
 
@@ -784,7 +850,8 @@ Servers that provide usage guidance via the MCP `instructions` field surface it
784
850
  |---------|--------------|
785
851
  | `/mcp` | Interactive panel and first-run onboarding surface |
786
852
  | `/mcp setup` | Guided setup for imports, a minimal `.mcp.json`, curated known servers, RepoPrompt quick-add, and config-path inspection |
787
- | `/mcp tools` | List all tools |
853
+ | `/mcp tools` | List discovered real tools, excluding resource aliases |
854
+ | `/mcp status` | Show connection state and separate tool/resource counts |
788
855
  | `/mcp prompts` | List all MCP prompts registered as slash commands |
789
856
  | `/mcp reconnect` | Reconnect all servers |
790
857
  | `/mcp reconnect <server>` | Connect or reconnect a single server |
@@ -794,24 +861,25 @@ Servers that provide usage guidance via the MCP `instructions` field surface it
794
861
  | `/mcp-auth` | Open an OAuth server picker in interactive UI sessions |
795
862
  | `/mcp-auth <server>` | OAuth setup for a specific server |
796
863
 
797
- If `settings.autoAuth` is `true`, `mcp({ connect: ... })`, `mcp({ tool: ... })`, and direct/script tool calls may run OAuth when needed, with at most one automatic auth attempt per invocation and one post-auth retry. Browser authorization requires an interactive host. Auth-only replacement preserves accepted work on the old client; ordinary `/mcp reconnect` and panel `ctrl+r` remain hard resets.
864
+ If `settings.autoAuth` is `true`, `mcp({ action: "connect", server: "..." })`, gateway calls, and typed/script tool calls may run OAuth when needed, with at most one automatic auth attempt per invocation and one post-auth retry. Browser authorization requires an interactive host. Auth-only replacement preserves accepted work on the old client; ordinary `/mcp reconnect` and panel `ctrl+r` remain hard resets.
798
865
 
799
866
  In interactive sessions, you can also authenticate from `/mcp` with `ctrl+a` or Enter on a server that needs auth. In remote/headless sessions, use the proxy tool's `auth-start` and `auth-complete` actions. Complete without arguments when the browser reaches Pi's callback, or paste the redirect URL when it cannot. `/mcp-auth` without a server only opens a picker in the interactive UI.
800
867
 
801
868
  ### MCP output schemas
802
869
 
803
- Advertised tool `outputSchema` values support JSON Schema draft-07 and 2020-12. Unstamped schemas use the SDK's 2020-12 default. Returned `structuredContent` is validated against the advertised schema for both proxy and direct-tool calls. When content blocks are empty, any JSON structured value—including `null`, `false`, and `0`—is rendered as text. Non-empty content, including images, takes precedence.
870
+ Advertised tool `outputSchema` values support JSON Schema draft-07 and 2020-12. Unstamped schemas use the SDK's 2020-12 default. Returned `structuredContent` is validated against the advertised schema for both proxy and direct-tool calls. Any JSON structured value—including `null`, `false`, and `0`—is rendered as text, including alongside non-empty content or images. The raw result remains available for script reduction and saved-result readback.
804
871
 
805
872
  ## How It Works
806
873
 
807
- - One `mcp` tool in context (~200 tokens) instead of hundreds
808
- - Servers are lazy by default — they connect on first tool call, not at startup
874
+ - Stable `mcp_search`, `mcp`, and optional `mcp_script` entry points; full typed schemas load when selected
875
+ - Lazy servers connect on selected discovery or calls; configured pins and eager/keep-alive servers can bootstrap intentionally
809
876
  - Tool metadata is cached to disk so search/list/describe work without live connections
810
877
  - Idle servers disconnect after 10 minutes (configurable), reconnect automatically on next use
811
878
  - npx-based servers resolve to direct binary paths, skipping the ~143 MB npm parent process
812
- - MCP server validates arguments, not the adapter
879
+ - Pi validates loaded typed-tool arguments against their schemas; gateway and script arguments are validated by the MCP server
813
880
  - Keep-alive servers get health checks and auto-reconnect
814
- - Specific tools can be promoted from the proxy to first-class Pi tools via `directTools` config, so the LLM sees them directly instead of having to search
881
+ - `directTools` pins initial tools; search-driven selections persist separately on the native session branch
882
+ - Resources use explicit URI-based listing and reading, without synthetic function definitions
815
883
 
816
884
  ## Limitations
817
885