pi-mcp-adapter 2.10.0 → 2.12.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 CHANGED
@@ -7,6 +7,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.12.0] - 2026-07-24
11
+
12
+ ### Added
13
+ - Added MCP prompts support as Pi slash commands under the `mcp__<server>__<prompt>` namespace, with capability-gated discovery, cache-backed startup registration, argument validation, lazy dispatch, and `/mcp prompts` listing. Thanks to Egor Egorov (@ee92) for PR #203.
14
+ - Hot-loaded refreshed direct MCP tools after metadata reconnects, lazy connects, direct-tool panel changes, and MCP list-change notifications. Thanks Devin Bost (@devinbost) for PR #72.
15
+ - Migrated the MCP client and interactive visualizer to the exact-pinned MCP SDK v2 beta.5 packages, with automatic protocol negotiation and client conformance coverage. Thanks Matt Carey (@mattzcarey) for PR #210.
16
+ - Added disabled MCP server definitions plus `/mcp disable` and `/mcp enable` project-local overrides that preserve visibility while preventing execution. Thanks Ömer Ulusoy (@ulusoyomer) for PR #61.
17
+ - Added argument completions for `/mcp` subcommands and reconnect/logout server names. Thanks @sting8k for PR #8.
18
+ - Surfaced MCP connection failure reasons from bounded stdio diagnostics in status output and the `/mcp` panel, with a shortcut to copy the selected failure. Thanks @parkuman for PR #197.
19
+ - Added Codex MCP imports from `.codex/config.toml`, with fallback to the existing JSON config. Thanks @npo-mmenke for PR #31.
20
+ - Added explicit OpenCode V1 MCP imports from global and project `opencode.json` files, including nested config merging and environment interpolation. Thanks @NicoAvanzDev for PR #25.
21
+ - Added environment-variable interpolation for HTTP MCP server URLs, with missing URL variables failing closed before requests are sent. Thanks @ozeias for PR #206.
22
+ - Added `settings.oauthDir` to store MCP OAuth credentials in a project-specific directory, with `MCP_OAUTH_DIR` still taking precedence. Thanks @Termina1 for PR #105.
23
+ - Added `lazy-keep-alive` lifecycle mode for MCP servers that should start on first use and then stay resident with health-check reconnects. Thanks @ricardoraposo for PR #143.
24
+ - Added `MCP_UI_VIEWER=none` / `off` / `disabled` to suppress MCP UI browser or Glimpse windows while keeping inline tool results available. Thanks @stevekrouse for PR #172.
25
+ - Surfaced MCP server `instructions` from the initialize handshake: captured at connect time, cached alongside tool metadata, shown as a truncated head in the `mcp` proxy tool description, previewed in `mcp({ server: "name" })` listings, and available in full via the new `mcp({ instructions: "name" })` mode. Thanks @JeongJuhyeon for issue #188 and PR #189.
26
+ - Added `createMcpAdapter({ config, configPath })` for isolated SDK configuration and file-path overrides. Thanks @Cansiny0320 for PR #86.
27
+
28
+ ### Changed
29
+ - Removed stale hot-loaded direct tools from Pi's registry when `pi.unregisterTool()` is available, while preserving active-tool deactivation fallback for older Pi hosts.
30
+ - Deferred loading the regex safety checker until regex search is used, improving startup time. Thanks @kaushikgopal for PR #175.
31
+ - Declared Pi host packages as optional peer dependencies with exact development pins, reducing extension install footprint and avoiding host version conflicts. Thanks @t0dorakis for PR #200.
32
+
33
+ ### Fixed
34
+ - Started MCP initialization at extension load when any server is configured with `lifecycle: "eager"` or `"keep-alive"`, so hosts that drive Pi programmatically without `session_start` still connect startup servers. Thanks Brian Gebel (@ductiletoaster) for PR #170.
35
+ - Enforced normalized standard `_meta.ui.csp` and OpenAI-compatible `_meta["openai/widgetCSP"]` metadata with response headers while preserving provider HTML. Thanks @IdoHadar for PR #195.
36
+ - Avoided MCP renderer crashes without a TUI theme and preserved status-bar updates with plain fallback text. Thanks @fankangsong for PR #183.
37
+ - Abandoned MCP initialization quietly when a session is disposed during eager or keep-alive connection setup. Thanks @luisfontes for PR #192.
38
+ - Fenced MCP runtime ownership across Pi reloads so stale callbacks and late connections cannot outlive their session. Thanks @uuunk (Paul Lorsbach) for PR #202.
39
+ - Added `toolPrefix: "mcp"` support for `mcp__<server>_<tool>` names across direct and proxy MCP tool paths. Thanks @riicodespretty for PR #99.
40
+ - Sanitized dotted MCP tool names before registering them with Pi. Thanks @benjaminrickels for PR #190.
41
+ - Reconnected OAuth MCP servers automatically after successful panel or `/mcp-auth` authorization, and made panel reconnect force a fresh connection like `/mcp reconnect`. Thanks @mightymatth for issue #171.
42
+ - Recovered stale Streamable HTTP MCP sessions that report `-32000 Server not initialized` after a server restart. Thanks @vicary for issue #184.
43
+ - Kept npm cache lookups working on Windows by resolving `npm` through `cross-spawn`. Thanks @zeyadhost for PR #201.
44
+ - Kept direct MCP tool registration working when a host TypeBox shim does not expose `Type.Unsafe`. Thanks @RaviTharuma for PR #198.
45
+ - Kept exact `npx` package specs from reusing a different same-name package version from npm's `_npx` cache. Thanks @danhrahal for issue #178.
46
+ - Kept POST-only Streamable HTTP servers on the Streamable HTTP transport when the optional GET stream returns 405. Thanks @ramhaidar for issue #204.
47
+ - Kept manual OAuth `auth-start` / `auth-complete` flows from being invalidated by keep-alive health checks, and made reserved manual callback states show a manual completion page instead of a CSRF error. Thanks @oozle for issue #207.
48
+ - Accepted object-valued `mcp.args` in addition to JSON strings, avoiding double-encoded tool arguments while preserving provider-compatible string calls. Thanks @johnny-smitherson for issue #205.
49
+ - Collapsed long single-line MCP results according to terminal-wrapped visual lines. Thanks @xz-dev for PR #181 and @maxpaulus43 for PR #177.
50
+ - Recovered Streamable HTTP MCP sessions after a server restart invalidates the previous session ID. Thanks @damselem for PR #194.
51
+ - Used server-advertised OAuth protected-resource metadata during authorization so resource servers can point Pi at the correct authorization server. Thanks @jameswarren for issue #173 and PR #174.
52
+ - Dropped inherited HTTP auth when a higher-precedence MCP config repoints a server URL, while preserving explicit OAuth disable flags. Thanks @ductiletoaster for PR #182.
53
+
54
+ ## [2.11.0] - 2026-07-03
55
+
56
+ ### Changed
57
+ - Restored the tracked npm lockfile for reproducible installs and downstream packaging. Thanks @fmoda3 for issue #71.
58
+
59
+ ### Added
60
+ - Added default-on MCP output guarding with temp-file spillover for oversized text results, compact summaries for large proxy result details, and `settings.outputGuard` tuning. Thanks @tmustier for PR #160.
61
+
62
+ ### Fixed
63
+ - Defaulted stdio MCP servers without an explicit `cwd` to the Pi session cwd so relative server output lands in the workspace. Thanks @TimoFreiberg for PR #152.
64
+ - Kept multiline/control MCP panel metadata from corrupting rows and made Keep & Close save dirty changes. Thanks @gpmarques for issue/PR #14, @Vahor for issue #115, and @markokocic for issue #134/PR #135.
65
+ - Preserved `--` separators when resolving `npx` wrapper commands so subcommand flags are not consumed by tools like `dotenv-cli`. Thanks @sherif-fanous for issue #15.
66
+ - Merged partial per-server Pi overrides into imported MCP server definitions instead of replacing the full server entry. Thanks @cfbraun for issue #94.
67
+ - Fixed the `pi-mcp-adapter` bin entrypoint when invoked through installed symlinks, so `init` runs instead of silently exiting. Thanks @cfbraun for issue #95.
68
+ - Normalized direct MCP tool schemas so draft metadata and strict top-level additional properties do not break Pi registration. Thanks @marchellodev for issue #2/PR #3 and @comtihon for PR #144.
69
+ - Routed interactive `/mcp-auth` OAuth URLs through Pi UI notifications so long authorization links remain intact instead of being truncated by raw terminal output. Thanks @feoh for issue #147/PR #148.
70
+ - Respected configured HTTP headers before implicit OAuth auto-detection so API-key/custom-header MCP servers do not trigger OAuth DCR. Thanks @OnlyXianzo for issue #158.
71
+ - Propagated Pi abort signals into MCP connect, resource, and tool requests so cancelled calls settle promptly. Thanks @xz-dev for PR #159 and @murrayju for PR #149.
72
+ - Re-flagged failed MCP tool calls (`tool_error`/`call_failed`) as errors so they are recorded as failures (`isError: true`) instead of successes. Thanks @ishinder for PR #157.
73
+ - Honored configured `requestTimeoutMs` during MCP connection, discovery, tool, resource, and UI proxy requests. Thanks @mizuikki for PR #155 and @danecando for PR #62.
74
+ - Rendered successful MCP `structuredContent` when servers return it without `content`. Thanks @dovixman for PR #146.
75
+
10
76
  ## [2.10.0] - 2026-06-13
