pi-mcp-adapter 2.16.0 → 2.18.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 +29 -0
- package/README.md +58 -8
- package/commands.ts +8 -0
- package/config.ts +40 -0
- package/direct-tools.ts +25 -0
- package/host-html-template.ts +54 -16
- package/index.ts +64 -2
- package/init.ts +27 -6
- package/mcp-auth-flow.ts +18 -2
- package/mcp-auth.ts +205 -12
- package/mcp-code.ts +295 -0
- package/mcp-keyring-helper.cjs +89 -0
- package/mcp-oauth-provider.ts +25 -1
- package/mcp-probe.ts +90 -0
- package/mcp-script-worker.mjs +115 -0
- package/mcp-setup-panel.ts +104 -34
- package/metadata-cache.ts +6 -0
- package/package.json +13 -1
- package/proxy-modes.ts +104 -62
- package/search-ranking.ts +126 -0
- package/server-manager.ts +15 -1
- package/skills/mcp-scripting/SKILL.md +36 -0
- package/state.ts +2 -0
- package/tool-approval.ts +78 -0
- package/tool-metadata.ts +7 -0
- package/tool-result-renderer.ts +62 -10
- package/ts-shape.ts +142 -0
- package/types.ts +47 -4
- package/ui-server.ts +178 -19
- package/ui-session.ts +101 -10
- package/ui-tool-visibility.ts +25 -0
- package/utils.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.18.0] - 2026-08-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added `settings.freezeDirectTools` to keep direct MCP tool registration stable after initial sync while preserving explicit reconnect refreshes. Thanks @ddfourtwo for PR #254.
|
|
14
|
+
- Added best-effort Linux OAuth credential recovery when Pi inherits a revoked session keyring, allowing explicit re-authentication through a fresh `keyctl` session helper. Thanks @anthod0 for issue #248 and the validation prototype.
|
|
15
|
+
- Ranked, paginated MCP tool search: best matches come first in a short page of 12 instead of an unranked dump of every match with full schemas, so the model stops guessing and each search costs a fraction of the tokens. Misses on describe/call now return top-5 "Did you mean" suggestions, letting the model self-correct a typo or missing prefix in the same turn instead of burning a round trip.
|
|
16
|
+
- Optional `approveTools` patterns (global and per-server) add the missing middle tier between "tool runs instantly" and "tool hidden entirely": flag risky tools and Pi asks before running them — Allow once / Allow for session / Deny — across proxy, direct, resource, and iframe-originated calls. Safe tools keep full speed; a deny is a normal result the model adapts to, not a crash.
|
|
17
|
+
- Opt-in `mcp_script` trusted JavaScript MCP scripting turns N-step jobs into one call: loop, filter, and chain tools for tool-restricted subagents where every round trip costs child context. Scripts discover tools with `await tools.search({ query })`, inspect exact shapes with `await tools.describe({ path })`, and call them with `tools.call(path, args)` — no more guessing names from outside the script. A runaway script can never freeze Pi itself: scripts run isolated from the main process and are force-stopped at their time limit, even if stuck in an infinite loop. Result details include a `calls` trace of every invoked path and outcome, and a bundled `mcp-scripting` skill teaches the full workflow on demand. Every scripted call still goes through auth, output guarding, and the approval gate.
|
|
18
|
+
- HTTP connection failures are now probe-classified into a plain-language diagnosis (for example "endpoint returned HTML (200) — this URL does not appear to speak MCP") instead of an opaque "fetch failed", so setup mistakes are fixed in seconds. Healthy connections are never probed.
|
|
19
|
+
- Tool parameters render as compact TypeScript shapes (`{ query: string; limit?: number }`) in describe and search, replacing multi-line schema dumps — the model reads less and acts sooner, with the previous formatting kept as a fallback for exotic schemas.
|
|
20
|
+
- `/mcp setup` gained curated one-click presets (DeepWiki, Context7, Notion, GitHub, Chrome DevTools): pick, preview the exact config write, confirm — new servers in under a minute with no hand-typed setup.
|
|
21
|
+
|
|
22
|
+
The ranked search scoring, did-you-mean suggestions, approval patterns, endpoint shape probe, TypeScript-shaped schemas, and codemode design in this release are adapted from [Executor](https://github.com/UsefulSoftwareCo/executor) by Rhys Sullivan (@RhysSullivan). Thanks Rhys.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Brought MCP Apps UI hosting in line with the current spec: provider HTML now runs in a real sandbox, gets a restrictive default CSP even when the resource omits one, and `_meta.ui.visibility` is honored so app-only tools stay out of the model tool list while model-only tools cannot be called from the UI.
|
|
26
|
+
- MCP Apps UI sessions are now easier to open from Moshi and remote terminals: the local UI server uses Moshi-discoverable low ports, answers preview discovery probes, serves a loopback-only landing shell, prints Moshi/SSH access hints for remote sessions, and fits the host shell better in narrow in-app browsers. UI-submitted model context is now captured as a bounded handoff, wakes the agent like prompts and intents, and remains available through `mcp({ action: "ui-messages" })` after the UI closes.
|
|
27
|
+
|
|
28
|
+
## [2.17.0] - 2026-07-31
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- Added `settings.mcpFooterStatus` to compact or hide the persistent MCP footer status. Thanks @jwintz for issue #5.
|
|
32
|
+
- Added per-server OAuth `authorizationParams` for provider-specific authorization URL parameters such as Google's `access_type=offline`, while rejecting OAuth flow-owned parameter overrides. Thanks @hank-warren for issue #238.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Added a best-effort absolute-path fallback for loading the `@napi-rs/keyring` native binding when compiled Pi/Bun cannot resolve the package loader. Thanks @sgiath for issue #230.
|
|
36
|
+
- Bound collapsed MCP tool result rendering by character count as well as line count, preventing huge single-line results from slowing long TUI sessions. Thanks @Whisperfall for issue #249.
|
|
37
|
+
- Let configured `oauth.scope` override OAuth discovery scopes during authorization flows. Thanks @viggy28 for issue #225 and @adity982 for PR #226.
|
|
38
|
+
|
|
10
39
|
## [2.16.0] - 2026-07-30
|
|
11
40
|
|
|
12
41
|
### Added
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ The adapter reads standard MCP files automatically. No extra setup needed if you
|
|
|
32
32
|
|---------------------|--------------|
|
|
33
33
|
| `.mcp.json` or `~/.config/mcp/mcp.json` | Pi uses it immediately. The first time you open `/mcp`, you'll see a short heads-up explaining which file Pi detected and that Pi only writes adapter-specific overrides to its own files. |
|
|
34
34
|
| Host-specific configs (Cursor, Claude Code, Codex, etc.) but no standard MCP files | Run `/mcp setup` to adopt those host configs into Pi. The setup flow shows exactly what it found, lets you pick which ones to import, and previews the exact file changes before writing. |
|
|
35
|
-
| Nothing configured yet | Run `/mcp setup` to scaffold a minimal `.mcp.json`, quick-add RepoPrompt, or inspect what the adapter discovered on your machine. |
|
|
35
|
+
| Nothing configured yet | Run `/mcp setup` to scaffold a minimal `.mcp.json`, add a curated known server, quick-add RepoPrompt, or inspect what the adapter discovered on your machine. |
|
|
36
36
|
|
|
37
37
|
If you prefer the terminal, you can also run `pi-mcp-adapter init` after install to scan for host-specific configs and add missing compatibility imports to the Pi agent dir (`~/.pi/agent/mcp.json` by default, or `$PI_CODING_AGENT_DIR/mcp.json` when set).
|
|
38
38
|
|
|
@@ -222,7 +222,9 @@ The adapter owns only its client socket and closes that connection when the Pi r
|
|
|
222
222
|
|
|
223
223
|
### Remote/headless OAuth
|
|
224
224
|
|
|
225
|
-
If Pi is running on a remote server and cannot open a local browser, start OAuth through the proxy tool. Persistent OAuth still requires an available OS credential store; on headless Linux that usually means an unlocked Secret Service/libsecret keyring. The adapter fails closed instead of falling back to plaintext credentials when the secure store is unavailable
|
|
225
|
+
If Pi is running on a remote server and cannot open a local browser, start OAuth through the proxy tool. Persistent OAuth still requires an available OS credential store; on headless Linux that usually means an unlocked Secret Service/libsecret keyring. The adapter fails closed instead of falling back to plaintext credentials when the secure store is unavailable.
|
|
226
|
+
|
|
227
|
+
On Linux, if credential access fails because Pi inherited a revoked session keyring, the adapter uses a best-effort recovery path through `keyctl session - node <packaged helper>` so explicit re-authentication can write fresh credentials without killing a long-lived tmux server. This path requires `keyctl` and `node` on `PATH`; missing, locked, or otherwise unavailable credential stores still fail closed.
|
|
226
228
|
|
|
227
229
|
```js
|
|
228
230
|
mcp({ action: "auth-start", server: "linear-server" })
|
|
@@ -258,7 +260,10 @@ When any enabled server uses `eager` or `keep-alive`, initialization also starts
|
|
|
258
260
|
"idleTimeout": 10,
|
|
259
261
|
"requestTimeoutMs": 30000,
|
|
260
262
|
"showStatusIcon": true,
|
|
263
|
+
"mcpFooterStatus": "full",
|
|
261
264
|
"hostConfigDiscovery": "off",
|
|
265
|
+
"approveTools": ["github_delete_*", "notion_update_*"],
|
|
266
|
+
"scriptMode": true,
|
|
262
267
|
"oauthDir": ".pi/mcp-oauth",
|
|
263
268
|
"trace": {
|
|
264
269
|
"enabled": true,
|
|
@@ -277,9 +282,14 @@ When any enabled server uses `eager` or `keep-alive`, initialization also starts
|
|
|
277
282
|
| `idleTimeout` | Global idle timeout in minutes (default: 10, 0 to disable) |
|
|
278
283
|
| `requestTimeoutMs` | Global request timeout in milliseconds for live MCP calls (if omitted or `<= 0`, the MCP SDK default timeout is used) |
|
|
279
284
|
| `showStatusIcon` | Show the plug icon in MCP status and connection text (default: `true`). Set to `false` for plain `MCP: ...` text. |
|
|
285
|
+
| `mcpFooterStatus` | MCP footer verbosity: `"full"` (default), `"compact"` for `MCP connected/enabled`, or `"off"` to clear the persistent footer status. `/mcp status` remains available. |
|
|
280
286
|
| `hostConfigDiscovery` | Host-specific config policy: `"off"` (default), `"prompt"` (detect/report only), or `"on"` (explicitly load detected host configs as the lowest-precedence fallback) |
|
|
287
|
+
| `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. |
|
|
281
288
|
| `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. |
|
|
289
|
+
| `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. |
|
|
282
290
|
| `directTools` | Global default for all servers (default: false). Per-server overrides this. |
|
|
291
|
+
| `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. |
|
|
292
|
+
| `scriptMode` | Register the MCP-only `mcp_script` plain-JavaScript tool (default: false). |
|
|
283
293
|
| `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
|
|
284
294
|
| `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
|
|
285
295
|
| `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
|
|
@@ -288,7 +298,25 @@ When any enabled server uses `eager` or `keep-alive`, initialization also starts
|
|
|
288
298
|
| `outputGuard` | Guard oversized MCP output: `true` (default), `false`, or `{ maxBytes, maxLines, detailsMaxBytes }`. See [Output Guard](#output-guard). |
|
|
289
299
|
| `trace` | Opt-in metadata-only protocol tracing. Set `{ enabled: true }` globally or `trace: true` on a server. The per-session JSONL file defaults to `.pi/mcp-traces/`; `file`, `maxBytes` (default 262144), and `maxEvents` (default 10000) can be set. Raw MCP payloads, prompts, tool arguments/results, auth data, and URLs are never persisted. |
|
|
290
300
|
|
|
291
|
-
Per-server `idleTimeout` and `
|
|
301
|
+
Per-server `idleTimeout`, `requestTimeoutMs`, and `approveTools` override the global settings. `debug` remains stderr display and is unrelated to protocol tracing.
|
|
302
|
+
|
|
303
|
+
### Tool Approval
|
|
304
|
+
|
|
305
|
+
Use `approveTools` when a tool should stay visible but not run without confirmation. This is useful for destructive or high-cost actions where hiding the tool would make planning harder, but running it silently is too risky.
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"settings": {
|
|
310
|
+
"approveTools": ["github_delete_*", "notion_update_*"]
|
|
311
|
+
},
|
|
312
|
+
"mcpServers": {
|
|
313
|
+
"github": { "approveTools": ["delete_*", "merge_pull_request"] },
|
|
314
|
+
"docs": { "approveTools": false }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
When a matching tool is called from the proxy tool, a direct MCP tool, a resource call, or an MCP UI iframe, Pi asks: **Allow once**, **Allow for session**, or **Deny**. Session approvals are kept in memory only. In headless sessions, matching calls fail closed with an `approval_required` result instead of running. `excludeTools` still removes tools entirely; `approveTools` only gates visible tools at call time.
|
|
292
320
|
|
|
293
321
|
### Output Guard
|
|
294
322
|
|
|
@@ -310,6 +338,22 @@ Tune the limits with the object form:
|
|
|
310
338
|
|
|
311
339
|
Set `"outputGuard": false` — or the env kill switch `MCP_OUTPUT_GUARD=0` — to disable the guard and restore raw output behavior. Saved temp files are created with mode `0600` under the system temp directory and are not cleaned up automatically; note that spilled MCP output may contain sensitive data.
|
|
312
340
|
|
|
341
|
+
### MCP Scripting
|
|
342
|
+
|
|
343
|
+
Set `settings.scriptMode` to `true` to register `mcp_script({ code, timeoutMs? })`, a trusted agent-authored JavaScript layer for orchestrating MCP tools. See the bundled `mcp-scripting` skill for the complete workflow guide. The canonical API is ordinary JavaScript with `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 invoked path and outcome. 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.
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
const first = await tools.github_search_issues({ query: "is:open label:bug" });
|
|
347
|
+
if (!first.ok) return first;
|
|
348
|
+
const selected = first.data.content.filter((item) => item.type === "text");
|
|
349
|
+
emit({ searched: true });
|
|
350
|
+
return selected;
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
For a tool-restricted subagent, enable script mode in the adapter configuration, then 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.
|
|
354
|
+
|
|
355
|
+
`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.
|
|
356
|
+
|
|
313
357
|
### MCP Prompts
|
|
314
358
|
|
|
315
359
|
MCP servers can advertise prompt templates alongside tools and resources. The adapter registers cached prompt definitions as Pi slash commands under `/mcp__<server>__<prompt>`, and refreshes their metadata whenever a server connects. Arguments support positional and `key=value` forms with quoting; required arguments are validated before `prompts/get` is called.
|
|
@@ -412,11 +456,13 @@ Each direct tool costs ~150-300 tokens in the system prompt (name + description
|
|
|
412
456
|
|
|
413
457
|
Direct tools register from the metadata cache in the Pi agent dir (`~/.pi/agent/mcp-cache.json` by default, or `$PI_CODING_AGENT_DIR/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. On Pi versions that expose `pi.unregisterTool()`, stale direct tools are removed from the registry during refresh; older Pi versions still deactivate them from the active tool set. To force a refresh: `/mcp reconnect <server>`.
|
|
414
458
|
|
|
459
|
+
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.
|
|
460
|
+
|
|
415
461
|
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.
|
|
416
462
|
|
|
417
463
|
**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.
|
|
418
464
|
|
|
419
|
-
**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`, or quick-add RepoPrompt into a standard/shared MCP file.
|
|
465
|
+
**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.
|
|
420
466
|
|
|
421
467
|
**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.
|
|
422
468
|
|
|
@@ -505,7 +551,7 @@ Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only
|
|
|
505
551
|
|------|---------|
|
|
506
552
|
| Status | `mcp({ })` |
|
|
507
553
|
| List server | `mcp({ server: "name" })` |
|
|
508
|
-
| Search | `mcp({ search: "screenshot navigate" })` |
|
|
554
|
+
| Search | `mcp({ search: "screenshot navigate", limit: 12, offset: 0 })` |
|
|
509
555
|
| Describe | `mcp({ describe: "tool_name" })` |
|
|
510
556
|
| Instructions | `mcp({ instructions: "name" })` |
|
|
511
557
|
| Call | `mcp({ tool: "...", args: { key: "value" } })` |
|
|
@@ -518,9 +564,13 @@ Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only
|
|
|
518
564
|
|
|
519
565
|
MCP proxy and direct-tool results render compactly by default: long text shows the first three terminal-wrapped lines plus a `Ctrl+O to expand` hint, while the full result remains available when expanded and is still returned unchanged to the model.
|
|
520
566
|
|
|
521
|
-
Search includes both MCP tools and Pi tools (from extensions). Pi tools appear first with `[pi tool]` prefix. Space-separated words are
|
|
567
|
+
Search includes both MCP tools and Pi tools (from extensions). Pi tools appear first with `[pi tool]` prefix. Space-separated words are ranked by weighted matches across name, server, and description, 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.
|
|
568
|
+
|
|
569
|
+
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.
|
|
570
|
+
|
|
571
|
+
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.
|
|
522
572
|
|
|
523
|
-
|
|
573
|
+
For HTTP servers, 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.
|
|
524
574
|
|
|
525
575
|
Servers that provide usage guidance via the MCP `instructions` field surface it at three levels: a truncated head in the `mcp` proxy tool description itself (so the model sees it without any call), a longer preview at the end of `mcp({ server: "name" })` listings, and the full text via `mcp({ instructions: "name" })`. Instructions are captured at connect time and cached alongside tool metadata, so they stay available without a live connection.
|
|
526
576
|
|
|
@@ -529,7 +579,7 @@ Servers that provide usage guidance via the MCP `instructions` field surface it
|
|
|
529
579
|
| Command | What it does |
|
|
530
580
|
|---------|--------------|
|
|
531
581
|
| `/mcp` | Interactive panel and first-run onboarding surface |
|
|
532
|
-
| `/mcp setup` | Guided setup for imports, a minimal `.mcp.json`, RepoPrompt quick-add, and config-path inspection |
|
|
582
|
+
| `/mcp setup` | Guided setup for imports, a minimal `.mcp.json`, curated known servers, RepoPrompt quick-add, and config-path inspection |
|
|
533
583
|
| `/mcp tools` | List all tools |
|
|
534
584
|
| `/mcp prompts` | List all MCP prompts registered as slash commands |
|
|
535
585
|
| `/mcp reconnect` | Reconnect all servers |
|
package/commands.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { isServerDisabled, type McpAuthResult, type McpConfig, type McpPanelCall
|
|
|
4
4
|
import {
|
|
5
5
|
ensureCompatibilityImports,
|
|
6
6
|
getMcpDiscoverySummary,
|
|
7
|
+
getProjectConfigPath,
|
|
8
|
+
type KnownServerPreset,
|
|
7
9
|
getServerProvenance,
|
|
8
10
|
previewCompatibilityImports,
|
|
9
11
|
previewSharedServerEntry,
|
|
@@ -395,6 +397,7 @@ export async function openMcpSetup(
|
|
|
395
397
|
if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) return null;
|
|
396
398
|
return previewSharedServerEntry(repoPrompt.targetPath, repoPrompt.serverName, repoPrompt.entry);
|
|
397
399
|
},
|
|
400
|
+
previewKnownServer: (preset: KnownServerPreset) => previewSharedServerEntry(getProjectConfigPath(ctx.cwd), preset.id, preset.entry),
|
|
398
401
|
adoptImports: async (imports: ImportKind[]) => {
|
|
399
402
|
const result = ensureCompatibilityImports(imports, configOverridePath);
|
|
400
403
|
if (result.added.length > 0) configChanged = true;
|
|
@@ -414,6 +417,11 @@ export async function openMcpSetup(
|
|
|
414
417
|
configChanged = true;
|
|
415
418
|
return { path, serverName: repoPrompt.serverName };
|
|
416
419
|
},
|
|
420
|
+
addKnownServer: async (preset: KnownServerPreset) => {
|
|
421
|
+
const path = writeSharedServerEntry(getProjectConfigPath(ctx.cwd), preset.id, preset.entry);
|
|
422
|
+
configChanged = true;
|
|
423
|
+
return { path, serverName: preset.name };
|
|
424
|
+
},
|
|
417
425
|
openPath: async (targetPath: string) => {
|
|
418
426
|
await openPath(pi, targetPath);
|
|
419
427
|
},
|
package/config.ts
CHANGED
|
@@ -20,6 +20,46 @@ const REPOPROMPT_BINARY_CANDIDATES = [
|
|
|
20
20
|
"/Applications/Repo Prompt.app/Contents/MacOS/repoprompt-mcp",
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
+
export interface KnownServerPreset {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
summary: string;
|
|
27
|
+
entry: ServerEntry;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const KNOWN_SERVER_PRESETS: readonly KnownServerPreset[] = [
|
|
31
|
+
{
|
|
32
|
+
id: "deepwiki",
|
|
33
|
+
name: "DeepWiki",
|
|
34
|
+
summary: "Ask questions about public GitHub repositories.",
|
|
35
|
+
entry: { url: "https://mcp.deepwiki.com/mcp" },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "context7",
|
|
39
|
+
name: "Context7",
|
|
40
|
+
summary: "Look up current library documentation and examples.",
|
|
41
|
+
entry: { url: "https://mcp.context7.com/mcp" },
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "notion",
|
|
45
|
+
name: "Notion",
|
|
46
|
+
summary: "Search and work with your Notion workspace.",
|
|
47
|
+
entry: { url: "https://mcp.notion.com/mcp", auth: "oauth" },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "github",
|
|
51
|
+
name: "GitHub",
|
|
52
|
+
summary: "Work with GitHub through your Copilot account.",
|
|
53
|
+
entry: { url: "https://api.githubcopilot.com/mcp", auth: "oauth" },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "chrome-devtools",
|
|
57
|
+
name: "Chrome DevTools",
|
|
58
|
+
summary: "Inspect and automate a local Chrome browser.",
|
|
59
|
+
entry: { command: "npx", args: ["-y", "chrome-devtools-mcp@latest"] },
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
23
63
|
const IMPORT_PATHS: Record<ImportKind, string[]> = {
|
|
24
64
|
cursor: [join(homedir(), ".cursor", "mcp.json")],
|
|
25
65
|
"claude-code": [
|
package/direct-tools.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { authenticate, supportsOAuth } from "./mcp-auth-flow.ts";
|
|
|
17
17
|
import { formatAuthRequiredMessage, resolveServerUrl, truncateAtWord } from "./utils.ts";
|
|
18
18
|
import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.ts";
|
|
19
19
|
import { combineAbortSignals, isAbortError } from "./runtime-owner.ts";
|
|
20
|
+
import { ensureToolCallApproved } from "./tool-approval.ts";
|
|
20
21
|
|
|
21
22
|
const BUILTIN_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "mcp"]);
|
|
22
23
|
const INSTRUCTIONS_SNIPPET_LENGTH = 150;
|
|
@@ -372,6 +373,30 @@ export function createDirectToolExecutor(
|
|
|
372
373
|
};
|
|
373
374
|
}
|
|
374
375
|
|
|
376
|
+
const approval = await ensureToolCallApproved(state, spec.serverName, {
|
|
377
|
+
name: spec.prefixedName,
|
|
378
|
+
originalName: spec.originalName,
|
|
379
|
+
description: spec.description,
|
|
380
|
+
inputSchema: spec.inputSchema,
|
|
381
|
+
resourceUri: spec.resourceUri,
|
|
382
|
+
uiResourceUri: spec.uiResourceUri,
|
|
383
|
+
uiStreamMode: spec.uiStreamMode,
|
|
384
|
+
}, params, ownedSignal);
|
|
385
|
+
if (approval.ok === false) {
|
|
386
|
+
const denied = approval.reason === "denied";
|
|
387
|
+
const message = denied
|
|
388
|
+
? `The user declined approval to run MCP tool "${spec.originalName}" on server "${spec.serverName}".`
|
|
389
|
+
: `MCP tool "${spec.originalName}" on server "${spec.serverName}" is approval-gated and requires an interactive session.`;
|
|
390
|
+
return {
|
|
391
|
+
content: [{ type: "text" as const, text: message }],
|
|
392
|
+
details: {
|
|
393
|
+
error: denied ? "approval_denied" : "approval_required",
|
|
394
|
+
server: spec.serverName,
|
|
395
|
+
tool: spec.originalName,
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
375
400
|
let uiSession: UiSessionRuntime | null = null;
|
|
376
401
|
const requestOptions = state.manager.getRequestOptions?.(spec.serverName, ownedSignal) ?? (ownedSignal ? { signal: ownedSignal } : undefined);
|
|
377
402
|
|
package/host-html-template.ts
CHANGED
|
@@ -63,8 +63,8 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
63
63
|
}
|
|
64
64
|
* { box-sizing: border-box; }
|
|
65
65
|
html, body { margin: 0; padding: 0; height: 100%; font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--text); }
|
|
66
|
-
body { display: flex; flex-direction: column; min-height: 100vh; }
|
|
67
|
-
header { background: var(--surface); border-bottom: 1px solid var(--border); padding: 10px 14px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
66
|
+
body { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
|
|
67
|
+
header { background: var(--surface); border-bottom: 1px solid var(--border); padding: calc(10px + env(safe-area-inset-top, 0px)) calc(14px + env(safe-area-inset-right, 0px)) 10px calc(14px + env(safe-area-inset-left, 0px)); display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
68
68
|
.title { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
|
|
69
69
|
.server { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; white-space: nowrap; }
|
|
70
70
|
.tool { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -75,13 +75,24 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
75
75
|
button.primary { border-color: color-mix(in srgb, var(--good) 40%, var(--border) 60%); color: var(--good); }
|
|
76
76
|
button.danger { border-color: color-mix(in srgb, var(--bad) 40%, var(--border) 60%); color: var(--bad); }
|
|
77
77
|
button:hover { background: color-mix(in srgb, var(--surface) 75%, var(--accent) 25%); }
|
|
78
|
-
main { flex: 1; min-height: 0; padding: 10px; display: flex; }
|
|
78
|
+
main { flex: 1; min-height: 0; padding: 10px; padding-inline: calc(10px + env(safe-area-inset-left, 0px)) calc(10px + env(safe-area-inset-right, 0px)); padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); display: flex; }
|
|
79
79
|
iframe { width: 100%; height: 100%; border: 1px solid var(--border); border-radius: 10px; background: white; }
|
|
80
|
-
.overlay { position: fixed; inset: 0; background: color-mix(in srgb, var(--bg) 90%, black 10%); display: none; align-items: center; justify-content: center; z-index: 2; }
|
|
80
|
+
.overlay { position: fixed; inset: 0; background: color-mix(in srgb, var(--bg) 90%, black 10%); display: none; align-items: center; justify-content: center; z-index: 2; padding: 16px; }
|
|
81
81
|
.overlay.visible { display: flex; }
|
|
82
82
|
.panel { width: min(680px, calc(100vw - 40px)); background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 18px; }
|
|
83
83
|
.panel h2 { margin: 0 0 8px; font-size: 16px; }
|
|
84
84
|
.panel p { margin: 0; color: var(--muted); line-height: 1.4; font-size: 14px; white-space: pre-wrap; }
|
|
85
|
+
@media (max-width: 640px) {
|
|
86
|
+
header { align-items: stretch; flex-direction: column; gap: 8px; }
|
|
87
|
+
.title { flex-wrap: wrap; row-gap: 4px; }
|
|
88
|
+
.server { flex-basis: 100%; }
|
|
89
|
+
.controls { width: 100%; }
|
|
90
|
+
.status { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
91
|
+
button { min-height: 44px; padding: 10px 14px; }
|
|
92
|
+
main { padding: 6px; padding-inline: calc(6px + env(safe-area-inset-left, 0px)) calc(6px + env(safe-area-inset-right, 0px)); padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }
|
|
93
|
+
iframe { border-radius: 6px; }
|
|
94
|
+
.panel { width: 100%; }
|
|
95
|
+
}
|
|
85
96
|
</style>
|
|
86
97
|
</head>
|
|
87
98
|
<body>
|
|
@@ -98,7 +109,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
98
109
|
</div>
|
|
99
110
|
</header>
|
|
100
111
|
<main>
|
|
101
|
-
<iframe id="mcp-app" referrerpolicy="no-referrer"></iframe>
|
|
112
|
+
<iframe id="mcp-app" sandbox="allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads" referrerpolicy="no-referrer"></iframe>
|
|
102
113
|
</main>
|
|
103
114
|
<div class="overlay" id="error-overlay">
|
|
104
115
|
<div class="panel">
|
|
@@ -106,6 +117,12 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
106
117
|
<p id="error-message"></p>
|
|
107
118
|
</div>
|
|
108
119
|
</div>
|
|
120
|
+
<div class="overlay" id="completion-overlay">
|
|
121
|
+
<div class="panel">
|
|
122
|
+
<h2>Done</h2>
|
|
123
|
+
<p>MCP UI session finished. You can close this page and return to Pi.</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
109
126
|
<script type="module">
|
|
110
127
|
import { AppBridge, PostMessageTransport } from ${moduleUrl};
|
|
111
128
|
|
|
@@ -125,6 +142,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
125
142
|
const doneBtn = document.getElementById("done-btn");
|
|
126
143
|
const cancelBtn = document.getElementById("cancel-btn");
|
|
127
144
|
const errorOverlay = document.getElementById("error-overlay");
|
|
145
|
+
const completionOverlay = document.getElementById("completion-overlay");
|
|
128
146
|
const errorMessage = document.getElementById("error-message");
|
|
129
147
|
|
|
130
148
|
document.getElementById("server-name").textContent = SERVER_NAME;
|
|
@@ -141,6 +159,26 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
141
159
|
setStatus("Error", true);
|
|
142
160
|
};
|
|
143
161
|
|
|
162
|
+
let completionPending = false;
|
|
163
|
+
const showCompletion = () => {
|
|
164
|
+
completionOverlay.classList.add("visible");
|
|
165
|
+
setStatus("Complete");
|
|
166
|
+
};
|
|
167
|
+
const closeOrShowDone = () => {
|
|
168
|
+
completionPending = true;
|
|
169
|
+
window.close();
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
if (!document.hidden) {
|
|
172
|
+
showCompletion();
|
|
173
|
+
}
|
|
174
|
+
}, 1000);
|
|
175
|
+
};
|
|
176
|
+
document.addEventListener("visibilitychange", () => {
|
|
177
|
+
if (completionPending && !document.hidden) {
|
|
178
|
+
showCompletion();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
144
182
|
const post = async (endpoint, params) => {
|
|
145
183
|
const response = await fetch(endpoint, {
|
|
146
184
|
method: "POST",
|
|
@@ -315,7 +353,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
315
353
|
eventSource.addEventListener("session-complete", async () => {
|
|
316
354
|
await bridge.teardownResource({}).catch(() => {});
|
|
317
355
|
eventSource.close();
|
|
318
|
-
|
|
356
|
+
closeOrShowDone();
|
|
319
357
|
});
|
|
320
358
|
eventSource.onerror = () => {
|
|
321
359
|
setStatus("Connection lost", true);
|
|
@@ -334,7 +372,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
334
372
|
} catch {}
|
|
335
373
|
clearInterval(heartbeat);
|
|
336
374
|
eventSource.close();
|
|
337
|
-
|
|
375
|
+
closeOrShowDone();
|
|
338
376
|
};
|
|
339
377
|
|
|
340
378
|
doneBtn.addEventListener("click", () => complete("done"));
|
|
@@ -353,13 +391,11 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
353
391
|
</html>`;
|
|
354
392
|
}
|
|
355
393
|
|
|
356
|
-
export function buildCspMetaContent(csp: UiResourceCsp | undefined): string
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const frameDomains = sanitizeCspDomains(csp.frameDomains);
|
|
362
|
-
const baseUriDomains = sanitizeCspDomains(csp.baseUriDomains);
|
|
394
|
+
export function buildCspMetaContent(csp: UiResourceCsp | undefined): string {
|
|
395
|
+
const resourceDomains = sanitizeCspDomains(csp?.resourceDomains);
|
|
396
|
+
const connectDomains = sanitizeCspDomains(csp?.connectDomains);
|
|
397
|
+
const frameDomains = sanitizeCspDomains(csp?.frameDomains);
|
|
398
|
+
const baseUriDomains = sanitizeCspDomains(csp?.baseUriDomains);
|
|
363
399
|
|
|
364
400
|
return [
|
|
365
401
|
"default-src 'none'",
|
|
@@ -368,11 +404,13 @@ export function buildCspMetaContent(csp: UiResourceCsp | undefined): string | un
|
|
|
368
404
|
toDirective("font-src", ["'self'"], resourceDomains),
|
|
369
405
|
toDirective("img-src", ["'self'", "data:"], resourceDomains),
|
|
370
406
|
toDirective("media-src", ["'self'", "data:"], resourceDomains),
|
|
371
|
-
|
|
407
|
+
connectDomains.length > 0
|
|
408
|
+
? `connect-src ${connectDomains.join(" ")}`
|
|
409
|
+
: "connect-src 'none'",
|
|
372
410
|
frameDomains.length > 0
|
|
373
411
|
? `frame-src ${frameDomains.join(" ")}`
|
|
374
412
|
: "frame-src 'none'",
|
|
375
|
-
|
|
413
|
+
"worker-src 'none'",
|
|
376
414
|
"object-src 'none'",
|
|
377
415
|
baseUriDomains.length > 0
|
|
378
416
|
? `base-uri ${baseUriDomains.join(" ")}`
|
package/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { createMcpDirectToolCallRenderer, renderMcpProxyToolCall, renderMcpToolR
|
|
|
17
17
|
import { toolErrorOverride } from "./error-signal.ts";
|
|
18
18
|
import { createMcpRuntimeOwner, createOwnedUi, isAbortError, type McpRuntimeOwner } from "./runtime-owner.ts";
|
|
19
19
|
import { publishMcpStatusShutdown } from "./mcp-status.ts";
|
|
20
|
+
import { runMcpScript } from "./mcp-code.ts";
|
|
20
21
|
|
|
21
22
|
export type { McpAdapterOptions } from "./types.ts";
|
|
22
23
|
export {
|
|
@@ -106,6 +107,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
106
107
|
const fallbackDeactivatedTools = new Set<string>();
|
|
107
108
|
let proxyToolRegistered = false;
|
|
108
109
|
let proxyToolDescription: string | null = null;
|
|
110
|
+
let directToolsFrozen = false;
|
|
109
111
|
|
|
110
112
|
// OMP remaps `typebox` to a host shim that historically lacked Type.Unsafe.
|
|
111
113
|
// Prefer Unsafe when present (real TypeBox / fixed OMP shim); otherwise pass
|
|
@@ -278,12 +280,20 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
278
280
|
nextState.onToolMetadataUpdated = (_serverName, _reason) => {
|
|
279
281
|
if (state !== nextState || !owner.isActive()) return;
|
|
280
282
|
syncPromptCommands();
|
|
283
|
+
if (directToolsFrozen) {
|
|
284
|
+
logger.debug(`MCP: metadata update for ${_serverName} (${_reason}) skipped — directTools frozen`);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
281
287
|
syncToolSurface(ctx);
|
|
282
288
|
};
|
|
283
289
|
syncPromptCommands();
|
|
284
290
|
syncToolSurface(ctx);
|
|
285
291
|
updateStatusBar(nextState);
|
|
286
292
|
initPromise = null;
|
|
293
|
+
if (earlyConfig.settings?.freezeDirectTools === true) {
|
|
294
|
+
directToolsFrozen = true;
|
|
295
|
+
logger.info("MCP: direct tools frozen after initial sync — reconnects won't rebuild the system prompt; use mcp({ connect: \"server\" }) to rediscover");
|
|
296
|
+
}
|
|
287
297
|
}).catch(async err => {
|
|
288
298
|
if (!owner.isActive() || generation !== lifecycleGeneration) {
|
|
289
299
|
return;
|
|
@@ -462,6 +472,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
462
472
|
case "reconnect":
|
|
463
473
|
commandOwner?.throwIfInactive();
|
|
464
474
|
await reconnectServers(state, commandCtx, targetServer);
|
|
475
|
+
if (directToolsFrozen) syncToolSurface(commandCtx);
|
|
465
476
|
break;
|
|
466
477
|
case "tools":
|
|
467
478
|
await showTools(state, commandCtx);
|
|
@@ -596,6 +607,52 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
596
607
|
},
|
|
597
608
|
});
|
|
598
609
|
|
|
610
|
+
if (earlyConfig.settings?.scriptMode === true) {
|
|
611
|
+
(pi.registerTool as (tool: unknown) => unknown)({
|
|
612
|
+
name: "mcp_script",
|
|
613
|
+
label: "MCP Script",
|
|
614
|
+
description: "Run a trusted JavaScript MCP script to orchestrate MCP tools. Discover with await tools.search({ query }) — resolves to { items: [{ path, name, server, description? }], total, hasMore, nextOffset }, not an { ok, data } envelope. Inspect with await tools.describe({ path }) — resolves to the tool descriptor with inputTypeScript, or { path, error: { code, message, suggestions } }. Then call tools.call(path, args) — resolves to { ok: true, data } or { ok: false, error: { code, message } } — or use direct flat calls when the name is already known; use emit(value) for user-visible output. Load the mcp-scripting skill for the full workflow guide.",
|
|
615
|
+
promptSnippet: "Run a JavaScript MCP script to chain and filter tool calls in one request",
|
|
616
|
+
parameters: Type.Object({
|
|
617
|
+
code: Type.String({ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value)." }),
|
|
618
|
+
// Raw JSON schema: host TypeBox shims may omit Type.Number (see index-lifecycle shim test).
|
|
619
|
+
timeoutMs: Type.Optional({ type: "number", minimum: 1, description: "Execution timeout in milliseconds (default: 30000)" } as any),
|
|
620
|
+
}),
|
|
621
|
+
renderResult: renderMcpToolResult,
|
|
622
|
+
async execute(_toolCallId, params: { code: string; timeoutMs?: number }, signal) {
|
|
623
|
+
const executeOwner = currentOwner;
|
|
624
|
+
if (!state && initPromise) {
|
|
625
|
+
try {
|
|
626
|
+
const initialized = await awaitWithTimeout(initPromise, INIT_WAIT_TIMEOUT_MS);
|
|
627
|
+
if (initialized === INIT_WAIT_TIMED_OUT) {
|
|
628
|
+
return {
|
|
629
|
+
content: [{ type: "text" as const, text: "MCP initialization is still in progress. Try again shortly." }],
|
|
630
|
+
details: { mode: "script", error: "init_timeout", timeoutMs: INIT_WAIT_TIMEOUT_MS },
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
executeOwner?.throwIfInactive();
|
|
634
|
+
state = initialized;
|
|
635
|
+
} catch (error) {
|
|
636
|
+
if (executeOwner && isAbortError(error, executeOwner.signal)) throw error;
|
|
637
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
638
|
+
return {
|
|
639
|
+
content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
|
|
640
|
+
details: { mode: "script", error: "init_failed", message },
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (!state) {
|
|
645
|
+
return {
|
|
646
|
+
content: [{ type: "text" as const, text: "MCP not initialized" }],
|
|
647
|
+
details: { mode: "script", error: "not_initialized" },
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
executeOwner?.throwIfInactive();
|
|
651
|
+
return runMcpScript(state, params.code, params.timeoutMs, getPiTools, signal);
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
599
656
|
function registerProxyTool(description: string): void {
|
|
600
657
|
(pi.registerTool as (tool: unknown) => unknown)({
|
|
601
658
|
name: "mcp",
|
|
@@ -618,6 +675,9 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
618
675
|
search: Type.Optional(Type.String({ description: "Search tools by name/description" })),
|
|
619
676
|
regex: Type.Optional(Type.Boolean({ description: "Treat search as regex (default: substring match)" })),
|
|
620
677
|
includeSchemas: Type.Optional(Type.Boolean({ description: "Include parameter schemas in search results (default: true)" })),
|
|
678
|
+
// Raw JSON schema: host TypeBox shims may omit Type.Number (see index-lifecycle shim test).
|
|
679
|
+
limit: Type.Optional({ type: "number", minimum: 1, description: "Maximum search results to return (default: 12)" } as any),
|
|
680
|
+
offset: Type.Optional({ type: "number", minimum: 0, description: "Search result offset (default: 0)" } as any),
|
|
621
681
|
server: Type.Optional(Type.String({ description: "Filter to specific server (also disambiguates tool calls)" })),
|
|
622
682
|
action: Type.Optional(Type.String({ description: "Action: 'ui-messages', 'auth-start', or 'auth-complete'" })),
|
|
623
683
|
}),
|
|
@@ -631,6 +691,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
631
691
|
search?: string;
|
|
632
692
|
regex?: boolean;
|
|
633
693
|
includeSchemas?: boolean;
|
|
694
|
+
limit?: number;
|
|
695
|
+
offset?: number;
|
|
634
696
|
server?: string;
|
|
635
697
|
action?: string;
|
|
636
698
|
}, signal, _onUpdate, _ctx) {
|
|
@@ -732,8 +794,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
732
794
|
if (params.instructions) {
|
|
733
795
|
return executeInstructions(state, params.instructions);
|
|
734
796
|
}
|
|
735
|
-
if (params.search) {
|
|
736
|
-
return executeSearch(state, params.search, params.regex, params.server, params.includeSchemas);
|
|
797
|
+
if (params.search !== undefined) {
|
|
798
|
+
return executeSearch(state, params.search, params.regex, params.server, params.includeSchemas, params.limit, params.offset);
|
|
737
799
|
}
|
|
738
800
|
if (params.server) {
|
|
739
801
|
return executeList(state, params.server);
|