@fitchmultz/pi-mcp-adapter 8.0.0 → 8.0.1
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 +18 -7
- package/LICENSE +1 -0
- package/OAUTH.md +3 -3
- package/README.md +43 -858
- package/dist/app-bridge.bundle.js +13 -13
- package/dist/config.js +1 -1
- package/dist/mcp-setup-panel.js +1 -1
- package/package.json +7 -10
package/README.md
CHANGED
|
@@ -1,906 +1,91 @@
|
|
|
1
|
-
<p>
|
|
2
|
-
<img src="banner.png" alt="pi-mcp-adapter" width="1100">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
1
|
# Fitch MCP Adapter
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
An independently maintained distribution of [Nico's Pi MCP Adapter](https://github.com/nicobailon/pi-mcp-adapter), under the original MIT license.
|
|
10
|
-
|
|
11
|
-
https://github.com/user-attachments/assets/4b7c66ff-e27e-4639-b195-22c3db406a5a
|
|
12
|
-
|
|
13
|
-
## Why This Exists
|
|
14
|
-
|
|
15
|
-
Mario wrote about [why you might not need MCP](https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/). The problem: tool definitions are verbose. A single MCP server can burn 10k+ tokens, and you're paying that cost whether you use those tools or not. Connect a few servers and you've burned half your context window before the conversation starts.
|
|
16
|
-
|
|
17
|
-
His take: skip MCP entirely, write simple CLI tools instead.
|
|
18
|
-
|
|
19
|
-
The MCP ecosystem has useful databases, browsers, and APIs. This adapter keeps their full tool schemas out of context until needed. Only `mcp_search` and configured pins start active. `mcp_search` discovers and loads typed tools for the next request; `mcp_search({ enable: ["gateway"] })` loads `mcp` for gateway actions, and `mcp_search({ enable: ["script"] })` loads `mcp_script` for JavaScript composition. Lazy servers stay disconnected until selected discovery or a call needs them.
|
|
20
|
-
|
|
21
|
-
## Pi release qualification
|
|
22
|
-
|
|
23
|
-
The supported Pi baseline is official **1.0.0**; host peers remain wildcard. Older Pi releases are not supported. `npm run check:compat` verifies the installed SDK and CLI identity, builds and typechecks, runs Vitest and memory-only OAuth tests, then loads a packed consumer through the native SDK and CLI.
|
|
24
|
-
|
|
25
|
-
GitHub CI runs that contract on Node 24 against both official Pi and the maintained fork through the shared Pi compatibility automation, including fresh Git and npm installations loaded by the real Pi CLI. A second job checks lockfile registry hosts, published type declarations, the built interactive visualizer, and MCP protocol conformance. These checks use local MCP fixtures and disposable agent directories, never live credentials or paid providers.
|
|
3
|
+
Connect [Pi](https://github.com/earendil-works/pi) to MCP servers for browsers, databases, and APIs. The adapter loads selected tool schemas when needed to keep large catalogs out of the conversation context.
|
|
26
4
|
|
|
27
|
-
|
|
5
|
+

|
|
28
6
|
|
|
29
7
|
## Install
|
|
30
8
|
|
|
31
|
-
|
|
9
|
+
Use Pi 1.0.0 or later and Node.js 24 or later.
|
|
32
10
|
|
|
33
11
|
```bash
|
|
34
12
|
pi install npm:@fitchmultz/pi-mcp-adapter
|
|
13
|
+
pi
|
|
35
14
|
```
|
|
36
15
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
On **Android/Termux**, Git/source installs and type checks require an Android-native
|
|
40
|
-
TypeScript 7 compiler as `tsgo` on `PATH`. The [Fold development environment](https://github.com/fitchmultz/fold-dev-environment)
|
|
41
|
-
provides a pinned native build (validated with `7.1.0-dev.20260904.1`). npm's TypeScript
|
|
42
|
-
package has no Android binary, and its Linux binary probes a syscall Android blocks.
|
|
43
|
-
macOS/Linux use the package-local pinned compiler. Published precompiled packages
|
|
44
|
-
do not need a compiler at runtime.
|
|
45
|
-
|
|
46
|
-
Restart Pi after installation. Existing v4 users should migrate first.
|
|
47
|
-
|
|
48
|
-
### Upgrading from v5
|
|
49
|
-
|
|
50
|
-
Version 6 keeps v5's package, config paths, and OAuth namespace. No credential migration is needed.
|
|
51
|
-
|
|
52
|
-
- Use `mcp_search({ query: "...", server: "name" })` to discover and load typed tools. It loads matches for the next request; it never executes a search hit. `directTools` now pins tools at startup rather than deciding which tools can ever be loaded.
|
|
53
|
-
- Give every new gateway call an explicit `action`: `mcp({ action: "search", query: "..." })`, `mcp({ action: "describe", tool: "..." })`, or `mcp({ action: "call", tool: "...", args: {} })`. Arguments are objects. Stored v5 calls with optional mode fields or JSON-string arguments are normalized at the ingress compatibility boundary; they are not the advertised v6 interface.
|
|
54
|
-
- Resources use `resources` and `read-resource` actions, or `tools.resources` and `tools.readResource` in scripts. Generated `read_<resource>` aliases remain callable through gateway/script compatibility, but no longer appear as functions or panel checkboxes. Remove resource aliases from `directTools`; legacy resource pins keep the gateway available even with `disableProxyTool: true`.
|
|
55
|
-
- Script descriptions always return complete JSON Schema in `inputSchema`, with `outputSchema` and other descriptor fields when supplied. Replace `inputTypeScript` consumers. Calls keep raw MCP results, including error-result data; use returned result references for bounded readback without another server call.
|
|
56
|
-
- Global search reports partial catalog coverage instead of starting every uncached lazy server. Select a server to discover it. Cache format 2 preserves raw descriptors and still reads format 1; downgrading to v5 ignores format 2 and requires rediscovery.
|
|
57
|
-
|
|
58
|
-
### Upgrading from v4
|
|
59
|
-
|
|
60
|
-
The Fitch distribution uses its own package, state paths and OAuth credential namespace. Remove the previous adapter package entry from Pi settings (for example, `pi remove npm:pi-mcp-adapter` for the unscoped npm source), then install the distribution above. Keep only one adapter loaded in a Pi host.
|
|
61
|
-
|
|
62
|
-
After installation, **before restarting Pi**, run from your project directory:
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
fitch-mcp-adapter migrate --dry-run
|
|
66
|
-
fitch-mcp-adapter migrate
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
If Pi's package bin is not on your `PATH`, use `npx --package @fitchmultz/pi-mcp-adapter fitch-mcp-adapter migrate` (append `--dry-run` to preview).
|
|
70
|
-
|
|
71
|
-
Migration copies v4's global `mcp.json`, `mcp-cache.json`, `mcp-onboarding.json` and `mcp-npx-cache.json` into `<Pi agent dir>/fitch-mcp-adapter/`, and the current project's `.pi/mcp.json` into `.pi/fitch-mcp-adapter/mcp.json`. Existing destinations are reported and left unchanged, without merging. Repeat in each project that has an old override. Use the same `PI_CODING_AGENT_DIR` as your Pi installation.
|
|
72
|
-
|
|
73
|
-
For configured HTTP servers, migration copies an existing OAuth entry from the old OS credential namespace or legacy token file only when the new namespace has no entry. It preserves URL/issuer bindings, client registration and tokens, verifies the copy, and never prints plaintext credentials. All old sources remain untouched. A configured `settings.oauthDir` uses a `fitch-mcp-adapter/` child directory during normal operation, keeping the original import files separate. Dry-run lists file actions without reading the keychain or writing anything; repeated migration skips already-copied destinations.
|
|
74
|
-
|
|
75
|
-
Normal startup never falls back to old adapter-owned defaults or the old credential namespace, so logout cannot resurrect old credentials. Migration copies an existing provider grant; it does not create an independent grant. To use both distributions with independent provider grants, sign in separately. Shared standard MCP files and explicit `configPath`/imports retain their behavior. See [OAuth storage](OAUTH.md#token-storage).
|
|
16
|
+
Restart Pi if it was open during installation. Load only one adapter per Pi host. For pre-v5 state, read the [upgrade guide](docs/migration.md) before you restart Pi.
|
|
76
17
|
|
|
77
|
-
|
|
18
|
+
For a Git install, run `pi install git:github.com/fitchmultz/pi-mcp-adapter`.
|
|
78
19
|
|
|
79
|
-
|
|
20
|
+
## First use
|
|
80
21
|
|
|
81
|
-
|
|
82
|
-
|---------------------|--------------|
|
|
83
|
-
| `.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. |
|
|
84
|
-
| 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. |
|
|
85
|
-
| 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. |
|
|
22
|
+
Enter `/mcp setup` to add a server or import configs from other hosts. Preview the changes before you save them. The adapter leaves external host files unchanged.
|
|
86
23
|
|
|
87
|
-
|
|
24
|
+
The adapter reads `.mcp.json` and `~/.config/mcp/mcp.json` automatically. Pi loads project configs only after project trust. See [config paths and precedence](docs/reference.md#quick-start).
|
|
88
25
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Preferred project config: `.mcp.json`
|
|
26
|
+
For Chrome DevTools, create `.mcp.json` in your project:
|
|
92
27
|
|
|
93
28
|
```json
|
|
94
29
|
{
|
|
95
30
|
"mcpServers": {
|
|
96
31
|
"chrome-devtools": {
|
|
97
32
|
"command": "npx",
|
|
98
|
-
"args": ["-y", "chrome-devtools-mcp
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Preferred user-global shared config: `~/.config/mcp/mcp.json`. Pi also reads the tool-agnostic global paths `~/.agents/mcp.json` and `~/.agents/mcp/mcp.json`.
|
|
105
|
-
|
|
106
|
-
Pi also reads Pi-owned override files for settings and host-specific compatibility:
|
|
107
|
-
|
|
108
|
-
- `<Pi agent dir>/fitch-mcp-adapter/mcp.json` — adapter global override (`~/.pi/agent/fitch-mcp-adapter/mcp.json` by default)
|
|
109
|
-
- `.pi/fitch-mcp-adapter/mcp.json` — adapter project override
|
|
110
|
-
|
|
111
|
-
Host-specific configs are detected and shown by `/mcp setup` and `fitch-mcp-adapter init`, but they are not loaded automatically. To explicitly opt in to host-config fallback discovery, set `settings.hostConfigDiscovery` to `"on"` or run `fitch-mcp-adapter init --discover-host-configs`. The default is `"off"`. Host configs are lower precedence than every shared and Pi-owned source, and `/mcp setup` continues to offer explicit import adoption. Discovery reports source paths, provenance, and same-name conflicts; it never writes to external host files or silently launches commands from them.
|
|
112
|
-
|
|
113
|
-
Precedence is:
|
|
114
|
-
|
|
115
|
-
1. `~/.config/mcp/mcp.json`
|
|
116
|
-
2. `~/.agents/mcp.json`
|
|
117
|
-
3. `~/.agents/mcp/mcp.json`
|
|
118
|
-
4. `<Pi agent dir>/fitch-mcp-adapter/mcp.json`
|
|
119
|
-
5. `.mcp.json`
|
|
120
|
-
6. `.pi/fitch-mcp-adapter/mcp.json`
|
|
121
|
-
|
|
122
|
-
If a higher-precedence config changes a stdio server's `command`, `args`, or `cwd`, it must explicitly supply any `env` values that the new process needs; the previous definition's `env` is not inherited. An OpenCode `environment` override also replaces the previous map rather than merging its keys.
|
|
123
|
-
|
|
124
|
-
Project layers and project-local host imports are read only after Pi marks the project trusted. Until then, the adapter uses global configuration only, does not start project-defined servers, and blocks project configuration panels and writes.
|
|
125
|
-
|
|
126
|
-
`/mcp disable <server>` and `/mcp enable <server>` persist only the `disabled` field in the project-local `.pi/fitch-mcp-adapter/mcp.json`, which is the highest-precedence Pi layer. Enabling removes the project flag when lower layers are enabled, or writes `false` when needed to override a disabled lower source. This applies even when the effective server came from a shared global/project file, an imported host config, or `configPath`; the source file is never rewritten and credentials are never copied. Run `/reload` after changing the flag so registered tool surfaces are refreshed. The manual equivalent is to add `{ "disabled": true }` to a server in any normal MCP config. Supplied in-memory `createMcpAdapter({ config })` configurations are isolated and do not read or write this project override; the commands are unavailable in that mode.
|
|
127
|
-
|
|
128
|
-
Servers are **lazy by default**. Cached tool descriptors can be searched without a live connection. To discover an uncached server and load the tools relevant to a task:
|
|
129
|
-
|
|
130
|
-
```js
|
|
131
|
-
mcp_search({ query: "take screenshot", server: "chrome-devtools" })
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
The returned exact tool references become available with their full argument schemas on the next model request. The agent then calls the chosen typed tool normally. Discovery does not run it.
|
|
135
|
-
|
|
136
|
-
To use resources, authentication, UI messages, saved-result readback, or fallback calls, first load the gateway with `mcp_search({ enable: ["gateway"] })`. No query is required and enabling alone performs no integration action or remote connection. Use `enable: ["script"]` for composition, or enable both together. Normal typed search still takes just one discovery call before a typed call.
|
|
137
|
-
|
|
138
|
-
If the host only permits `mcp` (and excludes `mcp_search`), it stays eager. Use explicit gateway actions instead:
|
|
139
|
-
|
|
140
|
-
```js
|
|
141
|
-
mcp({ action: "search", query: "take screenshot", server: "chrome-devtools" })
|
|
142
|
-
mcp({ action: "describe", tool: "chrome_devtools_take_screenshot" })
|
|
143
|
-
mcp({ action: "call", tool: "chrome_devtools_take_screenshot", args: { format: "png" } })
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Gateway search returns schemas without activating direct tools. All paths preserve the same authentication, approval, and call-capture behavior.
|
|
147
|
-
|
|
148
|
-
## Config
|
|
149
|
-
|
|
150
|
-
### File Layout
|
|
151
|
-
|
|
152
|
-
Use the shared MCP files when you want one setup to work across hosts, and Pi-owned files when you need Pi-specific overrides or settings.
|
|
153
|
-
|
|
154
|
-
| File | Purpose |
|
|
155
|
-
|------|---------|
|
|
156
|
-
| `~/.config/mcp/mcp.json` | User-global shared MCP config |
|
|
157
|
-
| `~/.agents/mcp.json` | User-global tool-agnostic MCP config |
|
|
158
|
-
| `~/.agents/mcp/mcp.json` | User-global tool-agnostic MCP config |
|
|
159
|
-
| `.mcp.json` | Project-local shared MCP config |
|
|
160
|
-
| `<Pi agent dir>/fitch-mcp-adapter/mcp.json` | Adapter global override and compatibility imports |
|
|
161
|
-
| `.pi/fitch-mcp-adapter/mcp.json` | Adapter project override |
|
|
162
|
-
|
|
163
|
-
The Pi agent dir defaults to `~/.pi/agent` and honors `PI_CODING_AGENT_DIR`. The owned `fitch-mcp-adapter/` root also contains `mcp-cache.json`, `mcp-onboarding.json`, `mcp-npx-cache.json`, and the `mcp-oauth/` legacy import directory.
|
|
164
|
-
|
|
165
|
-
For imported or shared global servers, Pi saves only `directTools` selections in its own config, without copying connection details or credentials from the source file.
|
|
166
|
-
|
|
167
|
-
### SDK configuration
|
|
168
|
-
|
|
169
|
-
Use `createMcpAdapter` when an SDK or server integration already owns its MCP configuration:
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
import { createMcpAdapter } from "@fitchmultz/pi-mcp-adapter";
|
|
173
|
-
|
|
174
|
-
const extension = createMcpAdapter({
|
|
175
|
-
config: {
|
|
176
|
-
mcpServers: {
|
|
177
|
-
docs: {
|
|
178
|
-
url: "https://mcp.example.com/mcp",
|
|
179
|
-
lifecycle: "eager",
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Register `extension` with the host SDK.
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
The package exports compiled ESM and declarations from `dist/`, so a standalone Node process can import it directly.
|
|
189
|
-
|
|
190
|
-
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.
|
|
191
|
-
|
|
192
|
-
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 are stored in the operating system credential store and keyed by the configured server name; URL binding prevents credentials from being accepted for a different server URL. `settings.oauthDir` selects the parent of a `fitch-mcp-adapter/` legacy plaintext import directory; `FITCH_MCP_OAUTH_DIR` explicitly overrides the final import path. Neither changes the OS credential namespace. CSRF state and PKCE verifiers are flow-local, so concurrent authorization flows do not share transient secrets.
|
|
193
|
-
|
|
194
|
-
Set `createMcpAdapter({ outputDirectory: "/workspace/internal/mcp-output" })` to keep oversized tool/resource text, raw MCP JSON, and final `mcp_script` output beneath a host-owned directory. The directory is created on the first spill; each file still uses a random subdirectory/name and mode `0600`. Relative paths resolve from the process working directory when written; prefer an absolute path for hosts that change directories. This runtime option works with either configuration mode and does not change live results, output limits, or cleanup. The host owns retention, access, and any redaction of saved copies; files may contain sensitive data. Omitting it keeps the system temp directory.
|
|
195
|
-
|
|
196
|
-
Set `createMcpAdapter({ defaultScriptTimeoutMs: null })` to remove the default overall `mcp_script` deadline for that host only. Omitting the option keeps 30,000ms; a numeric default must be an integer from 1 to 2,147,483,647 milliseconds. Each script's explicit `timeoutMs` takes precedence. This is a factory option, not an MCP config or global Pi setting. It does not change initialization waits, individual MCP request deadlines, Stop/AbortSignal, session-owner shutdown, or unfinished-call cleanup. Without an overall deadline, a script can run until it completes or is stopped. Script result `details.timeoutMs` is `null` when no overall deadline applies.
|
|
197
|
-
|
|
198
|
-
### Session configuration
|
|
199
|
-
|
|
200
|
-
Use `createMcpAdapter({ transformConfig: (config, ctx) => ... })` to add session-specific servers or settings in memory. The synchronous hook runs once per `session_start`, including reloads and new sessions, after normal config merging and project trust checks, before tool/metadata registration and initialization. It receives a fresh config clone and the current `ExtensionContext`; its result is cloned for session ownership. No config files are rewritten. Without an explicit `config`, normal status, setup, disable/enable, and auth panels remain available. With `config`, the hook transforms that isolated snapshot and its panel limitations still apply.
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
const extension = createMcpAdapter({
|
|
204
|
-
transformConfig(config, ctx) {
|
|
205
|
-
config.mcpServers.session = {
|
|
206
|
-
command: "my-mcp-server",
|
|
207
|
-
lifecycle: "lazy",
|
|
208
|
-
env: { SESSION_ID: ctx.sessionManager.getSessionId() },
|
|
209
|
-
};
|
|
210
|
-
config.settings = { ...config.settings, autoAuth: false };
|
|
211
|
-
return config;
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Hosts that share a server within a root session can supply their root-session ID in `env` instead. Load this factory instead of also loading the default adapter.
|
|
217
|
-
|
|
218
|
-
### Execution checkpoints and call capture
|
|
219
|
-
|
|
220
|
-
`createMcpAdapter({ beforeExecute: async (toolCallId, ctx, operation) => { ... } })` lets a host finish its workspace/native-session checkpoint before the next MCP action. For direct tools, actual `mcp` calls and every resolved `mcp_script` inner call, it runs after target resolution, connection, approval and UI preparation but **before the individual service deadline** and `onToolCall` capture. Any configured overall script deadline still applies. A second awaited script call reaches this boundary after the first call's output files have finished writing. The callback receives the native Pi call ID and that invocation's `ExtensionContext`, with its caller/owner/script cancellation signal in `ctx.signal`. Pass it to checkpoint I/O and throw if the checkpoint cannot complete. Rejection prevents that call: direct/proxy execution throws, while scripts keep their failed-call envelope so they can handle preparation failures. Existing two-argument callbacks remain supported.
|
|
221
|
-
|
|
222
|
-
The optional `McpOperationContext` (exported from the package and `/types`) carries the same `server`, `tool`, resolved `args`, optional `annotations`/`resourceUri`, and outer/inner IDs as the native before event, plus `annotationsTrusted`. Live connection annotations take precedence over frozen direct-tool hints, including when live hints are absent. `annotationsTrusted` reports only the existing server `retryOnTransportFailure: true` opt-in; it is not a new permission or configuration gate. No server configuration, headers, credentials, or SDK clients are exposed. Non-call proxy modes (including status, discovery, connect and auth actions) still invoke the callback without an operation; unresolved or denied tool calls do not dispatch.
|
|
223
|
-
|
|
224
|
-
A host can skip an expensive before-effect save for a native resource read (`operation.resourceUri`) or for `operation.annotationsTrusted && operation.annotations?.readOnlyHint === true`. Missing/false hints and `idempotentHint: true` alone are not read-only: an idempotent write still changes state. Keep unknown operations conservative and retain original intent/outcome capture. The adapter does not choose the host's save policy.
|
|
225
|
-
|
|
226
|
-
Supplying `beforeExecute` marks the adapter's tools `executionMode: "sequential"`, so Pi finishes earlier sibling tools before entering this boundary. Omitting it keeps normal scheduling. It does not serialize `Promise.all` inside a script, join detached writers, or implement checkpoint storage. The host still owns coherent capture and a final completion checkpoint, including final script output files. These callbacks do not change request or script timeout defaults.
|
|
227
|
-
|
|
228
|
-
`createMcpAdapter({ onToolCall: async (event) => { ... } })` supplies one optional, session-scoped callback for `mcp`, direct tools, and each resolved `mcp_script` call. Load this factory instead of also loading the default adapter. The callback is passed directly, so separate Pi/Jiti module instances do not need to share a singleton or event bus.
|
|
229
|
-
|
|
230
|
-
`McpToolCallEvent` (exported from `@fitchmultz/pi-mcp-adapter/types`) contains `server`, `tool`, the actual resolved `args`, optional SDK `annotations` and `resourceUri`, and a cancellation/deadline `signal`. `toolCallId` is the outer native Pi ID; scripts additionally carry their existing numeric `innerCallId`. These IDs are correlation only, not provider idempotency keys.
|
|
231
|
-
|
|
232
|
-
- `phase: "before"`: awaited after approval, before dispatch. Rejection prevents the call.
|
|
233
|
-
- `phase: "after"`: contains either the raw `result` (including MCP error results) or the thrown `error`. Awaited before delivering the outcome to Pi or dependent script work. It runs once after native recovery/retry settles, not once per HTTP subrequest. Output spill files are written **after** this callback; they are complete when the tool or awaited script call resolves, not at the after event.
|
|
234
|
-
- Capture failure returns `call_capture_failed` with the original event, excluding `signal`, in `details.recovery`. A completed result stays there; the adapter never repeats it. A script stops before consuming that result or starting dependent work. Cancellation remains `aborted`.
|
|
235
|
-
|
|
236
|
-
The two callbacks serve different boundaries: `beforeExecute` saves completed workspace writes; `onToolCall` captures the resolved operation and its raw outcome. The host owns `pi.appendEntry()` and the awaited checkpoint of that same native JSONL. Exclude `signal`, serialize thrown errors explicitly, redact sensitive checkpoint bytes, and honor the signal and session/attempt fence. `pi.appendEntry()` alone only persists locally; `pi.events.emit()` is not an awaited barrier. Custom entries do not enter model context automatically: the host must provide truthful recovered results or readback instructions when resuming. The adapter stores no separate transcript or receipts, does not replay scripts, and does not invent provider operation keys.
|
|
237
|
-
|
|
238
|
-
### Runtime shutdown and replacement
|
|
239
|
-
|
|
240
|
-
Pi 1.0 owns session replacement and emits `session_shutdown`. The adapter aborts outgoing activity before awaiting cleanup, closes owned connections, health watchers, OAuth callbacks and UI servers, and reports persistence/cleanup failures. Actual sampling/elicitation and native send/auth tails remain owned through settlement, even when a public request has already been cancelled. Unfinished callbacks prevent idle connection closure; final shutdown joins transport work. Pending `beforeExecute`/`onToolCall` promises prevent a clean-shutdown success claim. No detached work is declared finished merely because the agent settled.
|
|
241
|
-
|
|
242
|
-
The retired fork `session_checkpoint`/`acquireCheckpoint` APIs are no longer used. Host-supplied `beforeExecute` and raw `onToolCall` capture remain supported; the host still owns durable effect receipts and coherent workspace/session capture. No callback/process-memory recovery or automatic operation replay is promised.
|
|
243
|
-
|
|
244
|
-
### Runtime status snapshots
|
|
245
|
-
|
|
246
|
-
Extensions can subscribe to the adapter's versioned shared event-bus channel instead of parsing `/mcp` or `mcp({ action: "status" })` output:
|
|
247
|
-
|
|
248
|
-
```ts
|
|
249
|
-
import { MCP_STATUS_EVENT, type McpStatusSnapshot } from "@fitchmultz/pi-mcp-adapter";
|
|
250
|
-
|
|
251
|
-
pi.events.on(MCP_STATUS_EVENT, (snapshot) => {
|
|
252
|
-
const status = snapshot as McpStatusSnapshot;
|
|
253
|
-
// status.servers contains connected, cached, failed, needs-auth,
|
|
254
|
-
// not-connected, or disabled entries.
|
|
255
|
-
});
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
`MCP_STATUS_EVENT` is `fitch-mcp-adapter/status/v1`. The snapshot is read-only machine-readable data with copied per-server entries. It includes `totalTools`, `totalResources`, `connectedCount`, and `disabledCount`; each server includes `name`, `status`, `toolCount`, and `disabled`, with `resourceCount` when known, `catalogKnown` indicating whether discovery has completed, and `failedAgoSeconds` only for an active failure. An unknown catalog is not a confirmed zero-tool server. Reading status never connects a lazy server, starts authentication, or exposes SDK clients, transports, credentials, or server definitions. An initial snapshot is emitted after initialization, updates are emitted for status and metadata changes, and an empty snapshot is emitted when the session shuts down.
|
|
259
|
-
|
|
260
|
-
In the configuration examples below, `30000` is illustrative only. If `requestTimeoutMs` is omitted or set to `<= 0`, the MCP SDK default timeout is used.
|
|
261
|
-
|
|
262
|
-
### Transport compatibility
|
|
263
|
-
|
|
264
|
-
The adapter uses official split MCP SDK 2.2.0. HTTP connections automatically negotiate protocol `2026-07-28` and fall back to older revisions using the SDK's native rules. The SDK generates protocol metadata, method/name headers, and schema-driven `Mcp-Param-*` headers. Modern HTTP supports streamed POST responses without opening a legacy standalone GET stream. Deprecated SSE is tried only when the Streamable HTTP handshake rejects the endpoint with HTTP 404, 405, 406, or 415—not after a timeout, network failure, or authentication failure.
|
|
265
|
-
|
|
266
|
-
Native catalog discovery follows changing pages even when their opaque cursor repeats. Catalogs exceeding 1,024 pages fail explicitly instead of publishing a partial list or walking indefinitely; this also bounds automatic catalog refreshes.
|
|
267
|
-
|
|
268
|
-
For known legacy servers, set `protocolVersion: "legacy"`. This is also the workaround for servers that answer `server/discover` with an HTTP 200 JSON-RPC error whose `id` is `null`: SDK 2.0.0 cannot negotiate that response. The adapter reports the native failure rather than inventing authentication guidance or overriding the SDK's fallback classifier. Stdio and Unix sockets remain legacy by default. Explicit `"auto"` on native stdio uses the SDK's disposable probe process before starting the session process.
|
|
269
|
-
|
|
270
|
-
```json
|
|
271
|
-
{
|
|
272
|
-
"mcpServers": {
|
|
273
|
-
"modern": { "url": "https://mcp.example.com/mcp" },
|
|
274
|
-
"older": { "url": "https://legacy.example.com/mcp", "protocolVersion": "legacy" },
|
|
275
|
-
"read-only": { "url": "https://reports.example.com/mcp", "retryOnTransportFailure": true }
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
`retryOnTransportFailure` is off by default. Enabling it trusts the configured server's explicit `readOnlyHint: true` or `idempotentHint: true` tool annotations for at most one new `callTool` request on the same modern HTTP client. Annotations are retained in live/cached metadata and describe output; the live catalog takes precedence over frozen direct-tool metadata. Eligible failures are a rejected tool POST fetch before response headers arrive, an HTTP 5xx tool response without a JSON-RPC error, or an SSE response body disconnecting before its JSON-RPC response arrives. The first attempt may already have run. Fresh SDK request IDs are not idempotency keys.
|
|
281
|
-
|
|
282
|
-
Unknown or non-idempotent tools remain callable but are never automatically redispatched after those failures. An unresolved transport failure, interrupted JSON response body, closed connection, or cancellation/deadline while a dispatched tool request awaits its response returns internal `ambiguous_outcome` details with server/tool/call identity and readback instructions. Scripts retain these outcomes in their `calls` trace even when stopped by an overall timeout, cancellation, or early return. Use the original saved arguments and the provider's supported operation/resource identity to read the prior result, then continue the remaining work; do not blindly repeat a write or rerun its whole script. Missing annotations are not permission denial. Confirmed pre-dispatch legacy-session and authentication recovery remain available.
|
|
283
|
-
|
|
284
|
-
A broken SSE body fails only the affected request, even when retries are disabled. Completed responses and later stream notifications remain intact. Native SSE resumption stays SDK-owned: a server-provided event ID lets the SDK resume the existing stream before any adapter retry.
|
|
285
|
-
|
|
286
|
-
This transport-failure option does not retry OAuth failures, JSON-RPC errors, tool error results, invalid responses, closed connections, cancellation, or expired deadlines. Permission recovery uses the separate existing `autoAuth` gate, not this option. JSON response-body failures, clean SSE endings without a response, and streams that merely stop making progress are not replayed. The original tool deadline covers SDK subrequests and retries, and modern retry never stacks with legacy expired-session recovery. Native header-schema refresh and multi-round-trip input handling remain SDK-owned.
|
|
287
|
-
|
|
288
|
-
### Server Options
|
|
289
|
-
|
|
290
|
-
```json
|
|
291
|
-
{
|
|
292
|
-
"mcpServers": {
|
|
293
|
-
"my-server": {
|
|
294
|
-
"command": "npx",
|
|
295
|
-
"args": ["-y", "some-mcp-server"],
|
|
296
|
-
"lifecycle": "lazy",
|
|
297
|
-
"idleTimeout": 10,
|
|
298
|
-
"requestTimeoutMs": 30000
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
| Field | Description |
|
|
305
|
-
|-------|-------------|
|
|
306
|
-
| `command` | Executable for stdio transport; mutually exclusive with `url` and `socket` |
|
|
307
|
-
| `args` | Command arguments |
|
|
308
|
-
| `socket` | Explicit `rmcp-mux` Unix-domain socket path; supports `${VAR}`, `$env:VAR`, and `~` expansion and is mutually exclusive with `command` and `url` |
|
|
309
|
-
| `env` | Environment variables; supports `${VAR}` and `$env:VAR` interpolation. A value beginning with `!` runs a command when the stdio server connects; use `!!` for a literal leading `!`. |
|
|
310
|
-
| `cwd` | Working directory; supports `${VAR}`, `$env:VAR`, and `~` expansion |
|
|
311
|
-
| `protocolVersion` | `"auto"` or `"legacy"`. HTTP defaults to `"auto"`; stdio and Unix sockets default to `"legacy"`. |
|
|
312
|
-
| `retryOnTransportFailure` | Trust explicit read-only/idempotent annotations for one modern transport retry (default: `false`); see [Transport compatibility](#transport-compatibility) before enabling. |
|
|
313
|
-
| `url` | HTTP endpoint (StreamableHTTP with SSE fallback); supports raw `${VAR}` and `$env:VAR` interpolation, and missing URL variables fail before any request is sent |
|
|
314
|
-
| `headers` | HTTP headers; supports `${VAR}` and `$env:VAR` interpolation. A value beginning with `!` runs a command when the HTTP server connects or OAuth authenticates; use `!!` for a literal leading `!`. |
|
|
315
|
-
| `auth` | `"bearer"` or `"oauth"` |
|
|
316
|
-
| `oauth.grantType` | `"authorization_code"` (default) or `"client_credentials"` for non-interactive machine auth |
|
|
317
|
-
| `oauth.clientId` | Pre-registered OAuth client ID; takes priority over stored clients and automatic registration |
|
|
318
|
-
| `oauth.clientSecret` | OAuth client secret for confidential clients; a value beginning with `!` runs a command when OAuth authenticates, while `!!` escapes a literal leading `!` |
|
|
319
|
-
| `oauth.privateKeyJwt` | Native private-key authentication: `privateKey` (PKCS#8 PEM, JWK object/JSON, environment or `!command` source), `algorithm`, optional `audience`, `lifetimeSeconds`, `claims`; requires a configured client ID or custom metadata document, not `clientSecret` |
|
|
320
|
-
| `oauth.crossAppAccess` | Native enterprise OIDC exchange: `idpUrl`, IdP `clientId`, `idToken`, optional IdP `clientSecret`; all support lazy environment/`!command` sources. Omit `grantType`. |
|
|
321
|
-
| `oauth.scope` | Requested OAuth scopes |
|
|
322
|
-
| `oauth.redirectUri` | Exact localhost redirect URI for browser OAuth, including port and path, for providers that pre-register callbacks |
|
|
323
|
-
| `oauth.clientName` | Client display name advertised during dynamic registration |
|
|
324
|
-
| `oauth.clientUri` | Client homepage URI advertised during dynamic registration |
|
|
325
|
-
| `oauth.clientMetadataUrl` | Custom HTTPS Client ID Metadata Document URL, or `false` to disable automatic CIMD for new registrations; supports environment interpolation |
|
|
326
|
-
| `oauth.skipIssuerMetadataValidation` | Skip only the authorization-server metadata issuer check for a known incompatible provider (default: `false`). Stored issuer and callback checks remain enforced. |
|
|
327
|
-
| `bearerToken` / `bearerTokenEnv` | Token or env var name; `bearerToken` supports `${VAR}` and `$env:VAR` interpolation. A leading `!` in `bearerToken` runs a command when the HTTP server connects; use `!!` for a literal leading `!`. |
|
|
328
|
-
| `lifecycle` | `"lazy"` (default), `"eager"`, `"keep-alive"`, or `"lazy-keep-alive"` |
|
|
329
|
-
| `idleTimeout` | Minutes before idle disconnect (overrides global) |
|
|
330
|
-
| `requestTimeoutMs` | Request timeout in milliseconds for live MCP calls (overrides global; if omitted or `<= 0`, the MCP SDK default timeout is used) |
|
|
331
|
-
| `exposeResources` | Make MCP resources available through resource listing/reading (default: true) |
|
|
332
|
-
| `directTools` | `true`, `string[]`, or `false` — pin real tools into the initial active tool set |
|
|
333
|
-
| `toolPrefix` | Override global `settings.toolPrefix` for this server (`"server"`, `"short"`, `"none"`, or `"mcp"`) |
|
|
334
|
-
| `includeTools` | `string[]` of tool names or glob patterns to expose (matches original names like `get_screenshot`, legacy resource filter aliases like `read_figjam`, and prefixed names like `figma_get_screenshot`) |
|
|
335
|
-
| `excludeTools` | `string[]` of tool names or glob patterns to hide (applied after `includeTools`) |
|
|
336
|
-
| `debug` | Show server stderr (default: false) |
|
|
337
|
-
| `trace` | Enable metadata-only JSONL protocol tracing for this server; payloads, prompts, tool arguments/results, authorization data, and URLs are never persisted |
|
|
338
|
-
| `disabled` | Keep the server visible in config and status, but prevent connections, authentication, tools, and resource calls (only literal `true` disables it) |
|
|
339
|
-
|
|
340
|
-
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.
|
|
341
|
-
|
|
342
|
-
Secret values in `headers`, `bearerToken`, `oauth.clientSecret`, `oauth.privateKeyJwt.privateKey`, `oauth.crossAppAccess` fields, and stdio `env` may use a leading `!command` to obtain their value at connection or authentication time. The command runs with stdin and stderr suppressed, stdout is limited to 1 MiB and trimmed, and it must finish within 10 seconds with non-empty output; failures stop the connection or authentication flow. Commands are not run during OAuth discovery or while reading, merging, previewing, hashing, or rendering configuration. Use `!!` to escape a literal leading `!`; ordinary and escaped values retain environment interpolation.
|
|
343
|
-
|
|
344
|
-
### OAuth client registration
|
|
345
|
-
|
|
346
|
-
Eligible browser clients automatically use the [Fitch MCP Adapter client metadata document](https://fitchmultz.github.io/pi-mcp-adapter/client-metadata.json). The SDK keeps configured `clientId` and usable stored registrations first, then uses the document when the authorization server advertises `client_id_metadata_document_supported: true`. Otherwise it uses dynamic client registration (DCR), if available. An authorization server rejecting or failing to fetch a document does **not** guarantee a fallback to DCR.
|
|
347
|
-
|
|
348
|
-
The shared public identity covers `/callback` on HTTP `localhost`, `127.0.0.1`, or `[::1]`, with a variable loopback port. Normal callbacks still use an OS-assigned port. A different `clientName`, `clientUri`, callback path or query keeps DCR; explicitly matching identity values remain eligible. Set `oauth.clientMetadataUrl` to your own document URL for a custom identity/callback, or `false` to opt out. Documents cannot use shared secrets; the shared browser identity is never used for `client_credentials`, cross-app or private-key authentication.
|
|
349
|
-
|
|
350
|
-
Changing this option does not replace a usable stored login. Saved CIMD registrations allow only a loopback-port change during browser auth; host, scheme, path, query and issuer bindings remain enforced. To deliberately register again, use `/mcp logout <server>`. An authorization-server issuer change still requires clearing credentials; it is not silently migrated. See [OAuth configuration](OAUTH.md#client-registration-order) for details.
|
|
351
|
-
|
|
352
|
-
### Private-key OAuth
|
|
353
|
-
|
|
354
|
-
Use `oauth.privateKeyJwt` with an existing registered client ID, or a custom metadata document containing the matching public verification key:
|
|
355
|
-
|
|
356
|
-
```json
|
|
357
|
-
{
|
|
358
|
-
"url": "https://api.example.com/mcp",
|
|
359
|
-
"oauth": {
|
|
360
|
-
"grantType": "client_credentials",
|
|
361
|
-
"clientId": "registered-service-client",
|
|
362
|
-
"privateKeyJwt": {
|
|
363
|
-
"privateKey": "${MCP_PRIVATE_KEY}",
|
|
364
|
-
"algorithm": "ES256"
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
The SDK signs a fresh assertion for each token request. Keys are resolved at authentication time, never cached or added to the credential store. PEM strings must be PKCS#8 with an RS/PS/ES algorithm; EdDSA / Ed25519 use a JWK. `!command` may return PEM or JWK JSON. The same hook authenticates browser code and refresh requests when `grantType` is omitted. No keys are generated, registered or hosted for you. See [private-key options and identity rules](OAUTH.md#private-key-jwt) for details.
|
|
371
|
-
|
|
372
|
-
### Enterprise cross-app authorization
|
|
373
|
-
|
|
374
|
-
Use an existing enterprise OIDC ID token to obtain an MCP access token without opening an MCP browser flow:
|
|
375
|
-
|
|
376
|
-
```json
|
|
377
|
-
{
|
|
378
|
-
"url": "https://api.example.com/mcp",
|
|
379
|
-
"auth": "oauth",
|
|
380
|
-
"oauth": {
|
|
381
|
-
"clientId": "registered-mcp-client",
|
|
382
|
-
"clientSecret": "${MCP_CLIENT_SECRET}",
|
|
383
|
-
"crossAppAccess": {
|
|
384
|
-
"idpUrl": "https://idp.example.com",
|
|
385
|
-
"clientId": "registered-idp-client",
|
|
386
|
-
"idToken": "!your-id-token-command",
|
|
387
|
-
"clientSecret": "${IDP_CLIENT_SECRET}"
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
The nested credentials belong to the IdP, not the MCP authorization server. Omit `grantType`: this option selects the JWT-bearer grant. The SDK discovers both servers, exchanges the ID token for an ID-JAG (JWT authorization grant), then exchanges that grant for the MCP access token using the outer client identity. Public custom metadata documents and `privateKeyJwt` work instead of the outer shared secret. The shared browser identity is not eligible.
|
|
394
|
-
|
|
395
|
-
Initial enterprise sign-in, ID-token renewal, IdP client registration and keys remain user-managed. The token source is re-read when a new grant is needed; no IdP tokens, secrets or grants are added to storage. The IdP supports public or `client_secret_post` authentication, not IdP private-key JWT, SAML or a built-in login/refresh flow. Headless proxy/direct tools and manual auth use the same noninteractive path. See [cross-app configuration and limits](OAUTH.md#enterprise-cross-app-authorization).
|
|
396
|
-
|
|
397
|
-
### Shared MCP processes with rmcp-mux
|
|
398
|
-
|
|
399
|
-
To share one stdio MCP server across Pi sessions, run it under [`rmcp-mux`](https://github.com/VetCoders/rmcp-mux) and point each session at the service socket:
|
|
400
|
-
|
|
401
|
-
```json
|
|
402
|
-
{
|
|
403
|
-
"mcpServers": {
|
|
404
|
-
"memory": {
|
|
405
|
-
"socket": "~/.rmcp-servers/rmcp-mux/sockets/memory.sock"
|
|
33
|
+
"args": ["-y", "chrome-devtools-mcp"]
|
|
406
34
|
}
|
|
407
35
|
}
|
|
408
36
|
}
|
|
409
37
|
```
|
|
410
38
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
### Remote/headless OAuth
|
|
414
|
-
|
|
415
|
-
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.
|
|
416
|
-
|
|
417
|
-
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.
|
|
418
|
-
|
|
419
|
-
If the gateway is inactive, first call `mcp_search({ enable: ["gateway"] })`.
|
|
420
|
-
|
|
421
|
-
```js
|
|
422
|
-
mcp({ action: "auth-start", server: "linear-server" })
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
Open the returned authorization URL in an authenticated browser. This can be a headless browser that can reach Pi's localhost callback; the adapter does not open a browser for `auth-start`. After approval reaches the callback, complete the flow in the same Pi session without copying the callback URL or code:
|
|
426
|
-
|
|
427
|
-
```js
|
|
428
|
-
mcp({ action: "auth-complete", server: "linear-server" })
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
The validated callback stays in memory until explicit completion or the pending flow's five-minute timeout. Calling `auth-complete` before the callback arrives gives guidance and leaves the flow available. A denied callback reports failure without exchanging a code.
|
|
432
|
-
|
|
433
|
-
If the browser cannot reach Pi's callback (for example, Pi runs on a remote server), copy the full redirected localhost URL from the address bar even if the page fails to load:
|
|
434
|
-
|
|
435
|
-
```js
|
|
436
|
-
mcp({
|
|
437
|
-
action: "auth-complete",
|
|
438
|
-
server: "linear-server",
|
|
439
|
-
args: { redirectUrl: "http://localhost:19876/callback?code=...&state=..." }
|
|
440
|
-
})
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
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. Callback state and issuer are checked before provider error text is displayed, as well as before code exchange.
|
|
444
|
-
|
|
445
|
-
New scope requirements are retained within the current runtime for the next permitted OAuth flow. URL-only servers also activate OAuth when their first protected catalog or tool is reached; valid saved tokens do not require another sign-in. Token scopes remain exactly what the authorization server issued. See [OAuth permission recovery](OAUTH.md#newly-required-permissions) for consent, cancellation, and transport limits.
|
|
446
|
-
|
|
447
|
-
Persistent OAuth is unsupported out of the box on Android/Termux because `@napi-rs/keyring` 1.3.0 ships no Android native binding. Use a supported platform with an OS credential store for OAuth; there is no plaintext fallback. Unauthenticated, header-token, and stdio servers remain available. See [Android/Termux OAuth limits](OAUTH.md#android--termux).
|
|
448
|
-
|
|
449
|
-
### Lifecycle Modes
|
|
450
|
-
|
|
451
|
-
- **`lazy`** (default) — Connect on selected discovery or first call, then disconnect after idle timeout. Cached metadata keeps search/list working without connections. Configured pins can intentionally discover their server at startup.
|
|
452
|
-
- **`eager`** — Connect at startup but don't auto-reconnect if the connection drops. No idle timeout by default (set `idleTimeout` explicitly to enable).
|
|
453
|
-
- **`keep-alive`** — Connect at startup. Auto-reconnect via health checks. No idle timeout. Use for servers you always need available.
|
|
454
|
-
- **`lazy-keep-alive`** — Connect on selected discovery or first call (like `lazy`); configured pins can discover it at startup. Once spawned, never idle-shut down and auto-reconnect via health checks if the process dies (like `keep-alive`). Use for servers that are expensive to start but should stay resident after their first use.
|
|
455
|
-
|
|
456
|
-
### Settings
|
|
457
|
-
|
|
458
|
-
```json
|
|
459
|
-
{
|
|
460
|
-
"settings": {
|
|
461
|
-
"toolPrefix": "server",
|
|
462
|
-
"idleTimeout": 10,
|
|
463
|
-
"requestTimeoutMs": 30000,
|
|
464
|
-
"showStatusIcon": true,
|
|
465
|
-
"mcpFooterStatus": "compact",
|
|
466
|
-
"hostConfigDiscovery": "off",
|
|
467
|
-
"approveTools": ["github_delete_*", "notion_update_*"],
|
|
468
|
-
"oauthDir": ".pi/mcp-oauth",
|
|
469
|
-
"trace": {
|
|
470
|
-
"enabled": true,
|
|
471
|
-
"file": ".pi/fitch-mcp-adapter/traces/mcp.jsonl",
|
|
472
|
-
"maxBytes": 262144,
|
|
473
|
-
"maxEvents": 10000
|
|
474
|
-
}
|
|
475
|
-
},
|
|
476
|
-
"mcpServers": { }
|
|
477
|
-
}
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
| Setting | Description |
|
|
481
|
-
|---------|-------------|
|
|
482
|
-
| `toolPrefix` | `"server"` (default), `"short"` (strips `-mcp` suffix), `"none"`, or `"mcp"` (prefixes with `mcp__`, using server-mode normalization). Per-server `toolPrefix` overrides this for that server. |
|
|
483
|
-
| `idleTimeout` | Global idle timeout in minutes (default: 10, 0 to disable) |
|
|
484
|
-
| `requestTimeoutMs` | Global request timeout in milliseconds for live MCP calls (if omitted or `<= 0`, the MCP SDK default timeout is used) |
|
|
485
|
-
| `showStatusIcon` | Show the plug icon in MCP status and connection text (default: `true`). Set to `false` for plain `MCP: ...` text. |
|
|
486
|
-
| `mcpFooterStatus` | MCP footer verbosity: `"compact"` (default) for `MCP connected/enabled`, `"full"` for enabled/connected/disabled detail, or `"off"` to clear the persistent footer status. `/mcp status` remains available. |
|
|
487
|
-
| `hostConfigDiscovery` | Host-specific config policy: `"off"` (default) or `"on"` (explicitly load detected host configs as the lowest-precedence fallback) |
|
|
488
|
-
| `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. |
|
|
489
|
-
| `oauthDir` | Parent directory for legacy OAuth imports. Runtime uses its `fitch-mcp-adapter/` child; explicit v4 migration reads the original directory. Relative paths resolve from the active project cwd. `FITCH_MCP_OAUTH_DIR` overrides the final runtime import path; the old `MCP_OAUTH_DIR` is read only by explicit migration. Persistent credentials live in the OS credential store. |
|
|
490
|
-
| `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. |
|
|
491
|
-
| `directTools` | Global default for all servers (default: false). Per-server overrides this. |
|
|
492
|
-
| `freezeDirectTools` | Keep direct-tool registration stable after the initial sync so automatic reconnects and list-change notifications do not rebuild the system prompt. Use `mcp({ action: "connect", server: "server" })` or `/mcp reconnect <server>` to refresh deliberately. Default: false. |
|
|
493
|
-
| `scriptMode` | Make the MCP-only `mcp_script` plain-JavaScript tool loadable (default: true). Set to `false` to hide it. |
|
|
494
|
-
| `disableProxyTool` | Hide `mcp` once configured pinned tools are available. Legacy resource pins keep it available for resource access. |
|
|
495
|
-
| `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
|
|
496
|
-
| `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
|
|
497
|
-
| `samplingAutoApprove` | Skip sampling confirmation prompts. Required for sampling in non-UI sessions (default: false). |
|
|
498
|
-
| `elicitation` | Allow MCP servers to request user input through Pi dialogs (default: true when Pi UI is available). |
|
|
499
|
-
| `outputGuard` | Guard oversized MCP output: `true` (default), `false`, or `{ maxBytes, maxLines, detailsMaxBytes }`. See [Output Guard](#output-guard). |
|
|
500
|
-
| `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/fitch-mcp-adapter/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. |
|
|
501
|
-
|
|
502
|
-
Per-server `idleTimeout`, `requestTimeoutMs`, and `approveTools` override the global settings. `debug` remains stderr display and is unrelated to protocol tracing.
|
|
503
|
-
|
|
504
|
-
### Tool Approval
|
|
505
|
-
|
|
506
|
-
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.
|
|
507
|
-
|
|
508
|
-
```json
|
|
509
|
-
{
|
|
510
|
-
"settings": {
|
|
511
|
-
"approveTools": ["github_delete_*", "notion_update_*"]
|
|
512
|
-
},
|
|
513
|
-
"mcpServers": {
|
|
514
|
-
"github": { "approveTools": ["delete_*", "merge_pull_request"] },
|
|
515
|
-
"docs": { "approveTools": false }
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
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.
|
|
521
|
-
|
|
522
|
-
### Output Guard
|
|
523
|
-
|
|
524
|
-
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:
|
|
525
|
-
|
|
526
|
-
- 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.
|
|
527
|
-
- **Image content blocks pass through unchanged** — only text output is guarded. Images are delivered to the provider as native image content.
|
|
528
|
-
- In gateway and direct modes, `details.mcpResult` is kept raw when its JSON is **≤ 16 KiB**; larger results use a compact summary and retain the full raw JSON in a file. Scripts receive the raw result before model-facing rendering, so they can reduce it without creating an unused text preview.
|
|
529
|
-
- Structured data, media, resources, extra protocol fields, and oversized results include a model-visible **result reference** to the saved raw result. Tiny plain-text results remain inline without an artifact. `structuredContent` is shown even when the server also returns content blocks.
|
|
530
|
-
|
|
531
|
-
Tune the limits with the object form:
|
|
532
|
-
|
|
533
|
-
```json
|
|
534
|
-
{
|
|
535
|
-
"settings": {
|
|
536
|
-
"outputGuard": { "maxBytes": 51200, "maxLines": 2000, "detailsMaxBytes": 16384 }
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
Set `"outputGuard": false` — or the env kill switch `MCP_OUTPUT_GUARD=0` — to disable the guard and restore raw output behavior. Recovery notices remain visible even when a configured cap is too small to fit them. Saved files are created with mode `0600` under the system temp directory (or the SDK host's `outputDirectory`) and are not cleaned up automatically; note that spilled MCP output may contain sensitive data.
|
|
542
|
-
|
|
543
|
-
Read a retained result without repeating the MCP operation. Load the gateway first if inactive with `mcp_search({ enable: ["gateway"] })`:
|
|
544
|
-
|
|
545
|
-
```js
|
|
546
|
-
mcp({ action: "read-result", ref: "/returned/result/path", path: "/structuredContent/rows", fields: ["id", "title"], offset: 0, limit: 12000 })
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
`path` is an RFC 6901 JSON Pointer; `fields` keeps immediate keys on the selected object or each object in an array. Selection happens before paging. `offset` and `limit` count **characters in the selected, pretty-printed JSON**, starting at zero—not rows, lines, or bytes. Readback defaults to 12,000 characters and remains bounded by the output byte/line caps. Continue with `details.nextOffset` until it is `null`. Readback requires an existing adapter output artifact in this host's output directory; it never calls the server again. The same operation is available as `tools.readResult` in scripts.
|
|
550
|
-
|
|
551
|
-
### MCP Scripting
|
|
552
|
-
|
|
553
|
-
For multi-call MCP work, write ordinary JavaScript: discover, inspect, call, loop, filter, chain, or fan out, then return one result. Load the script tool with `mcp_search({ enable: ["script"] })`, then run that code with `mcp_script`. Use `mcp_search` for typed discovery, a loaded typed tool for a single call, and `mcp` for explicit gateway actions. Set `settings.scriptMode` to `false` to hide the scripting tool.
|
|
554
|
-
|
|
555
|
-
The bundled `mcp-scripting` skill is a separate Pi package resource. To hide that skill while keeping the adapter extension installed, replace the package entry in Pi settings with the object form and disable package skills:
|
|
556
|
-
|
|
557
|
-
```json
|
|
558
|
-
{
|
|
559
|
-
"packages": [
|
|
560
|
-
{ "source": "npm:@fitchmultz/pi-mcp-adapter", "skills": [] }
|
|
561
|
-
]
|
|
562
|
-
}
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
Preserve any version pin in `source` if your existing package entry has one. You can also disable package resources through `pi config`.
|
|
566
|
-
|
|
567
|
-
First inspect candidate schemas. This script discovers and describes without executing a search hit:
|
|
568
|
-
|
|
569
|
-
```js
|
|
570
|
-
const found = await tools.search({ query: "search issues", server: "github" });
|
|
571
|
-
if (found.error) return found;
|
|
572
|
-
emit(found.coverage);
|
|
573
|
-
for (const item of found.items) {
|
|
574
|
-
emit(await tools.describe({ path: item.path, server: item.server }));
|
|
575
|
-
}
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
After choosing a tool and checking its schema, pass ordinary JavaScript as `mcp_script`'s `code` argument:
|
|
579
|
-
|
|
580
|
-
```js
|
|
581
|
-
const results = [];
|
|
582
|
-
for (const query of ["is:open label:bug", "is:open label:docs"]) {
|
|
583
|
-
const result = await tools.call("github_search_issues", { query });
|
|
584
|
-
if (!result.ok) return result;
|
|
585
|
-
results.push({ query, data: result.data });
|
|
586
|
-
}
|
|
587
|
-
return results;
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
The script API is:
|
|
591
|
-
|
|
592
|
-
| Method | Result |
|
|
593
|
-
|--------|--------|
|
|
594
|
-
| `tools.search({ query, server?, limit?, offset? })` | `{ items, total, hasMore, nextOffset, coverage }`; items include `path`, `name`, and `server`; discovery errors include `error` |
|
|
595
|
-
| `tools.describe({ path, server? })` | Complete descriptor with `path`, `server`, `name`, and the server's `inputSchema`, `outputSchema`, `title`, annotations, `_meta`, and other fields when present; failures contain `error` |
|
|
596
|
-
| `tools.call(path, args, server?)` or `tools.exact_flat_name(args)` | `{ ok: true, data, resultRef? }` or `{ ok: false, error: { code, message, ... }, data?, resultRef? }` |
|
|
597
|
-
| `tools.resources({ server, limit?, offset? })` | `{ mode: "resources", server, items, total, hasMore, nextOffset }`; failures contain `error` |
|
|
598
|
-
| `tools.readResource({ server, uri })` | The call envelope above, with raw resource data in `data.contents` |
|
|
599
|
-
| `tools.readResult({ ref, path?, fields?, offset?, limit? })` | `{ content, details }`; readback text and `details.nextOffset`, or `details.error` on failure |
|
|
600
|
-
| `emit(value)` / `console.log(value)` | Captured output before the final return value |
|
|
601
|
-
|
|
602
|
-
For an ambiguous flat name, call `tools.call(descriptor.name, args, descriptor.server)` using the original name and server returned by describe.
|
|
603
|
-
|
|
604
|
-
Search/resource pages default to 12 items, maximum 100. `coverage` contains `complete`, `knownServers`, and `unknownServers`. Selecting an uncached server discovers it; global search does not connect every lazy server. Script discovery does not activate typed tools. Descriptions retain JSON Schema rather than a lossy TypeScript projection.
|
|
605
|
-
|
|
606
|
-
`data` is the raw MCP result: tool calls usually return `{ content, structuredContent?, isError?, ... }`; resource reads return `{ contents, ... }`. Error results retain their raw data when available. Handle `ok: false`; ordinary call failures do not automatically stop the script. Capture failures stop dependent work, and ambiguous outcomes require provider readback instead of blindly repeating a call. Result details include a concise `calls` trace and saved result references. Reduce raw results before emitting them; the final output uses the normal output guard.
|
|
607
|
-
|
|
608
|
-
Use JavaScript loops and Promise utilities; fluent helpers such as `tools.find(...).one()`, `tools.parallel(...)`, and `tools.retry(...)` are not provided. Await every call before returning. The default timeout is 30 seconds unless the SDK host overrides it with `defaultScriptTimeoutMs`; an explicit per-script `timeoutMs` wins. Each script runs in a local worker that is terminated on timeout or cancellation, including infinite loops.
|
|
609
|
-
|
|
610
|
-
For a tool-restricted subagent, a host can allow only `mcp_script`; include the relevant exact tool names or let the script use its discovery methods. A gateway-only host can use `mcp({ action: "search", query: "..." })` without activating tools outside its allowlist. Authentication, approvals, filters, and cancellation apply to every call.
|
|
611
|
-
|
|
612
|
-
`mcp_script` is a trusted agent-authored MCP scripting layer, not an isolation boundary. The worker exposes no Node, filesystem, or network globals such as `process`, `Buffer`, or `fetch`. Run Pi in an isolated environment if you need isolation. This is local orchestration, not provider-native programmatic tool calling; it does not claim Codex native PTC support. Pi's general code-mode skill is separate and batches general Pi tools.
|
|
613
|
-
|
|
614
|
-
See the bundled `mcp-scripting` skill for the workflow and exact return shapes.
|
|
615
|
-
|
|
616
|
-
### MCP Resources
|
|
617
|
-
|
|
618
|
-
Resources are catalog entries addressed by URI, separate from tools. Load the gateway first if inactive with `mcp_search({ enable: ["gateway"] })`:
|
|
619
|
-
|
|
620
|
-
```js
|
|
621
|
-
mcp({ action: "resources", server: "docs", limit: 12, offset: 0 })
|
|
622
|
-
mcp({ action: "read-resource", server: "docs", uri: "docs://guide" })
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
List first, then use the exact URI. Listings preserve the server's resource descriptor fields, including name, title, MIME type, icons, and `_meta` when supplied. `exposeResources`, include/exclude filters, disabled servers, authentication, and approval policy remain binding. Existing `read_<resource>` filter names still select resources, but these compatibility aliases are not typed functions and cannot be pinned in `/mcp`. Scripts use `tools.resources` and `tools.readResource`.
|
|
626
|
-
|
|
627
|
-
### MCP Prompts
|
|
628
|
-
|
|
629
|
-
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.
|
|
630
|
-
|
|
631
|
-
```text
|
|
632
|
-
/mcp__agent_board__create_plan "harden retry policy"
|
|
633
|
-
/mcp__agent_board__review_pipeline status=paused
|
|
634
|
-
/mcp prompts
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
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.
|
|
638
|
-
|
|
639
|
-
### MCP Elicitation
|
|
640
|
-
|
|
641
|
-
When Pi exposes dialog-capable UI, the adapter advertises form elicitation support. Forms use Pi's stock `select()` and `input()` dialogs, validate the response, and provide a review/edit step before submission. Explicit refusal maps to MCP `decline`; dismissing a dialog maps to `cancel`.
|
|
642
|
-
|
|
643
|
-
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.
|
|
644
|
-
|
|
645
|
-
### Typed Discovery and Pinned Tools
|
|
646
|
-
|
|
647
|
-
`mcp_search({ query, server?, limit?, offset? })` searches real tools and loads the matching typed functions for the next request. It defaults to five matches, with a maximum of 100 and zero-based pagination. Use a specific query, or an empty query with `server` to browse that server. It does not execute a hit or rewrite dynamic instructions into the system prompt.
|
|
648
|
-
|
|
649
|
-
Cached eligible schemas and the full gateway/script definitions register inactive; only pins and tools selected for the current branch are active. Legacy resource pins keep the gateway eager. Allowed gateway/script tools also stay eager when the host excludes the loader, so restricted hosts remain usable. The adapter persists canonical `{ server, tool }` selections and enabled gateway/script features in native Pi session entries and restores them across resume, reload, branch navigation, and working-directory changes. Host tool allowlists remain binding. Manual deselection survives reload. Loaded schemas remain available rather than unloading each turn; activation changes declarations and can invalidate the prompt-cache prefix.
|
|
650
|
-
|
|
651
|
-
Both official Pi and the maintained fork use public tool registration and active-tool APIs. Discovered tools retain their flat prefixed names, with `mcp_<server>` namespace metadata for grouping. Always use the exact name returned by discovery rather than constructing names. Inactive tools are not exposed through deferred/codemode execution; loading them still requires the same allowlist and selection checks.
|
|
652
|
-
|
|
653
|
-
Tool execution uses Pi's public request-boundary scheduling, not retired native async or pending-call APIs. Configured approval, MCP App UI consent, and the `beforeExecute` sequential checkpoint barrier remain binding. This is separate from local `mcp_script` orchestration.
|
|
654
|
-
|
|
655
|
-
Add `directTools` to pin frequently used tools into the initial active set:
|
|
656
|
-
|
|
657
|
-
Per-server:
|
|
658
|
-
|
|
659
|
-
```json
|
|
660
|
-
{
|
|
661
|
-
"mcpServers": {
|
|
662
|
-
"chrome-devtools": {
|
|
663
|
-
"command": "npx",
|
|
664
|
-
"args": ["-y", "chrome-devtools-mcp@latest"],
|
|
665
|
-
"directTools": true
|
|
666
|
-
},
|
|
667
|
-
"github": {
|
|
668
|
-
"command": "npx",
|
|
669
|
-
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
670
|
-
"directTools": ["search_repositories", "get_file_contents"]
|
|
671
|
-
},
|
|
672
|
-
"huge-server": {
|
|
673
|
-
"command": "npx",
|
|
674
|
-
"args": ["-y", "mega-mcp@latest"]
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
| Value | Behavior |
|
|
681
|
-
|-------|----------|
|
|
682
|
-
| `true` | Pin all eligible real tools from this server |
|
|
683
|
-
| `["tool_a", "tool_b"]` | Pin these tools (use original MCP names) |
|
|
684
|
-
| Omitted or `false` | No initial pins; load tools with `mcp_search` when needed (default) |
|
|
685
|
-
|
|
686
|
-
To set a global default for all servers:
|
|
687
|
-
|
|
688
|
-
```json
|
|
689
|
-
{
|
|
690
|
-
"settings": {
|
|
691
|
-
"directTools": true
|
|
692
|
-
},
|
|
693
|
-
"mcpServers": {
|
|
694
|
-
"huge-server": {
|
|
695
|
-
"directTools": false
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
Per-server `directTools` overrides the global setting. The example above pins tools for every server except `huge-server`, whose tools remain discoverable.
|
|
702
|
-
|
|
703
|
-
Set `MCP_DIRECT_TOOLS=__none__` before loading the adapter to suppress all direct-tool registration, including pins and search-driven activation. `mcp_search` can still discover schemas and report that no functions were loaded; use gateway or script calls instead. Normal tool filters, host allowlists, and disabled-server settings still apply.
|
|
704
|
-
|
|
705
|
-
To expose only a subset of a server, add `includeTools`. Values can be exact original names, prefixed names, or simple glob patterns. Legacy `read_<resource>` names continue to filter resource access:
|
|
706
|
-
|
|
707
|
-
```json
|
|
708
|
-
{
|
|
709
|
-
"mcpServers": {
|
|
710
|
-
"dokploy": {
|
|
711
|
-
"url": "http://localhost:3845/mcp",
|
|
712
|
-
"directTools": true,
|
|
713
|
-
"includeTools": ["get_*", "dokploy_list_apps"]
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
To hide specific tools while still using `directTools: true`, add `excludeTools` on the server. `excludeTools` is applied after `includeTools`:
|
|
720
|
-
|
|
721
|
-
```json
|
|
722
|
-
{
|
|
723
|
-
"mcpServers": {
|
|
724
|
-
"figma": {
|
|
725
|
-
"url": "http://localhost:3845/mcp",
|
|
726
|
-
"directTools": true,
|
|
727
|
-
"excludeTools": ["read_figjam", "figma_get_code_connect_map"]
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
`includeTools` and `excludeTools` filter typed tools, gateway/script discovery, resource access, and the `/mcp` panel. App-only tools stay out of the model catalog and pin choices.
|
|
734
|
-
|
|
735
|
-
Active schemas consume context; inactive cached schemas do not. Pin the tools you routinely need and discover the rest as the task requires.
|
|
736
|
-
|
|
737
|
-
Valid cached descriptors avoid startup connections. Missing or stale metadata for configured pins is discovered intentionally at startup; eager and keep-alive servers also connect as configured. An empty cache does not trigger discovery of every lazy server. Search coverage reports which configured servers are still unknown.
|
|
738
|
-
|
|
739
|
-
MCP catalog notifications and reconnects refresh eligible metadata. Removed tools leave the active set. `settings.freezeDirectTools: true` keeps the automatic registered surface stable after initial sync; deliberate `mcp({ action: "connect", server: "name" })` or `/mcp reconnect <server>` refreshes remain available.
|
|
740
|
-
|
|
741
|
-
Run `/mcp` to see known tools, pinned counts, resource counts, and connection state. Uncached servers say **undiscovered**. Expand a server to pin or unpin real tools; resources have no checkboxes and use the resource actions instead. Pin changes apply to the current session and persist through `directTools`. Discovered session selections are separate from these startup pins: unpinning leaves a tool active if discovery already selected it for the current branch. Press Enter on a server that needs auth or `ctrl+a` on an OAuth server to authenticate; `ctrl+r` reconnects. Broader setup changes still use Pi's normal reload flow.
|
|
742
|
-
|
|
743
|
-
**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.
|
|
744
|
-
|
|
745
|
-
**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.
|
|
746
|
-
|
|
747
|
-
### MCP UI Integration
|
|
748
|
-
|
|
749
|
-
MCP servers can ship interactive UIs via [MCP Apps](https://github.com/modelcontextprotocol/ext-apps). The adapter uses MCP Apps 2.0.3. When you call a tool that has a UI resource, the adapter opens it in a native macOS window via [Glimpse](https://github.com/hazat/glimpse) if available, otherwise falls back to the browser.
|
|
750
|
-
|
|
751
|
-
**How it works:**
|
|
752
|
-
|
|
753
|
-
1. Agent calls a tool like `launch_dashboard`
|
|
754
|
-
2. The tool's metadata includes `_meta.ui.resourceUri` pointing to a UI resource
|
|
755
|
-
3. The adapter fetches the UI HTML and opens it in an iframe
|
|
756
|
-
4. The UI can call MCP tools and send messages back to the agent
|
|
757
|
-
|
|
758
|
-
**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.
|
|
759
|
-
|
|
760
|
-
**Bidirectional communication:** The UI talks back. When it sends a prompt or intent, the message is stored and `triggerTurn()` wakes the agent. The agent loads the gateway if inactive with `mcp_search({ enable: ["gateway"] })`, then retrieves messages via `mcp({ action: "ui-messages" })` and responds, enabling conversational UIs where the app and agent collaborate in real-time.
|
|
761
|
-
|
|
762
|
-
**Session reuse:** When the agent calls the same tool again while its UI is already open, the adapter pushes the new result to the existing window instead of replacing it. This enables live updates — the agent can refine a chart, add data, or respond to user input without losing the current view. Different tools still replace the session as before.
|
|
763
|
-
|
|
764
|
-
**Message types from UI:**
|
|
765
|
-
|
|
766
|
-
| Type | Purpose |
|
|
767
|
-
|------|---------|
|
|
768
|
-
| `prompt` | User message that triggers an agent response |
|
|
769
|
-
| `intent` | Structured action with name + params |
|
|
770
|
-
| `notify` | Fire-and-forget notification |
|
|
771
|
-
| `message` | Generic message payload |
|
|
772
|
-
| (custom) | Any other type forwarded as intent |
|
|
773
|
-
|
|
774
|
-
**Retrieving UI messages:**
|
|
775
|
-
|
|
776
|
-
```
|
|
777
|
-
mcp({ action: "ui-messages" })
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
Returns completed UI sessions (the last ten), grouped in the existing prompt → intent → context → notification order. Prompt handoffs are also normalized into intents. Prompts, intents and notifications are saved in private disk history as they arrive; context updates and SSE replay retain their existing bounds. Small retrievals keep the original inline content/details. Large retrievals return `resultRef` for the complete grouped output and `historyRefs` for original JSONL events; use `read-result` with `offset`/`limit` to page them without rerunning UI actions. Plain output pages are streamed without loading the entire artifact into memory.
|
|
781
|
-
|
|
782
|
-
Retrieval drains only the sessions included in a usable inline/file receipt; read/write failure leaves them available for retry. History-write failure is explicitly reported, and unsaved events remain recoverable in the current instance's memory. **If normal storage fails and you then reload or exit, that unsaved tail is lost.** There is deliberately no session-journal rescue or alternate recovery file. Retrieve the pending messages before reloading or exiting when possible; do not treat the warning as a durable receipt. This rare failed-storage boundary does not change normal disk-history persistence or failed-delivery recovery. Files use private directory/file permissions and the existing output-directory lifetime: receipts remain readable after draining or UI closure, and are not deleted automatically. Remove artifacts only when you no longer need recovery. New sessions completed during retrieval are not cleared.
|
|
783
|
-
|
|
784
|
-
Messages and context updates are accepted only while the UI is open. An upload finishing after completion is rejected before it can invoke callbacks or alter history. Retrieval verifies the accepted history prefix's byte and event counts; truncated history remains pending recovery instead of being delivered and cleared.
|
|
785
|
-
|
|
786
|
-
**Browser controls:**
|
|
787
|
-
|
|
788
|
-
- **Cmd/Ctrl+Enter** — Complete and close
|
|
789
|
-
- **Escape** — Cancel and close
|
|
790
|
-
- **Done/Cancel buttons** — Same as keyboard shortcuts
|
|
791
|
-
|
|
792
|
-
**Technical notes:**
|
|
793
|
-
|
|
794
|
-
- Tool consent gates whether UIs can call MCP tools (never/once-per-server/always)
|
|
795
|
-
- `_meta.ui.visibility` controls audience: tools marked app-only stay out of the model tool list, and tools marked model-only cannot be called from the UI iframe.
|
|
796
|
-
- Works with both stdio and HTTP MCP servers
|
|
797
|
-
- Uses a local MCP Apps AppBridge bundle for browser↔server communication
|
|
798
|
-
- Enforces CSP from standard `_meta.ui.csp` and OpenAI-compatible `_meta["openai/widgetCSP"]` metadata in the response header while preserving provider HTML.
|
|
799
|
-
|
|
800
|
-
### Local Example: Interactive Visualizer
|
|
801
|
-
|
|
802
|
-
A minimal MCP UI example at `examples/interactive-visualizer` demonstrating charts, bidirectional messaging, and streaming. From that directory:
|
|
803
|
-
|
|
804
|
-
```bash
|
|
805
|
-
npm install
|
|
806
|
-
npm run build
|
|
807
|
-
npm run install-local
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
Restart pi, then ask the agent to show a chart — it calls `show_chart` and opens the UI in Glimpse (macOS) or the browser. Use `npm run uninstall-local` to remove the MCP entry.
|
|
811
|
-
|
|
812
|
-
### Import Existing Configs
|
|
813
|
-
|
|
814
|
-
Shared MCP files are loaded automatically. Use `imports` only for host-specific config formats that are not already covered by `.mcp.json` or `~/.config/mcp/mcp.json`.
|
|
815
|
-
|
|
816
|
-
```json
|
|
817
|
-
{
|
|
818
|
-
"imports": ["cursor", "claude-code", "claude-desktop", "opencode"],
|
|
819
|
-
"mcpServers": { }
|
|
820
|
-
}
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
Supported compatibility imports: `cursor`, `claude-code`, `claude-desktop`, `opencode`, `vscode`, `windsurf`, `codex`
|
|
824
|
-
|
|
825
|
-
`fitch-mcp-adapter init` detects these host-specific configs and adds missing imports to the adapter-owned global 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.
|
|
39
|
+
Run `/reload` after config changes. Ask Pi:
|
|
826
40
|
|
|
827
|
-
|
|
41
|
+
> Use Chrome DevTools to take a screenshot of the current page.
|
|
828
42
|
|
|
829
|
-
|
|
43
|
+
By default, servers connect when Pi discovers an uncached catalog or calls a tool. Cached catalogs remain searchable without a connection.
|
|
830
44
|
|
|
831
|
-
##
|
|
45
|
+
## Controls and settings
|
|
832
46
|
|
|
833
|
-
|
|
|
834
|
-
|
|
835
|
-
|
|
|
836
|
-
|
|
|
837
|
-
|
|
|
838
|
-
|
|
|
839
|
-
|
|
|
840
|
-
|
|
|
841
|
-
| Instructions | `mcp({ action: "instructions", server: "name" })` |
|
|
842
|
-
| Call | `mcp({ action: "call", tool: "tool_name", args: { key: "value" } })` |
|
|
843
|
-
| Connect or refresh | `mcp({ action: "connect", server: "name", limit: 12, offset: 0 })` |
|
|
844
|
-
| List resources | `mcp({ action: "resources", server: "name" })` |
|
|
845
|
-
| Read resource | `mcp({ action: "read-resource", server: "name", uri: "docs://guide" })` |
|
|
846
|
-
| Read saved result | `mcp({ action: "read-result", ref: "/returned/result/path", path: "/structuredContent" })` |
|
|
847
|
-
| UI messages | `mcp({ action: "ui-messages" })` |
|
|
848
|
-
| Auth start | `mcp({ action: "auth-start", server: "name" })` |
|
|
849
|
-
| Auth complete | `mcp({ action: "auth-complete", server: "name" })` after the browser callback, or supply `args: { redirectUrl: "..." }` |
|
|
47
|
+
| Command | Purpose |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `/mcp` | Show servers and pin tools |
|
|
50
|
+
| `/mcp status` | Show connection state and tool/resource counts |
|
|
51
|
+
| `/mcp reconnect <server>` | Reconnect and refresh the catalog |
|
|
52
|
+
| `/mcp-auth <server>` | Start OAuth in an interactive session |
|
|
53
|
+
| `/mcp disable <server>` | Disable the server for this project; apply with `/reload` |
|
|
54
|
+
| `/mcp enable <server>` | Enable the server for this project; apply with `/reload` |
|
|
850
55
|
|
|
851
|
-
|
|
56
|
+
Use [`directTools`](docs/reference.md#typed-discovery-and-pinned-tools) to pin tools at startup. Other tools remain available for discovery. Global search covers known catalogs; select a server to discover an unknown catalog.
|
|
852
57
|
|
|
853
|
-
`
|
|
58
|
+
Use [`approveTools`](docs/reference.md#tool-approval) to require confirmation before sensitive calls. Matching calls fail in headless sessions without approval.
|
|
854
59
|
|
|
855
|
-
|
|
60
|
+
## More server features
|
|
856
61
|
|
|
857
|
-
|
|
62
|
+
The adapter supports [resources](docs/reference.md#mcp-resources) and [prompt templates](docs/reference.md#mcp-prompts). Prompt commands use `/mcp__<server>__<prompt>` names.
|
|
858
63
|
|
|
859
|
-
|
|
64
|
+
[MCP Apps](docs/reference.md#mcp-ui-integration) open interactive UIs in a browser or through [Glimpse](https://github.com/hazat/glimpse) on macOS. Apps can send messages to Pi. Try the [interactive visualizer](examples/interactive-visualizer/README.md).
|
|
860
65
|
|
|
861
|
-
|
|
66
|
+
Use [`mcp_script`](docs/reference.md#mcp-scripting) for loops and dependent tool calls. See [gateway examples](docs/reference.md#usage) for exact call shapes.
|
|
862
67
|
|
|
863
|
-
|
|
68
|
+
## Warnings and platform limits
|
|
864
69
|
|
|
865
|
-
|
|
70
|
+
MCP servers can execute programs or access accounts. Check server commands and account access before you trust a project config.
|
|
866
71
|
|
|
867
|
-
|
|
868
|
-
|---------|--------------|
|
|
869
|
-
| `/mcp` | Interactive panel and first-run onboarding surface |
|
|
870
|
-
| `/mcp setup` | Guided setup for imports, a minimal `.mcp.json`, curated known servers, RepoPrompt quick-add, and config-path inspection |
|
|
871
|
-
| `/mcp tools` | List discovered real tools, excluding resource aliases |
|
|
872
|
-
| `/mcp status` | Show connection state and separate tool/resource counts |
|
|
873
|
-
| `/mcp prompts` | List all MCP prompts registered as slash commands |
|
|
874
|
-
| `/mcp reconnect` | Reconnect all servers |
|
|
875
|
-
| `/mcp reconnect <server>` | Connect or reconnect a single server |
|
|
876
|
-
| `/mcp disable <server>` | Disable a server in the project-local `.pi/fitch-mcp-adapter/mcp.json` (requires `/reload` to apply) |
|
|
877
|
-
| `/mcp enable <server>` | Enable through the project-local override layer (requires `/reload` to apply) |
|
|
878
|
-
| `/mcp logout <server>` | Clear stored OAuth credentials for a server and disconnect it |
|
|
879
|
-
| `/mcp-auth` | Open an OAuth server picker in interactive UI sessions |
|
|
880
|
-
| `/mcp-auth <server>` | OAuth setup for a specific server |
|
|
72
|
+
OAuth requires an available OS credential store. Unlock a Secret Service-compatible keyring on headless Linux. The adapter has no plaintext fallback. See the [OAuth guide](OAUTH.md) for remote sign-in.
|
|
881
73
|
|
|
882
|
-
|
|
74
|
+
Android and Termux do not support persistent OAuth by default. Unauthenticated, header-token, and stdio servers work. Git/source builds require an Android-native `tsgo` compiler. Use the precompiled npm package if you have no compiler. See [Android build details](docs/development.md#androidtermux-source-builds).
|
|
883
75
|
|
|
884
|
-
|
|
76
|
+
The adapter saves text above the default 50 KiB or 2,000-line limit to local files. Saved results and App histories can contain sensitive data. Remove files when you no longer need them. See [output handling and readback](docs/reference.md#output-guard).
|
|
885
77
|
|
|
886
|
-
|
|
78
|
+
If App history cannot be saved, reload or exit discards unsaved messages. Retrieve pending messages before either action. See [App history and recovery](docs/reference.md#mcp-ui-integration).
|
|
887
79
|
|
|
888
|
-
|
|
80
|
+
## Documentation
|
|
889
81
|
|
|
890
|
-
|
|
82
|
+
- [Reference](docs/reference.md): all settings, tool actions, SDK integration, transport behavior, and limits.
|
|
83
|
+
- [Upgrade guide](docs/migration.md): API changes and pre-v5 state migration.
|
|
84
|
+
- [Development and releases](docs/development.md): local checks and maintainer procedures.
|
|
85
|
+
- [Changelog](CHANGELOG.md) · [Report an issue](https://github.com/fitchmultz/pi-mcp-adapter/issues)
|
|
891
86
|
|
|
892
|
-
|
|
893
|
-
- Lazy servers connect on selected discovery or calls; configured pins and eager/keep-alive servers can bootstrap intentionally
|
|
894
|
-
- Tool metadata is cached to disk so search/list/describe work without live connections
|
|
895
|
-
- Idle servers disconnect after 10 minutes (configurable), reconnect automatically on next use
|
|
896
|
-
- npx-based servers resolve to direct binary paths, skipping the ~143 MB npm parent process
|
|
897
|
-
- Pi validates loaded typed-tool arguments against their schemas; gateway and script arguments are validated by the MCP server
|
|
898
|
-
- Keep-alive servers get health checks and auto-reconnect
|
|
899
|
-
- `directTools` pins initial tools; search-driven selections persist separately on the native session branch
|
|
900
|
-
- Resources use explicit URI-based listing and reading, without synthetic function definitions
|
|
87
|
+
## License and background
|
|
901
88
|
|
|
902
|
-
|
|
89
|
+
Mitch Fultz maintains Fitch MCP Adapter under the [MIT license](LICENSE). Mario Zechner discusses tool schema costs in [his article about MCP](https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/).
|
|
903
90
|
|
|
904
|
-
|
|
905
|
-
- Compact MCP result rendering summarizes text, but inline images are still controlled by Pi's image display settings and may render below the compact text summary.
|
|
906
|
-
- MCP sampling support is text-only; context inclusion, tools, stop sequences, audio, and image content are rejected with explicit errors.
|
|
91
|
+
[Watch the adapter demo](https://github.com/user-attachments/assets/4b7c66ff-e27e-4639-b195-22c3db406a5a).
|