11
77
 
12
78
  ### Added
package/README.md CHANGED
@@ -65,6 +65,8 @@ Precedence is:
65
65
  3. `.mcp.json`
66
66
  4. `.pi/mcp.json`
67
67
 
68
+ `/mcp disable <server>` and `/mcp enable <server>` persist only the `disabled` field in the project-local `.pi/mcp.json`, which is the highest-precedence Pi layer. Enabling removes the project flag when lower layers are enabled, or writes `false` when needed to override a disabled lower source. This applies even when the effective server came from a shared global/project file, an imported host config, or `configPath`; the source file is never rewritten and credentials are never copied. Run `/reload` after changing the flag so registered tool surfaces are refreshed. The manual equivalent is to add `{ "disabled": true }` to a server in any normal MCP config. Supplied in-memory `createMcpAdapter({ config })` configurations are isolated and do not read or write this project override; the commands are unavailable in that mode.
69
+
68
70
  Servers are **lazy by default** — they won't connect until you actually call one of their tools. The adapter caches tool metadata so search and describe work without live connections.
69
71
 
70
72
  ```
@@ -79,10 +81,10 @@ chrome_devtools_take_screenshot
79
81
  fullPage (boolean) - Full page instead of viewport
80
82
  ```
81
83
  ```
82
- mcp({ tool: "chrome_devtools_take_screenshot", args: '{"format": "png"}' })
84
+ mcp({ tool: "chrome_devtools_take_screenshot", args: { format: "png" } })
83
85
  ```
