pi-mcp-adapter 3.2.0 → 4.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.
- package/CHANGELOG.md +80 -0
- package/OAUTH.md +4 -2
- package/README.md +72 -10
- package/cli.js +1 -0
- package/commands.ts +20 -2
- package/config.ts +19 -1
- package/direct-tool-surface.ts +3 -2
- package/direct-tools.ts +12 -2
- package/dist/config.d.ts +6 -0
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -1
- package/dist/elicitation-handler.d.ts +9 -1
- package/dist/elicitation-handler.js +65 -2
- package/dist/elicitation-handler.js.map +1 -1
- package/dist/jev-client.js +7 -5
- package/dist/jev-client.js.map +1 -1
- package/dist/jev-key-store.d.ts +1 -0
- package/dist/jev-key-store.js +11 -0
- package/dist/jev-key-store.js.map +1 -1
- package/dist/mcp-auth-flow.d.ts +2 -0
- package/dist/mcp-auth-flow.js +12 -3
- package/dist/mcp-auth-flow.js.map +1 -1
- package/dist/metadata-cache.d.ts +12 -0
- package/dist/metadata-cache.js +50 -6
- package/dist/metadata-cache.js.map +1 -1
- package/dist/output-shape.d.ts +36 -0
- package/dist/output-shape.js +338 -0
- package/dist/output-shape.js.map +1 -0
- package/dist/package-mcp-loader.js +9 -5
- package/dist/package-mcp-loader.js.map +1 -1
- package/dist/server-manager.js +27 -16
- package/dist/server-manager.js.map +1 -1
- package/dist/state.d.ts +6 -0
- package/dist/ts-shape.d.ts +3 -0
- package/dist/ts-shape.js +153 -0
- package/dist/ts-shape.js.map +1 -0
- package/dist/types.d.ts +24 -3
- package/dist/types.js.map +1 -1
- package/dist/unix-socket-transport.js +1 -1
- package/dist/unix-socket-transport.js.map +1 -1
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +17 -0
- package/dist/utils.js.map +1 -1
- package/elicitation-handler.ts +78 -1
- package/index.ts +72 -12
- package/init.ts +20 -5
- package/jev-client.ts +7 -4
- package/jev-key-store.ts +7 -0
- package/mcp-auth-flow.ts +13 -2
- package/mcp-code.ts +126 -68
- package/mcp-output-guard.ts +24 -5
- package/mcp-script-worker.mjs +6 -2
- package/mcp-setup-panel.ts +583 -302
- package/metadata-cache.ts +60 -5
- package/output-shape.ts +348 -0
- package/package-mcp-loader.ts +8 -5
- package/package.json +6 -6
- package/prompts.ts +68 -0
- package/proxy-modes.ts +149 -106
- package/server-manager.ts +28 -15
- package/skills/mcp-scripting/SKILL.md +24 -28
- package/skills/mcp-scripting/references/jev.md +3 -0
- package/state.ts +6 -0
- package/tool-approval.ts +17 -4
- package/tool-metadata.ts +3 -1
- package/ts-shape.ts +1 -1
- package/types.ts +22 -3
- package/ui-server.ts +5 -2
- package/unix-socket-transport.ts +1 -1
- package/utils.ts +16 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.0.0] - 2026-09-30
|
|
11
|
+
|
|
12
|
+
### Highlights
|
|
13
|
+
|
|
14
|
+
- Figma now works with Pi through the Figma desktop app. `/mcp-adapter setup` offers it when the app is installed and tells you how to turn its server on.
|
|
15
|
+
- `/mcp-adapter setup` is easier to use: it keeps one size, groups actions into sections, shows details beside the list, and scrolls long previews.
|
|
16
|
+
- Other extensions can call your MCP tools directly, with the same approval rules the model gets.
|
|
17
|
+
- `mcpScript` is now opt-in. When you turn it on, scripts pick the right tool when servers share tool names and remember what each tool returns across sessions.
|
|
18
|
+
- Errors now say what to do when an OAuth server refuses Pi or a local MCP server isn't running, and Pi 0.99 and git-installed Pi packages work again.
|
|
19
|
+
|
|
20
|
+
### Breaking
|
|
21
|
+
|
|
22
|
+
- `mcpScript` is off by default. To keep using it, set `settings.scriptMode` to `true` in `mcp-adapter.json`. Its bundled skill, the pointer to it in the `mcp` tool description, and the large-result hint (see Changed) follow the same setting.
|
|
23
|
+
- `tools.describe` in `mcpScript` now looks tools up the same way as `mcp({ describe })`. Where it used to return the first server's match, it can now return `error: { code, message }`: `ambiguous_tool` when equally good matches exist on more than one server, and `server_disabled` or `server_backoff` when only a disabled or backed-off server has the tool. A clearly closer match, such as an exact name against a normalized one, is still picked without an error. Pass the `server` from the search result when the target matters: `tools.describe({ path, server })`.
|
|
24
|
+
- `observedOutput.target` from `tools.describe` is now the full expression that reads the JSON, such as `(await tools.call("github_list_issues", args)).data.structuredContent`, instead of `data.structuredContent`. Models misread the short form.
|
|
25
|
+
- `observedOutput` no longer has `calls`, and `describe` no longer says how many calls a shape came from, since saved shapes can come from earlier sessions.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Other extensions can call a configured MCP tool by emitting `pi-mcp-adapter:runtime-tool-call:v1` on Pi's event bus. The call goes through the same tool lookup and approval as `mcp({ tool })`, and `request.result` resolves to `{ ok: true, result }` or `{ ok: false, error }`. Thanks to [@Djarid](https://github.com/Djarid) for [PR #735](https://github.com/nicobailon/pi-mcp-adapter/pull/735).
|
|
30
|
+
- `/mcp-adapter setup` offers Figma (desktop) when the Figma app is installed. It adds the app's local server at `http://127.0.0.1:3845/mcp` and, if nothing answers there yet, says how to turn it on in Figma. Figma's remote server only accepts clients Figma has approved, and Pi isn't one yet.
|
|
31
|
+
- `tools.describe` and `tools.call` in `mcpScript` accept the `server` from a search result: `tools.describe({ path, server })` and `tools.call(path, args, { server })`. This makes a tool usable when two servers share its name, as with `toolPrefix: "none"`. Before, describe returned the first server's tool and call failed as ambiguous. The README has a new "Composable tool search" section showing search, filter, describe, and call in one script.
|
|
32
|
+
- With `mcpScript` on, the output shapes seen from tools are saved in `mcp-cache.json`, so a later session can script a tool without first calling it to see its fields. Saved shapes hold field names and types only, and are dropped when the tool's description, input schema, or server config changes.
|
|
33
|
+
- When an `mcpScript` run throws, times out, or returns nothing useful (`[]`, `{}`, `null`, `""`, or no value), the result ends with the output shapes of the tools it called, so a wrong field guess can be fixed without another call.
|
|
34
|
+
- `settings.scriptSkill: "model"` adds the `mcp-scripting` skill's path to the `mcpScript` description so the model reads it before writing a script. The default, `"manual"`, leaves the skill to `/skill:mcp-scripting`.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- `/mcp-adapter setup` keeps one size as you move through it, groups actions into sections, and shows details beside the list. PgUp/PgDn scroll long details. The `Close` row is gone; press Esc to close.
|
|
39
|
+
- When a server refuses Pi's OAuth client registration, the error now says what to do: for Figma's remote server, use the desktop app's server through `/mcp-adapter setup`; for other servers, set `oauth.clientId` if the provider gave you one. Before, it only said `Dynamic Client Registration rejected (HTTP 403): Forbidden`.
|
|
40
|
+
- When nothing is listening at a `localhost`, `127.0.0.1`, or `[::1]` server URL, the error now adds `Nothing is listening at <url>. Start the app or local process that serves this MCP server.`
|
|
41
|
+
- `tools.search` in `mcpScript` now behaves like `mcp({ search })`. An empty query with a `server` lists that server's tools, and a search that can't run returns `error: { code, message }` next to `items: []`, with the same codes as `mcp({ search })`, instead of a bare empty result.
|
|
42
|
+
- With `mcpScript` on, MCP tool results of 8 KiB or more end with a one-line hint to use `mcpScript` when passing them to another call, so models stop retyping large results by hand.
|
|
43
|
+
- The `mcpScript` description is shorter and now explains that a tool call's `data` is the raw MCP result and how to read JSON from it.
|
|
44
|
+
- Output shapes list a wide object that repeats as a named type, so a GitHub-style issue list shape is about 40% shorter with every field still shown.
|
|
45
|
+
- The `mcp-scripting` skill is about half as long, with `jev.evaluate` details moved to `references/jev.md`. It now says to write the real script first, check the first item's fields before a loop that writes, and fix the script from the listed fields when it fails or finds nothing.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Pi 0.99's built-in MCP extension is detected again. Pi 0.99 renamed it `builtin:mcp`, so the adapter took over `/mcp` and warned about `mcp.json` on every start even though the built-in extension owns that file. Thanks to [@Sebastianlopez-dev](https://github.com/Sebastianlopez-dev) for reporting it in [#736](https://github.com/nicobailon/pi-mcp-adapter/issues/736).
|
|
50
|
+
- Installing next to Pi 0.99 no longer prints an npm peer dependency warning. The optional `@earendil-works/pi-ai` peer range now includes `^0.99.0`, and the adapter is tested against Pi 0.99.1. Thanks to [@fl4pj4ck](https://github.com/fl4pj4ck) for reporting it in [#741](https://github.com/nicobailon/pi-mcp-adapter/issues/741).
|
|
51
|
+
- MCP servers from a Pi package installed from a git URL with a port or a user other than `git`, such as `ssh://git@gitlab.example.com:2235/acme/tools.git`, now load. Before, they were skipped without a warning. Thanks to [@shura-v](https://github.com/shura-v) for [#746](https://github.com/nicobailon/pi-mcp-adapter/pull/746).
|
|
52
|
+
- MCP servers from a Pi package installed at a ref that contains a slash, such as `https://gitlab.example.com/acme/tools.git@release/v2`, now load.
|
|
53
|
+
- MCP metadata marked `private` is no longer reused from the cache in a later session, including output shapes saved for `mcpScript`. It is still used within the session that fetched it. Thanks to [@quifox](https://github.com/quifox) for [#743](https://github.com/nicobailon/pi-mcp-adapter/pull/743).
|
|
54
|
+
- When a server's tool list changes and saving the refreshed metadata fails, the update is retried instead of lost. Thanks to [@quifox](https://github.com/quifox) for [#742](https://github.com/nicobailon/pi-mcp-adapter/pull/742).
|
|
55
|
+
- `regex: true` in `mcpScript`'s `tools.search` was ignored: scripts got ranked word matches, and an invalid pattern gave no error. It now runs the same checked regex search as `mcp({ search })`.
|
|
56
|
+
- Models no longer call doubled names such as `tracker_tracker_list_issues` in `mcpScript`. The `code` parameter's example now uses the tool name exactly as `mcp` lists it: `tools.call("github_search_issues", args)`.
|
|
57
|
+
- When two servers share a tool name, the `tools.call` examples in `observedOutput.target` and in the fields listed after a failed or empty script include the server, such as `tools.call("echo", args, { server: "other" })`, so copying them works. A script that called the shared name on both servers now lists each server's fields, not just the last one's. Closes [#751](https://github.com/nicobailon/pi-mcp-adapter/issues/751).
|
|
58
|
+
- Output shapes in `describe` no longer drop nested fields such as `labels: { name: string }[]` on GitHub-style lists.
|
|
59
|
+
- The `mcp` tool description mentions `mcpScript` only when it is available. Before, it pointed to `mcpScript` even with `settings.scriptMode` set to `false`.
|
|
60
|
+
|
|
61
|
+
## [3.3.0] - 2026-09-29
|
|
62
|
+
|
|
63
|
+
### Highlights
|
|
64
|
+
|
|
65
|
+
- You can now tell a read-only MCP tool from one that deletes data. The hints servers declare on their tools show up when you describe a tool and in the approval prompt.
|
|
66
|
+
- New `approveTools: "destructive"` setting asks before any tool that may change or delete data, without making you list tool names.
|
|
67
|
+
- A tool call no longer fails with "Request timed out" while you are still answering the server's input form.
|
|
68
|
+
- The model gets a better idea of what a tool returns, even when the server declares no output schema.
|
|
69
|
+
- MCP prompt slash commands now suggest argument names as you type.
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- The hints a server declares on its tools (read-only, destructive, idempotent, open-world, title) are now kept, including in the metadata cache, and shown in `mcp({ describe })`, in `tools.describe` inside `mcpScript`, and in the tool approval prompt. Search results and direct-tool descriptions stay the same, so nothing is added to every turn's context.
|
|
74
|
+
- `approveTools: "destructive"` asks before any tool that may change or delete data. Only tools the server marks read-only or non-destructive run without a prompt; tools with no hints still ask. It works globally or per server.
|
|
75
|
+
- For tools that declare no output schema, `mcp({ describe })` and `tools.describe` in `mcpScript` now show the output shape seen so far this session, after a successful call returns structured content or a JSON text result. Only field names and types are shown, never values. The shape is kept in memory only and is labeled as observed, not a contract.
|
|
76
|
+
- MCP prompt slash commands now suggest declared argument names as you type. Arguments you already entered are kept, suggestions follow the server's latest prompt list, and nothing connects to a server just to show them. Thanks to [@quifox](https://github.com/quifox) for [PR #733](https://github.com/nicobailon/pi-mcp-adapter/pull/733).
|
|
77
|
+
- Jev semantic search works with an existing OpenRouter key. Set `SYSTEMONE_ENDPOINT=https://openrouter.ai/api/alpha/decisions` and `OPENROUTER_API_KEY` is used as the key, with the model defaulting to `typesafe/jev-1.13`. The key is never sent to any other endpoint.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- A mistyped `approveTools` value no longer turns approval off silently. Any value other than `true`, `false`, `"destructive"`, or a list of tool name patterns now asks before every tool.
|
|
82
|
+
- Dropped the unused `@modelcontextprotocol/ext-apps` dependency. MCP Apps already run from a bundled app bridge, and the package made a plain `npm install` pull in the old MCP SDK v1 and its HTTP server dependencies.
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- A tool call no longer times out while you are answering the server's input prompt. The request timeout (60 seconds by default) kept running while the form was open, so a slow answer failed the call with "Request timed out" and the answer was lost. The timeout now pauses while the prompt is open.
|
|
87
|
+
- Jev no longer rejects responses that carry extra provider details, such as OpenRouter's `id`, `provider`, and `usage.cost`. The documented OpenRouter setup previously failed with "Jev returned an invalid response."
|
|
88
|
+
- Typechecking the adapter's sources with `@types/node` 25 no longer fails in the Unix socket transport. Thanks to [@abdwhb-png](https://github.com/abdwhb-png) for [PR #732](https://github.com/nicobailon/pi-mcp-adapter/pull/732).
|
|
89
|
+
|
|
10
90
|
## [3.2.0] - 2026-09-28
|
|
11
91
|
|
|
12
92
|
### Highlights
|
package/OAUTH.md
CHANGED
|
@@ -314,9 +314,11 @@ The SDK automatically discovers OAuth endpoints from the MCP server. If discover
|
|
|
314
314
|
}
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
### "Dynamic client registration not supported"
|
|
317
|
+
### "Dynamic client registration not supported" or "Dynamic Client Registration rejected (HTTP 403)"
|
|
318
318
|
|
|
319
|
-
Some servers
|
|
319
|
+
Some servers only accept pre-registered clients. Obtain a client ID from your OAuth provider and set `oauth.clientId` (and `oauth.clientSecret` if the provider issued one).
|
|
320
|
+
|
|
321
|
+
Figma's remote server (`https://mcp.figma.com/mcp`) only accepts clients Figma has approved, and Pi isn't one yet. Use the Figma desktop app's local server instead: run `/mcp-adapter setup` and add Figma (desktop).
|
|
320
322
|
|
|
321
323
|
### Callback server already in use
|
|
322
324
|
|
package/README.md
CHANGED
|
@@ -225,6 +225,32 @@ Cross-extension registration uses Pi's shared event bus and does not require a r
|
|
|
225
225
|
|
|
226
226
|
Runtime registrations are session scoped and never written to config files. Duplicate server names fail closed against configured servers and other registrations. Registered servers use the normal lazy connection, OAuth, approval, and shutdown behavior, but they are proxy-tool-only and their tools become visible at the next tool sync. To change a definition, dispose the registration and register again.
|
|
227
227
|
|
|
228
|
+
### Runtime tool calls from other extensions
|
|
229
|
+
|
|
230
|
+
An extension can call a configured MCP tool from its own code, without a model turn:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const MCP_RUNTIME_TOOL_CALL_EVENT = "pi-mcp-adapter:runtime-tool-call:v1";
|
|
234
|
+
type RuntimeToolCallRequest = {
|
|
235
|
+
version: 1;
|
|
236
|
+
tool: string;
|
|
237
|
+
args?: Record<string, unknown>;
|
|
238
|
+
server?: string;
|
|
239
|
+
result?: Promise<
|
|
240
|
+
| { ok: true; result: { content: unknown[]; details?: unknown } }
|
|
241
|
+
| { ok: false; error: Error }
|
|
242
|
+
>;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const request: RuntimeToolCallRequest = { version: 1, tool: "search", args: { query: "mcp" } };
|
|
246
|
+
pi.events.emit(MCP_RUNTIME_TOOL_CALL_EVENT, request);
|
|
247
|
+
if (!request.result) throw new Error("pi-mcp-adapter is not installed");
|
|
248
|
+
const outcome = await request.result;
|
|
249
|
+
if (!outcome.ok) throw outcome.error;
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`tool` and `server` resolve the same way as `mcp({ tool, server })`, and approval settings and disabled servers apply as they do for that call. The adapter sets `request.result` to a promise during `emit()`; await it. A tool error or denied approval resolves with `ok: false`. Emit during `session_start` or later; calls fail after the session shuts down.
|
|
253
|
+
|
|
228
254
|
### SDK configuration
|
|
229
255
|
|
|
230
256
|
Use `createMcpAdapter` when an SDK or server integration already owns its MCP configuration:
|
|
@@ -571,7 +597,7 @@ When any enabled server uses `eager` or `keep-alive`, initialization also starts
|
|
|
571
597
|
| `projectServers` | Project-server admission policy for trusted headless sessions: `"ask"` (default, skip unapproved servers) or `"allow"`. Only user-global or explicitly selected config may set it; project files are ignored. |
|
|
572
598
|
| `ancestorConfigRoots` | Trusted absolute or `~/...` roots for opt-in ancestor config discovery. Only user-global or explicitly selected config may set it; roots outside cwd are ignored and the deepest matching root is used. |
|
|
573
599
|
| `agentPluginPaths` | Agent Plugins package directories to load MCP servers from. Relative paths resolve from the active project cwd. |
|
|
574
|
-
| `approveTools` | `true` to require approval before every MCP tool call, or an array of glob patterns such as `["github_delete_*", "notion_update_*"]`. Per-server `approveTools` overrides this. |
|
|
600
|
+
| `approveTools` | `true` to require approval before every MCP tool call, `"destructive"` to require it for tools the server does not mark read-only or non-destructive, or an array of glob patterns such as `["github_delete_*", "notion_update_*"]`. Per-server `approveTools` overrides this. |
|
|
575
601
|
| `oauthDir` | Legacy OAuth `tokens.json` import directory for this MCP config. Relative paths resolve from the active project cwd. `MCP_OAUTH_DIR` still wins when set. Persistent OAuth credentials are stored in the OS credential store, not this directory. |
|
|
576
602
|
| `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. |
|
|
577
603
|
| `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. |
|
|
@@ -581,7 +607,8 @@ When any enabled server uses `eager` or `keep-alive`, initialization also starts
|
|
|
581
607
|
| `directToolResultDetails` | Direct-tool result details: `"lean"` (default) or `"bounded"` to retain the guarded raw MCP result. |
|
|
582
608
|
| `warnOnLargeDirectTools` | Show the advisory when 75 or more direct tools resolve (default: `true`). Set to `false` to suppress only this advisory. |
|
|
583
609
|
| `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. |
|
|
584
|
-
| `scriptMode` | Register the MCP-only `mcpScript` plain-JavaScript tool (default:
|
|
610
|
+
| `scriptMode` | Register the MCP-only `mcpScript` plain-JavaScript tool and its bundled skill (default: false). Read when Pi loads the adapter; run `/reload` after changing it. |
|
|
611
|
+
| `scriptSkill` | How the model finds the bundled `mcp-scripting` skill when `scriptMode` is on: `"manual"` (default) keeps it to `/skill:mcp-scripting`; `"model"` adds its path to the `mcpScript` description so the model reads it before writing a script. |
|
|
585
612
|
| `exposeResources` | Expose MCP resources as tools (default: `true`). Set to `false` to disable globally across all servers. Per-server `exposeResources` overrides this. |
|
|
586
613
|
| `jev` | Optional System One Jev settings. A valid System One 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. `jev: false` disables both. Run `/mcp-adapter jev setup` for guided configuration. |
|
|
587
614
|
| `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 the gateway (`mcp({ search })` or a successful `mcp({ tool })` call). |
|
|
@@ -610,6 +637,8 @@ Use `approveTools` when a tool should stay visible but not run without confirmat
|
|
|
610
637
|
}
|
|
611
638
|
```
|
|
612
639
|
|
|
640
|
+
To be asked before any tool that may change or delete data, set `"approveTools": "destructive"` globally or per server. A tool then runs without a prompt only when its server marks it read-only (`readOnlyHint: true`) or non-destructive (`destructiveHint: false`); tools with no such hints, including those restored from an older metadata cache, are gated. Resource reads are never gated by this value. Any other string requires approval for every tool.
|
|
641
|
+
|
|
613
642
|
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.
|
|
614
643
|
|
|
615
644
|
**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.
|
|
@@ -697,7 +726,9 @@ System One decisions are the same API at different origins, so pointing at anoth
|
|
|
697
726
|
| Command Code | `https://api.commandcode.ai/provider/v1/systemone` | `typesafe/jev` |
|
|
698
727
|
| OpenRouter | `https://openrouter.ai/api/alpha/decisions` | `typesafe/jev-1.13` |
|
|
699
728
|
|
|
700
|
-
These are example configurations, subject to each provider's current documentation ([OpenCode Zen](https://opencode.ai/docs/zen/), [Command Code](https://commandcode.ai/docs/provider), [TypeSafe](https://docs.typesafe.ai/)).
|
|
729
|
+
These are example configurations, subject to each provider's current documentation ([OpenCode Zen](https://opencode.ai/docs/zen/), [Command Code](https://commandcode.ai/docs/provider), [OpenRouter](https://openrouter.ai/docs/guides/community/jev), [TypeSafe](https://docs.typesafe.ai/)).
|
|
730
|
+
|
|
731
|
+
With `SYSTEMONE_ENDPOINT` pointed at OpenRouter, an existing `OPENROUTER_API_KEY` works as the key and the model defaults to `typesafe/jev-1.13`. `OPENROUTER_API_KEY` is never sent to any other endpoint, and `SYSTEMONE_API_KEY` takes precedence over it.
|
|
701
732
|
|
|
702
733
|
The endpoint must be an absolute `https` URL with a path. A set-but-invalid `SYSTEMONE_ENDPOINT` disables Jev instead of falling back to the default. Treat the endpoint as trusted configuration: it receives the API key and the judgment payload. Credentials are stored per endpoint, so switching endpoints does not overwrite a saved key. Set the model with:
|
|
703
734
|
|
|
@@ -729,9 +760,9 @@ Optional `jev` controls bound timeout/retries, request and script budgets, seman
|
|
|
729
760
|
|
|
730
761
|
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.
|
|
731
762
|
|
|
732
|
-
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
|
|
763
|
+
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 `mcpScript` tool, which is off by default; set `settings.scriptMode` to `true` to register it and its bundled skill. For a single MCP call, search, describe, status check, or auth action, use `mcp` instead.
|
|
733
764
|
|
|
734
|
-
The bundled `mcp-scripting` skill is manual-only
|
|
765
|
+
The bundled `mcp-scripting` skill is manual-only: use `/skill:mcp-scripting`, or set `settings.scriptSkill` to `"model"` so the `mcpScript` description tells the model where to read it.
|
|
735
766
|
|
|
736
767
|
For example, this is the JavaScript passed as the `code` argument to `mcpScript`:
|
|
737
768
|
|
|
@@ -749,9 +780,30 @@ emit({ tool: details.path, completed: true });
|
|
|
749
780
|
return result.data;
|
|
750
781
|
```
|
|
751
782
|
|
|
752
|
-
|
|
783
|
+
For tool calls, successful `result.data` is the raw MCP `CallToolResult`, not the domain payload; resource reads return text. Use `result.data.structuredContent` when present. Otherwise most JSON APIs return their payload as text, so parse `result.data.content[0].text`. If neither shape is understood, return the envelope for inspection instead of coercing it to an empty collection.
|
|
784
|
+
|
|
785
|
+
#### Composable tool search
|
|
753
786
|
|
|
754
|
-
|
|
787
|
+
Use JavaScript to filter, sort, or combine search results before describing or calling tools. This adds no model context until a script runs.
|
|
788
|
+
|
|
789
|
+
```js
|
|
790
|
+
const found = await tools.search({ query: "issue", server: "github", limit: 50 });
|
|
791
|
+
if (found.error) return found.error;
|
|
792
|
+
|
|
793
|
+
const readOnly = [];
|
|
794
|
+
for (const hit of found.items) {
|
|
795
|
+
const details = await tools.describe({ path: hit.path, server: hit.server });
|
|
796
|
+
if (details.annotations?.readOnlyHint) readOnly.push(hit.path);
|
|
797
|
+
}
|
|
798
|
+
return readOnly;
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
- `tools.search` runs the same search as `mcp({ search })`: ranked words by default, `regex: true` for a pattern, or `searchMode: "semantic"` for Jev. An empty `query` with a `server` lists that server's tools. Results come one page at a time (`limit` defaults to 12); follow `nextOffset` for the rest.
|
|
802
|
+
- Every hit carries its `server`. Pass it on with `tools.describe({ path, server })` and `tools.call(path, args, { server })`, so the script reaches the tool it found even when two servers expose the same name, as with `toolPrefix: "none"`.
|
|
803
|
+
- A search that cannot run returns `items: []` with `error: { code, message }`, using the same codes as `mcp({ search })`, such as `empty_query`, `server_disabled`, `server_backoff`, and `invalid_pattern`.
|
|
804
|
+
- Annotations are the server's own hints, not guarantees. Script searches never activate `directTools: "search"` tools, and every call still goes through the normal approval gate.
|
|
805
|
+
|
|
806
|
+
See the bundled `mcp-scripting` skill for the complete workflow guide. The API is `await tools.search({ query, server?, regex?, searchMode?, limit?, offset? })`, `await tools.describe({ path, server? })`, `tools.call(path, args, { server }?)`, 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; each script runs in a worker thread that is terminated at the deadline, including for infinite loops.
|
|
755
807
|
|
|
756
808
|
Successful intermediate results reach the script without presentation truncation, details summaries, or output-guard spill files. Each script has a fixed **16 MiB cumulative UTF-8 JSON transfer budget** for successful intermediate data, shared by sequential and parallel calls. A result that cannot fit returns `{ ok: false, error: { code: "intermediate_result_too_large", message } }` and a failed call trace; rejected bytes do not consume the budget, and the script can continue. Request less data or start a new script; there is no configuration option for this cap. Resource calls retain their text-result semantics. Only script-selected output (`emit`, captured console, and `return`) reaches the final output guard; ordinary MCP calls remain guarded as before.
|
|
757
809
|
|
|
@@ -779,6 +831,8 @@ When Pi exposes dialog-capable UI, the adapter advertises form elicitation suppo
|
|
|
779
831
|
|
|
780
832
|
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.
|
|
781
833
|
|
|
834
|
+
Time spent answering a server's prompt does not count against its tool calls' `requestTimeoutMs` (or the SDK's 60-second default). The timeout pauses while a prompt from that server is open and resumes with the time that was left. Task-backed calls are unchanged: their timeout applies to each task request, not to the whole task.
|
|
835
|
+
|
|
782
836
|
### Direct Tools
|
|
783
837
|
|
|
784
838
|
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.
|
|
@@ -867,7 +921,7 @@ To hide specific tools while still using `directTools: true`, add `excludeTools`
|
|
|
867
921
|
{
|
|
868
922
|
"mcpServers": {
|
|
869
923
|
"figma": {
|
|
870
|
-
"url": "http://
|
|
924
|
+
"url": "http://127.0.0.1:3845/mcp",
|
|
871
925
|
"directTools": true,
|
|
872
926
|
"excludeTools": ["read_figjam", "figma_get_code_connect_map"]
|
|
873
927
|
}
|
|
@@ -895,7 +949,9 @@ When you change direct-tool toggles in `/mcp-adapter`, the extension updates dir
|
|
|
895
949
|
|
|
896
950
|
**Interactive configuration:** Run `/mcp-adapter` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. `/mcp` is also available as an alias when Pi's built-in MCP extension is not installed. You can reconnect servers, toggle tools between direct and proxy, and enable or disable servers (`ctrl+d`) from the same overlay. For OAuth, press Enter on a server that needs auth or `ctrl+a` on any OAuth server. The Save action defaults to `ctrl+s` and can be remapped with the `mcp.panel.save` keybinding.
|
|
897
951
|
|
|
898
|
-
**Guided first-run setup:** Run `/mcp-adapter setup` to choose the normal write target for new shared servers — project `.mcp.json` or global `~/.config/mcp/mcp.json` — 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 selected config, add a curated known server (DeepWiki, Context7, Notion, GitHub, or
|
|
952
|
+
**Guided first-run setup:** Run `/mcp-adapter setup` to choose the normal write target for new shared servers — project `.mcp.json` or global `~/.config/mcp/mcp.json` — 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 selected config, add a curated known server (DeepWiki, Context7, Notion, GitHub, Chrome DevTools, or Figma (desktop) when the Figma app is installed), or quick-add RepoPrompt into a standard/shared MCP file.
|
|
953
|
+
|
|
954
|
+
**Figma:** Figma's remote server, `https://mcp.figma.com/mcp`, only accepts approved clients, and Pi isn't approved yet. Use the Figma desktop app's local server instead: run `/mcp-adapter setup` and add Figma (desktop). It needs a Dev or Full seat on a paid Figma plan; to turn it on, open a Design file, switch to Dev Mode (Shift+D), and click "Enable desktop MCP server" in the inspect panel.
|
|
899
955
|
|
|
900
956
|
**Subagent integration:** If you use the subagent extension, agents can request direct MCP tools in their frontmatter with `mcp:server-name` syntax. See the subagent README for details.
|
|
901
957
|
|
|
@@ -998,10 +1054,12 @@ Prefer `.mcp.json` for project-local shared MCP config and `~/.config/mcp/mcp.js
|
|
|
998
1054
|
|
|
999
1055
|
MCP proxy and direct-tool results use compact self-rendered rows by default. Collapsed success output shows the call title, a bounded one-line input preview when arguments exist, and the first result line, with a `Ctrl+O to expand` hint when more text is hidden. The full result remains available when expanded and is still returned unchanged to the model. Set `settings.toolResultRendering` to `"boxed"` to restore the legacy boxed Pi row, or set `settings.collapsedResultLines` to `2` or `3` when you want more collapsed text.
|
|
1000
1056
|
|
|
1001
|
-
Search
|
|
1057
|
+
Search covers MCP tools only. Space-separated words are ranked by weighted matches across name, server, description, and any configured `searchKeywords`, then returned one page at a time (`limit` defaults to 12). Use `details.nextOffset` for the next page. Regex search is still available with `regex: true`, but regex results are paginated without ranking.
|
|
1002
1058
|
|
|
1003
1059
|
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.
|
|
1004
1060
|
|
|
1061
|
+
When a server declares MCP tool annotations, `describe` adds a `Hints:` line such as `Hints: read-only, idempotent`, `tools.describe` in `mcpScript` returns them as `annotations`, and the approval prompt says when a tool is marked destructive or read-only. They are the server's own hints, not guarantees, and never appear in search results or direct-tool descriptions.
|
|
1062
|
+
|
|
1005
1063
|
### Search keywords
|
|
1006
1064
|
|
|
1007
1065
|
Search uses literal matching so a tool whose name and description use different vocabulary than the query won't be found. Per-server `searchKeywords` adds extra vocabulary for matching tools:
|
|
@@ -1027,6 +1085,10 @@ Keys match a tool's original name, prefixed name, or a glob (`*` applies to ever
|
|
|
1027
1085
|
|
|
1028
1086
|
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.
|
|
1029
1087
|
|
|
1088
|
+
Most tools declare no output schema. After such a tool returns structured content or a JSON text result, `describe` (and `tools.describe` in `mcpScript`) also shows the output shape seen so far: field names and types only, never values, labeled as observed, not a contract. A wide object that appears more than once, such as a user under `user`, `assignee`, and `assignees`, is written once as a named type. With `scriptMode` on, shapes are also saved in `mcp-cache.json`, so later sessions can write a script without calling the tool first. A shape is used only while the tool's description and input schema stay the same, and a saved one is dropped when they or the server's config change.
|
|
1089
|
+
|
|
1090
|
+
When an `mcpScript` run throws, times out, or returns `[]`, `{}`, `null`, `""`, or nothing, its result ends with the shapes seen from the schemaless tools it called, so the model can fix a wrong field guess without a separate look at the data.
|
|
1091
|
+
|
|
1030
1092
|
For HTTP servers, Pi reports HTTP 503 as temporary unavailability and does not add another immediate retry loop. Keep-alive servers keep cached metadata available and retry after 30 seconds, backing off to 5 minutes. Other failed connects run a one-request shape probe that can turn opaque transport errors into setup hints such as `endpoint returned HTML (200) — this URL does not appear to speak MCP`. Healthy connections are not probed.
|
|
1031
1093
|
|
|
1032
1094
|
Servers that provide usage guidance via the MCP `instructions` field surface it through discovery paths: `mcp({ server: "name" })` includes a preview, and `mcp({ instructions: "name" })` returns the full text. Instructions are captured at connect time and cached alongside tool metadata, so they stay available without a live connection.
|
package/cli.js
CHANGED
|
@@ -406,6 +406,7 @@ async function runKey(argv, log, error, stdin) {
|
|
|
406
406
|
log("Jev API key removed from the OS secure credential store.");
|
|
407
407
|
}
|
|
408
408
|
if (Object.hasOwn(process.env, "SYSTEMONE_API_KEY")) log("Note: SYSTEMONE_API_KEY is present and overrides the stored key.");
|
|
409
|
+
else if (endpoint.origin === store.OPENROUTER_ORIGIN && Object.hasOwn(process.env, "OPENROUTER_API_KEY")) log("Note: OPENROUTER_API_KEY is present and overrides the stored key.");
|
|
409
410
|
else if (Object.hasOwn(process.env, "TYPESAFE_API_KEY")) log(endpoint.href === store.JEV_DEFAULT_ENDPOINT
|
|
410
411
|
? "Note: TYPESAFE_API_KEY is present and overrides the stored key."
|
|
411
412
|
: `Note: TYPESAFE_API_KEY is ignored for ${endpoint.href}; it is a TypeSafe credential and is never sent there.`);
|
package/commands.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
2
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import type { OverlayHandle } from "@earendil-works/pi-tui";
|
|
4
5
|
import type { McpExtensionState } from "./state.ts";
|
|
@@ -587,6 +588,21 @@ function buildSharedConfigNoticeLines(configOverridePath: string | undefined, cw
|
|
|
587
588
|
};
|
|
588
589
|
}
|
|
589
590
|
|
|
591
|
+
// Any accepted TCP connection counts as reachable; failures resolve false so the add still succeeds.
|
|
592
|
+
function isLocalServerReachable(url: string, timeoutMs = 1_500): Promise<boolean> {
|
|
593
|
+
const { hostname, port } = new URL(url);
|
|
594
|
+
return new Promise((resolve) => {
|
|
595
|
+
const socket = createConnection({ host: hostname, port: Number(port) });
|
|
596
|
+
const finish = (reachable: boolean) => {
|
|
597
|
+
socket.destroy();
|
|
598
|
+
resolve(reachable);
|
|
599
|
+
};
|
|
600
|
+
socket.setTimeout(timeoutMs, () => finish(false));
|
|
601
|
+
socket.once("connect", () => finish(true));
|
|
602
|
+
socket.once("error", () => finish(false));
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
590
606
|
export async function openMcpSetup(
|
|
591
607
|
state: McpExtensionState,
|
|
592
608
|
pi: ExtensionAPI,
|
|
@@ -641,7 +657,8 @@ export async function openMcpSetup(
|
|
|
641
657
|
addKnownServer: async (preset: KnownServerPreset, target: SharedConfigTarget) => {
|
|
642
658
|
const path = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry);
|
|
643
659
|
configChanged = true;
|
|
644
|
-
return { path, serverName: preset.name };
|
|
660
|
+
if (!preset.desktopApp) return { path, serverName: preset.name };
|
|
661
|
+
return { path, serverName: preset.name, reachable: await isLocalServerReachable(preset.entry.url!) };
|
|
645
662
|
},
|
|
646
663
|
openPath: async (targetPath: string) => {
|
|
647
664
|
await openPath(pi, targetPath);
|
|
@@ -659,7 +676,8 @@ export async function openMcpSetup(
|
|
|
659
676
|
resolve({ configChanged });
|
|
660
677
|
});
|
|
661
678
|
},
|
|
662
|
-
|
|
679
|
+
// The panel sizes its height from tui.terminal.rows minus this margin, so it always fits.
|
|
680
|
+
{ overlay: true, overlayOptions: { anchor: "center", width: 92, maxHeight: "100%", margin: { top: 1, bottom: 1 } } },
|
|
663
681
|
);
|
|
664
682
|
});
|
|
665
683
|
}
|
package/config.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface KnownServerPreset {
|
|
|
31
31
|
name: string;
|
|
32
32
|
summary: string;
|
|
33
33
|
entry: ServerEntry;
|
|
34
|
+
/** Offered only when one of these app paths exists; the local server is probed after adding. */
|
|
35
|
+
desktopApp?: { paths: readonly string[]; enableSteps: string };
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export const KNOWN_SERVER_PRESETS: readonly KnownServerPreset[] = [
|
|
@@ -74,6 +76,20 @@ export const KNOWN_SERVER_PRESETS: readonly KnownServerPreset[] = [
|
|
|
74
76
|
summary: "Inspect and automate a local Chrome browser.",
|
|
75
77
|
entry: { command: "npx", args: ["-y", "chrome-devtools-mcp@1.6.0"] },
|
|
76
78
|
},
|
|
79
|
+
{
|
|
80
|
+
id: "figma",
|
|
81
|
+
name: "Figma (desktop)",
|
|
82
|
+
summary: "Read designs through the Figma desktop app. Needs a Dev or Full seat on a paid Figma plan.",
|
|
83
|
+
entry: { url: "http://127.0.0.1:3845/mcp", protocolVersion: "auto" },
|
|
84
|
+
desktopApp: {
|
|
85
|
+
paths: [
|
|
86
|
+
"/Applications/Figma.app",
|
|
87
|
+
join(homedir(), "Applications", "Figma.app"),
|
|
88
|
+
join(homedir(), "AppData", "Local", "Figma", "Figma.exe"),
|
|
89
|
+
],
|
|
90
|
+
enableSteps: "To enable it, open a Design file in Figma, switch to Dev Mode (Shift+D), and click 'Enable desktop MCP server' in the inspect panel.",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
77
93
|
];
|
|
78
94
|
|
|
79
95
|
const IMPORT_PATHS: Record<ImportKind, string[]> = {
|
|
@@ -162,6 +178,7 @@ export interface McpDiscoverySummary {
|
|
|
162
178
|
totalServerCount: number;
|
|
163
179
|
fingerprint: string;
|
|
164
180
|
repoPrompt: RepoPromptDiscovery;
|
|
181
|
+
knownServerPresets: readonly KnownServerPreset[];
|
|
165
182
|
}
|
|
166
183
|
|
|
167
184
|
export interface McpStandardConfigSummary {
|
|
@@ -360,6 +377,7 @@ export function getMcpDiscoverySummary(
|
|
|
360
377
|
...summaryWithoutRepoPrompt,
|
|
361
378
|
fingerprint,
|
|
362
379
|
repoPrompt: detectRepoPrompt(summaryWithoutRepoPrompt, cwd),
|
|
380
|
+
knownServerPresets: KNOWN_SERVER_PRESETS.filter(({ desktopApp }) => !desktopApp || desktopApp.paths.some((path) => existsSync(path))),
|
|
363
381
|
};
|
|
364
382
|
}
|
|
365
383
|
|
|
@@ -1531,7 +1549,7 @@ function buildRepoPromptEntry(executablePath: string): ServerEntry {
|
|
|
1531
1549
|
};
|
|
1532
1550
|
}
|
|
1533
1551
|
|
|
1534
|
-
function detectRepoPrompt(summary: Omit<McpDiscoverySummary, "fingerprint" | "repoPrompt">, cwd = process.cwd()): RepoPromptDiscovery {
|
|
1552
|
+
function detectRepoPrompt(summary: Omit<McpDiscoverySummary, "fingerprint" | "repoPrompt" | "knownServerPresets">, cwd = process.cwd()): RepoPromptDiscovery {
|
|
1535
1553
|
for (const source of summary.sources) {
|
|
1536
1554
|
if (source.kind !== "shared" || source.serverCount === 0) continue;
|
|
1537
1555
|
const config = readValidatedConfig(source.path, `MCP config from ${source.path}`);
|
package/direct-tool-surface.ts
CHANGED
|
@@ -199,8 +199,9 @@ export function resolveDirectTools(
|
|
|
199
199
|
* Live counts/status belong to `mcp({ })`, full instructions to
|
|
200
200
|
* `mcp({ instructions })`.
|
|
201
201
|
*/
|
|
202
|
-
export function buildProxyDescription(config: McpConfig): string {
|
|
203
|
-
|
|
202
|
+
export function buildProxyDescription(config: McpConfig, scriptTool = false): string {
|
|
203
|
+
const scriptHint = scriptTool ? " Use mcpScript for several MCP calls with logic between them." : "";
|
|
204
|
+
let desc = `MCP gateway — URL installation, server status, tool search/describe, auth, and single MCP tool calls. When a user supplies an MCP endpoint URL, install it with the install action.${scriptHint} Non-MCP Pi tools should be called directly, not through mcp.\n`;
|
|
204
205
|
|
|
205
206
|
const serverNames = Object.keys(config.mcpServers)
|
|
206
207
|
.filter((serverName) => !isServerDisabled(config.mcpServers[serverName]));
|
package/direct-tools.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { describeFailure } from "./failure-backoff.ts";
|
|
|
8
8
|
import { abortable, throwIfAborted } from "./abort.ts";
|
|
9
9
|
import { formatSchema } from "./tool-metadata.ts";
|
|
10
10
|
import { resolveMcpResultContent, transformMcpResourceContents } from "./tool-registrar.ts";
|
|
11
|
-
import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions } from "./mcp-output-guard.ts";
|
|
11
|
+
import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions, scriptPipeHint } from "./mcp-output-guard.ts";
|
|
12
12
|
import { maybeStartUiSession, summarizeUiSessionResult, type UiSessionRuntime } from "./ui-session.ts";
|
|
13
13
|
import { isServerDisabled } from "./types.ts";
|
|
14
14
|
import { authenticate, supportsOAuth } from "./mcp-auth-flow.ts";
|
|
@@ -16,6 +16,8 @@ import { formatAuthRequiredMessage, normalizeToolArguments, resolveServerUrl, wi
|
|
|
16
16
|
import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.ts";
|
|
17
17
|
import { combineAbortSignals, isAbortError } from "./runtime-owner.ts";
|
|
18
18
|
import { callToolViaTaskSession } from "./mcp-tasks.ts";
|
|
19
|
+
import { callToolPausingForElicitation } from "./elicitation-handler.ts";
|
|
20
|
+
import { observedOutputRecorder } from "./output-shape.ts";
|
|
19
21
|
import { ensureToolCallApproved } from "./tool-approval.ts";
|
|
20
22
|
import { getInputRequiredNeedsUiDetails } from "./errors.ts";
|
|
21
23
|
|
|
@@ -207,6 +209,10 @@ export function createDirectToolExecutor(
|
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
const normalizedParams = spec.resourceUri ? params : normalizeToolArguments(params);
|
|
212
|
+
// The spec is fixed at registration; approval must see the hints the server advertises now.
|
|
213
|
+
const annotations = spec.resourceUri
|
|
214
|
+
? undefined
|
|
215
|
+
: state.toolMetadata.get(spec.serverName)?.find(tool => !tool.resourceUri && tool.originalName === spec.originalName)?.annotations;
|
|
210
216
|
const approval = await ensureToolCallApproved(state, spec.serverName, {
|
|
211
217
|
name: spec.prefixedName,
|
|
212
218
|
originalName: spec.originalName,
|
|
@@ -215,6 +221,7 @@ export function createDirectToolExecutor(
|
|
|
215
221
|
...(spec.resourceUri !== undefined ? { resourceUri: spec.resourceUri } : {}),
|
|
216
222
|
...(spec.uiResourceUri !== undefined ? { uiResourceUri: spec.uiResourceUri } : {}),
|
|
217
223
|
...(spec.uiStreamMode !== undefined ? { uiStreamMode: spec.uiStreamMode } : {}),
|
|
224
|
+
...(annotations !== undefined ? { annotations } : {}),
|
|
218
225
|
}, normalizedParams, ownedSignal, spec.resourceUri ? "resource" : "direct");
|
|
219
226
|
if (approval.ok === false) {
|
|
220
227
|
const denied = approval.reason === "denied";
|
|
@@ -305,6 +312,7 @@ export function createDirectToolExecutor(
|
|
|
305
312
|
: null;
|
|
306
313
|
|
|
307
314
|
const requestMeta = withToolCallIdMeta(uiSession?.requestMeta, toolCallId);
|
|
315
|
+
const recordOutput = observedOutputRecorder(state, spec.serverName, spec.originalName);
|
|
308
316
|
const result = await withSessionRecovery<ClientCallToolResult>(
|
|
309
317
|
{
|
|
310
318
|
manager: state.manager,
|
|
@@ -324,7 +332,7 @@ export function createDirectToolExecutor(
|
|
|
324
332
|
requestTimeoutMs: requestOptions?.timeout,
|
|
325
333
|
}) as unknown as ClientCallToolResult;
|
|
326
334
|
}
|
|
327
|
-
return abortable(conn.client
|
|
335
|
+
return abortable(callToolPausingForElicitation(conn.client, {
|
|
328
336
|
name: spec.originalName,
|
|
329
337
|
arguments: normalizedParams,
|
|
330
338
|
_meta: requestMeta,
|
|
@@ -332,6 +340,7 @@ export function createDirectToolExecutor(
|
|
|
332
340
|
},
|
|
333
341
|
);
|
|
334
342
|
uiSession?.sendToolResult(result as unknown as import("@modelcontextprotocol/client").CallToolResult);
|
|
343
|
+
if (!result.isError) recordOutput(result as Record<string, unknown>);
|
|
335
344
|
|
|
336
345
|
if (result.isError) {
|
|
337
346
|
const content = resolveMcpResultContent(result as Record<string, unknown>, state.owner?.signal);
|
|
@@ -372,6 +381,7 @@ export function createDirectToolExecutor(
|
|
|
372
381
|
|
|
373
382
|
const guarded = await guardMcpOutput(outputContent, {
|
|
374
383
|
...outputGuardOptions,
|
|
384
|
+
...scriptPipeHint(state.scriptTool, outputContent),
|
|
375
385
|
...(state.config.settings?.directToolResultDetails === "bounded" ? { rawMcpResult: result } : {}),
|
|
376
386
|
});
|
|
377
387
|
return {
|
package/dist/config.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export interface KnownServerPreset {
|
|
|
5
5
|
name: string;
|
|
6
6
|
summary: string;
|
|
7
7
|
entry: ServerEntry;
|
|
8
|
+
/** Offered only when one of these app paths exists; the local server is probed after adding. */
|
|
9
|
+
desktopApp?: {
|
|
10
|
+
paths: readonly string[];
|
|
11
|
+
enableSteps: string;
|
|
12
|
+
};
|
|
8
13
|
}
|
|
9
14
|
export declare const KNOWN_SERVER_PRESETS: readonly KnownServerPreset[];
|
|
10
15
|
interface ConfigSourceSpec {
|
|
@@ -71,6 +76,7 @@ export interface McpDiscoverySummary {
|
|
|
71
76
|
totalServerCount: number;
|
|
72
77
|
fingerprint: string;
|
|
73
78
|
repoPrompt: RepoPromptDiscovery;
|
|
79
|
+
knownServerPresets: readonly KnownServerPreset[];
|
|
74
80
|
}
|
|
75
81
|
export interface McpStandardConfigSummary {
|
|
76
82
|
sources: ConfigDiscoverySource[];
|
package/dist/config.js
CHANGED
|
@@ -65,6 +65,20 @@ export const KNOWN_SERVER_PRESETS = [
|
|
|
65
65
|
summary: "Inspect and automate a local Chrome browser.",
|
|
66
66
|
entry: { command: "npx", args: ["-y", "chrome-devtools-mcp@1.6.0"] },
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
id: "figma",
|
|
70
|
+
name: "Figma (desktop)",
|
|
71
|
+
summary: "Read designs through the Figma desktop app. Needs a Dev or Full seat on a paid Figma plan.",
|
|
72
|
+
entry: { url: "http://127.0.0.1:3845/mcp", protocolVersion: "auto" },
|
|
73
|
+
desktopApp: {
|
|
74
|
+
paths: [
|
|
75
|
+
"/Applications/Figma.app",
|
|
76
|
+
join(homedir(), "Applications", "Figma.app"),
|
|
77
|
+
join(homedir(), "AppData", "Local", "Figma", "Figma.exe"),
|
|
78
|
+
],
|
|
79
|
+
enableSteps: "To enable it, open a Design file in Figma, switch to Dev Mode (Shift+D), and click 'Enable desktop MCP server' in the inspect panel.",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
68
82
|
];
|
|
69
83
|
const IMPORT_PATHS = {
|
|
70
84
|
cursor: [join(homedir(), ".cursor", "mcp.json")],
|
|
@@ -247,6 +261,7 @@ export function getMcpDiscoverySummary(overridePath, cwd = process.cwd(), option
|
|
|
247
261
|
...summaryWithoutRepoPrompt,
|
|
248
262
|
fingerprint,
|
|
249
263
|
repoPrompt: detectRepoPrompt(summaryWithoutRepoPrompt, cwd),
|
|
264
|
+
knownServerPresets: KNOWN_SERVER_PRESETS.filter(({ desktopApp }) => !desktopApp || desktopApp.paths.some((path) => existsSync(path))),
|
|
250
265
|
};
|
|
251
266
|
}
|
|
252
267
|
export function cloneMcpConfig(config) {
|