84
86
 
85
- Note: `args` is a JSON string, not an object.
87
+ `args` can be a JSON object or a JSON string. Prefer the object form when your model handles it reliably; the string form remains supported for providers that need simpler schemas.
86
88
 
87
89
  Two calls instead of 26 tools cluttering the context.
88
90
 
@@ -101,6 +103,35 @@ Use the shared MCP files when you want one setup to work across hosts, and Pi-ow
101
103
 
102
104
  Pi-specific files are the write targets for imported or shared global servers when Pi needs to persist adapter-only settings such as `directTools`.
103
105
 
106
+ ### SDK configuration
107
+
108
+ Use `createMcpAdapter` when an SDK or server integration already owns its MCP configuration:
109
+
110
+ ```ts
111
+ import { createMcpAdapter } from "pi-mcp-adapter";
112
+
113
+ const extension = createMcpAdapter({
114
+ config: {
115
+ mcpServers: {
116
+ docs: {
117
+ url: "https://mcp.example.com/mcp",
118
+ lifecycle: "eager",
119
+ },
120
+ },
121
+ },
122
+ });
123
+
124
+ // Register `extension` with the host SDK.
125
+ ```
126
+
127
+ The package ships TypeScript source for Pi's source-loader and SDK integrations. Use a TypeScript-capable loader/toolchain (for example `node --import tsx`) when importing the package from a standalone Node process; raw Node ESM does not execute the `.ts` entry directly.
128
+
129
+ A supplied `config` is a complete, isolated snapshot. It is not merged with files, imports, global config, project config, or `--mcp-config`, and it is never mutated. Each adapter factory and session receives its own clone, so separate integrations can use different servers and settings safely. In this mode, server status, reconnect, explicit `/mcp-auth <server>`, proxy calls, and direct tools continue to work; setup and no-argument auth/status panels report the limitation instead of discovering or writing ambient config.
130
+
131
+ With `configPath` and no `config`, the adapter keeps normal file merge behavior, and that path takes precedence over argv and `--mcp-config`. The default export keeps the normal file-based behavior. OAuth credentials remain persistent and are shared by the configured server name by default; callers requiring strict same-name credential-file isolation should set distinct `settings.oauthDir` values. URL binding prevents credentials from being accepted for a different server URL. CSRF state and PKCE verifiers are flow-local, so concurrent authorization flows do not share transient secrets.
132
+
133
+ In the configuration examples below, `30000` is illustrative only. If `requestTimeoutMs` is omitted or set to `<= 0`, the MCP SDK default timeout is used.
134
+
104
135
  ### Server Options
105
136
 
106
137
  ```json
@@ -110,7 +141,8 @@ Pi-specific files are the write targets for imported or shared global servers wh
110
141
  "command": "npx",
111
142
  "args": ["-y", "some-mcp-server"],
112
143
  "lifecycle": "lazy",
113
- "idleTimeout": 10
144
+ "idleTimeout": 10,
145
+ "requestTimeoutMs": 30000
114
146
  }
115
147
  }
116
148
  }
@@ -122,7 +154,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
122
154
  | `args` | Command arguments |
123
155
  | `env` | Environment variables; supports `${VAR}` and `$env:VAR` interpolation |
124
156
  | `cwd` | Working directory; supports `${VAR}`, `$env:VAR`, and `~` expansion |
125
- | `url` | HTTP endpoint (StreamableHTTP with SSE fallback) |
157
+ | `url` | HTTP endpoint (StreamableHTTP with SSE fallback); supports raw `${VAR}` and `$env:VAR` interpolation, and missing URL variables fail before any request is sent |
126
158
  | `headers` | HTTP headers; supports `${VAR}` and `$env:VAR` interpolation |
127
159
  | `auth` | `"bearer"` or `"oauth"` |
128
160
  | `oauth.grantType` | `"authorization_code"` (default) or `"client_credentials"` for non-interactive machine auth |
@@ -133,12 +165,14 @@ Pi-specific files are the write targets for imported or shared global servers wh
133
165
  | `oauth.clientName` | Client display name advertised during dynamic registration |
134
166
  | `oauth.clientUri` | Client homepage URI advertised during dynamic registration |
135
167
  | `bearerToken` / `bearerTokenEnv` | Token or env var name; `bearerToken` supports `${VAR}` and `$env:VAR` interpolation |
136
- | `lifecycle` | `"lazy"` (default), `"eager"`, or `"keep-alive"` |
168
+ | `lifecycle` | `"lazy"` (default), `"eager"`, `"keep-alive"`, or `"lazy-keep-alive"` |
137
169
  | `idleTimeout` | Minutes before idle disconnect (overrides global) |
170
+ | `requestTimeoutMs` | Request timeout in milliseconds for live MCP calls (overrides global; if omitted or `<= 0`, the MCP SDK default timeout is used) |
138
171
  | `exposeResources` | Expose MCP resources as tools (default: true) |
139
172
  | `directTools` | `true`, `string[]`, or `false` — register tools individually instead of through proxy |
140
173
  | `excludeTools` | `string[]` of tool names to hide (matches original names like `get_screenshot` and prefixed names like `figma_get_screenshot`) |
141
174
  | `debug` | Show server stderr (default: false) |
175
+ | `disabled` | Keep the server visible in config and status, but prevent connections, authentication, tools, and resource calls (only literal `true` disables it) |
142
176
 
143
177
  For pre-registered browser OAuth clients, set `oauth.redirectUri` to the exact callback registered with the provider, for example `"http://localhost:3118/callback"`. Dynamic clients normally omit it and use a lazy OS-assigned localhost callback port.
144
178
 
@@ -156,17 +190,20 @@ Open the returned authorization URL in your local browser. After approval, your
156
190
  mcp({
157
191
  action: "auth-complete",
158
192
  server: "linear-server",
159
- args: '{"redirectUrl":"http://localhost:19876/callback?code=...&state=..."}'
193
+ args: { redirectUrl: "http://localhost:19876/callback?code=...&state=..." }
160
194
  })
161
195
  ```
162
196
 
163
- You can also pass only the `code` query parameter with `args: '{"code":"..."}'`. Treat authorization URLs and codes as sensitive; they can grant access to the MCP server until the flow expires or completes.
197
+ You can also pass only the `code` query parameter with `args: { code: "..." }`. Treat authorization URLs and codes as sensitive; they can grant access to the MCP server until the flow expires or completes.
164
198
 
165
199
  ### Lifecycle Modes
166
200
 
167
201
  - **`lazy`** (default) — Don't connect at startup. Connect on first tool call. Disconnect after idle timeout. Cached metadata keeps search/list working without connections.
168
202
  - **`eager`** — Connect at startup but don't auto-reconnect if the connection drops. No idle timeout by default (set `idleTimeout` explicitly to enable).
169
203
  - **`keep-alive`** — Connect at startup. Auto-reconnect via health checks. No idle timeout. Use for servers you always need available.
204
+ - **`lazy-keep-alive`** — Don't connect at startup. Connect on first tool call (like `lazy`). Once spawned, never idle-shut down and auto-reconnect via health checks if the process dies (like `keep-alive`). Use for servers that are expensive to start but should stay resident after their first use.
205
+
206
+ When any enabled server uses `eager` or `keep-alive`, initialization also starts when the extension loads. This supports hosts that embed Pi programmatically and never emit `session_start`; if a session does start later, the session-owned runtime supersedes the load-time runtime.
170
207
 
171
208
  ### Settings
172
209
 
@@ -174,7 +211,9 @@ You can also pass only the `code` query parameter with `args: '{"code":"..."}'`.
174
211
  {
175
212
  "settings": {
176
213
  "toolPrefix": "server",
177
- "idleTimeout": 10
214
+ "idleTimeout": 10,
215
+ "requestTimeoutMs": 30000,
216
+ "oauthDir": ".pi/mcp-oauth"
178
217
  },
179
218
  "mcpServers": { }
180
219
  }
@@ -182,16 +221,51 @@ You can also pass only the `code` query parameter with `args: '{"code":"..."}'`.
182
221
 
183
222
  | Setting | Description |
184
223
  |---------|-------------|
185
- | `toolPrefix` | `"server"` (default), `"short"` (strips `-mcp` suffix), or `"none"` |
224
+ | `toolPrefix` | `"server"` (default), `"short"` (strips `-mcp` suffix), `"none"`, or `"mcp"` (prefixes with `mcp__`, using server-mode normalization) |
186
225
  | `idleTimeout` | Global idle timeout in minutes (default: 10, 0 to disable) |
226
+ | `requestTimeoutMs` | Global request timeout in milliseconds for live MCP calls (if omitted or `<= 0`, the MCP SDK default timeout is used) |
227
+ | `oauthDir` | OAuth credential directory for this MCP config. Relative paths resolve from the active project cwd. `MCP_OAUTH_DIR` still wins when set. |
187
228
  | `directTools` | Global default for all servers (default: false). Per-server overrides this. |
188
229
  | `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
189
230
  | `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
190
231
  | `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
191
232
  | `samplingAutoApprove` | Skip sampling confirmation prompts. Required for sampling in non-UI sessions (default: false). |
192
233
  | `elicitation` | Allow MCP servers to request user input through Pi dialogs (default: true when Pi UI is available). |
234
+ | `outputGuard` | Guard oversized MCP output: `true` (default), `false`, or `{ maxBytes, maxLines, detailsMaxBytes }`. See [Output Guard](#output-guard). |
235
+
236
+ Per-server `idleTimeout` and `requestTimeoutMs` override the global settings.
237
+
238
+ ### Output Guard
193
239
 
194
- Per-server `idleTimeout` overrides the global setting.
240
+ Oversized MCP tool/resource results are guarded by default so a single huge response can't blow up the model context window or the session file:
241
+
242
+ - Inline text output is capped at **50 KiB / 2,000 lines** (matching Pi's built-in `bash` guard). Larger output is truncated to a head preview and the full text is saved to a temp file whose path is included in the result, so the agent can `read`/`grep` it.
243
+ - **Image content blocks pass through unchanged** — only text output is guarded. Images are delivered to the provider as native image content.
244
+ - In proxy mode, `details.mcpResult` is kept raw when its JSON is **≤ 16 KiB**; larger results are replaced with a compact summary (block counts, sizes, key previews) and the raw JSON is saved to a temp file. Direct tools keep their lean details and never carry `mcpResult`.
245
+
246
+ Tune the limits with the object form:
247
+
248
+ ```json
249
+ {
250
+ "settings": {
251
+ "outputGuard": { "maxBytes": 51200, "maxLines": 2000, "detailsMaxBytes": 16384 }
252
+ }
253
+ }
254
+ ```
255
+
256
+ 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.
257
+
258
+ ### MCP Prompts
259
+
260
+ 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.
261
+
262
+ ```text
263
+ /mcp__agent_board__create_plan "harden retry policy"
264
+ /mcp__agent_board__review_pipeline status=paused
265
+ /mcp prompts
266
+ ```
267
+
268
+ Prompt results are flattened into one user message, preserving `[user]` and `[assistant]` role markers for multi-message results. Servers without the `prompts` capability are not probed.
195
269
 
196
270
  ### MCP Elicitation
197
271
 
@@ -267,9 +341,9 @@ To exclude specific tools while still using `directTools: true`, add `excludeToo
267
341
 
268
342
  Each direct tool costs ~150-300 tokens in the system prompt (name + description + schema). Good for targeted sets of 5-20 tools. For servers with 75+ tools, stick with the proxy or pick specific tools with a `string[]`.
269
343
 
270
- 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 and the cache populates in the background. To force it: `/mcp reconnect <server>`.
344
+ 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>`.
271
345
 
272
- When you change direct-tool toggles in `/mcp` or write new config through `/mcp setup`, the extension triggers Pi's normal reload flow automatically. That refreshes extensions, prompts, skills, and MCP tool registration in one shot, so newly configured direct tools can appear without a manual restart.
346
+ 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.
273
347
 
274
348
  **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.
275
349
 
@@ -288,7 +362,7 @@ MCP servers can ship interactive UIs via the [MCP UI](https://github.com/MCP-UI-
288
362
  3. pi-mcp-adapter fetches the UI HTML and opens it in an iframe
289
363
  4. The UI can call MCP tools and send messages back to the agent
290
364
 
291
- **Native rendering:** On macOS, if [Glimpse](https://github.com/hazat/glimpse) is installed (`pi install npm:glimpseui`), UIs open in a native WKWebView window instead of a browser tab. Set `MCP_UI_VIEWER=browser` to force the browser, or `MCP_UI_VIEWER=glimpse` to require native rendering.
365
+ **Native rendering:** On macOS, if [Glimpse](https://github.com/hazat/glimpse) is installed (`pi install npm:glimpseui`), UIs open in a native WKWebView window instead of a browser tab. Set `MCP_UI_VIEWER=browser` to force the browser, `MCP_UI_VIEWER=glimpse` to require native rendering, or `MCP_UI_VIEWER=none` (also accepts `off` / `disabled`) to suppress the window entirely — the tool still runs and its inline result is returned to the agent, but no browser or native window opens. This is useful for headless setups, CI, or users who want the tool output delivered inline as text only. When suppressed, a one-line info notification shows the UI URL so it can still be opened manually if needed.
292
366
 
293
367
  **Bidirectional communication:** The UI talks back. When it sends a prompt or intent, the message is stored and `triggerTurn()` wakes the agent. The agent retrieves messages via `mcp({ action: "ui-messages" })` and responds, enabling conversational UIs where the app and agent collaborate in real-time.
294
368
 
@@ -323,6 +397,7 @@ Returns accumulated messages from UI sessions. Each message includes `type`, `se
323
397
  - Tool consent gates whether UIs can call MCP tools (never/once-per-server/always)
324
398
  - Works with both stdio and HTTP MCP servers
325
399
  - Uses a local 408KB AppBridge bundle (MCP SDK + Zod) for browser↔server communication
400
+ - Enforces CSP from standard `_meta.ui.csp` and OpenAI-compatible `_meta["openai/widgetCSP"]` metadata in the response header while preserving provider HTML.
326
401
 
327
402
  ### Local Example: Interactive Visualizer
328
403
 
@@ -342,14 +417,14 @@ Shared MCP files are loaded automatically. Use `imports` only for host-specific
342
417
 
343
418
  ```json
344
419
  {
345
- "imports": ["cursor", "claude-code", "claude-desktop"],
420
+ "imports": ["cursor", "claude-code", "claude-desktop", "opencode"],
346
421
  "mcpServers": { }
347
422
  }
348
423
  ```
349
424
 
350
- Supported compatibility imports: `cursor`, `claude-code`, `claude-desktop`, `vscode`, `windsurf`, `codex`
425
+ Supported compatibility imports: `cursor`, `claude-code`, `claude-desktop`, `opencode`, `vscode`, `windsurf`, `codex`
351
426
 
352
- `pi-mcp-adapter init` detects these host-specific configs and adds missing imports to the Pi agent dir config for you.
427
+ `pi-mcp-adapter init` detects these host-specific configs and adds missing imports to the Pi agent dir config for you. The `opencode` import reads OpenCode V1 `mcp` entries from both `~/.config/opencode/opencode.json` and the project `opencode.json`, with project fields taking precedence. It is explicit-import only; OpenCode V2, inline content, managed configs, and remote discovery are not supported.
353
428
 
354
429
  ### Project Config
355
430
 
@@ -363,18 +438,21 @@ Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only
363
438
  | List server | `mcp({ server: "name" })` |
364
439
  | Search | `mcp({ search: "screenshot navigate" })` |
365
440
  | Describe | `mcp({ describe: "tool_name" })` |
366
- | Call | `mcp({ tool: "...", args: '{"key": "value"}' })` |
441
+ | Instructions | `mcp({ instructions: "name" })` |
442
+ | Call | `mcp({ tool: "...", args: { key: "value" } })` |
367
443
  | Connect | `mcp({ connect: "server-name" })` |
368
444
  | UI messages | `mcp({ action: "ui-messages" })` |
369
445
  | Auth start | `mcp({ action: "auth-start", server: "name" })` |
370
- | Auth complete | `mcp({ action: "auth-complete", server: "name", args: '{"redirectUrl":"..."}' })` |
446
+ | Auth complete | `mcp({ action: "auth-complete", server: "name", args: { redirectUrl: "..." } })` |
371
447
 
372
- MCP proxy and direct-tool results render compactly by default: long text shows the first three lines plus a `Ctrl+O to expand` hint, while the full result remains available when expanded and is still returned unchanged to the model.
448
+ 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.
373
449
 
374
450
  Search includes both MCP tools and Pi tools (from extensions). Pi tools appear first with `[pi tool]` prefix. Space-separated words are OR'd.
375
451
 
376
452
  Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_library_id` finds `context7_resolve-library-id`.
377
453
 
454
+ 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.
455
+
378
456
  ## Commands
379
457
 
380
458
  | Command | What it does |
@@ -382,8 +460,11 @@ Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_li
382
460
  | `/mcp` | Interactive panel and first-run onboarding surface |
383
461
  | `/mcp setup` | Guided setup for imports, a minimal `.mcp.json`, RepoPrompt quick-add, and config-path inspection |
384
462
  | `/mcp tools` | List all tools |
463
+ | `/mcp prompts` | List all MCP prompts registered as slash commands |
385
464
  | `/mcp reconnect` | Reconnect all servers |
386
465
  | `/mcp reconnect <server>` | Connect or reconnect a single server |
466
+ | `/mcp disable <server>` | Disable a server in the project-local `.pi/mcp.json` (requires `/reload` to apply) |
467
+ | `/mcp enable <server>` | Enable through the project-local override layer (requires `/reload` to apply) |
387
468
  | `/mcp logout <server>` | Clear stored OAuth credentials for a server and disconnect it |
388
469
  | `/mcp-auth` | Open an OAuth server picker in interactive UI sessions |
389
470
  | `/mcp-auth <server>` | OAuth setup for a specific server |
package/abort.ts ADDED
@@ -0,0 +1,34 @@
1
+ export function throwIfAborted(signal?: AbortSignal): void {
2
+ if (!signal?.aborted) return;
3
+ throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted"));
4
+ }
5
+
6
+ export async function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
7
+ if (!signal) return promise;
8
+ throwIfAborted(signal);
9
+ return await new Promise<T>((resolve, reject) => {
10
+ let settled = false;
11
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
12
+ const onAbort = () => {
13
+ if (settled) return;
14
+ settled = true;
15
+ cleanup();
16
+ reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted")));
17
+ };
18
+ signal.addEventListener("abort", onAbort, { once: true });
19
+ promise.then(
20
+ value => {
21
+ if (settled) return;
22
+ settled = true;
23
+ cleanup();
24
+ resolve(value);
25
+ },
26
+ error => {
27
+ if (settled) return;
28
+ settled = true;
29
+ cleanup();
30
+ reject(error);
31
+ },
32
+ );
33
+ });
34
+ }
package/cli.js CHANGED
@@ -29,7 +29,10 @@ const IMPORT_PATHS = {
29
29
  path.join(HOME, ".claude", "claude_desktop_config.json"),
30
30
  ],
31
31
  "claude-desktop": [path.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json")],
32
- codex: [path.join(HOME, ".codex", "config.json")],
32
+ codex: [
33
+ path.join(HOME, ".codex", "config.toml"),
34
+ path.join(HOME, ".codex", "config.json"),
35
+ ],
33
36
  windsurf: [path.join(HOME, ".windsurf", "mcp.json")],
34
37
  vscode: [path.resolve(process.cwd(), ".vscode", "mcp.json")],
35
38
  };
@@ -172,7 +175,8 @@ export async function main(argv = process.argv.slice(2), log = console.log, erro
172
175
  return 1;
173
176
  }
174
177
 
175
- const isEntrypoint = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
178
+ const resolvedEntrypoint = process.argv[1] ? fs.realpathSync(process.argv[1]) : undefined;
179
+ const isEntrypoint = resolvedEntrypoint && import.meta.url === pathToFileURL(resolvedEntrypoint).href;
176
180
 
177
181
  if (isEntrypoint) {
178
182
  main().then((code) => {