pi-mcp-adapter 2.9.0 → 2.10.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 +11 -0
- package/README.md +27 -4
- package/commands.ts +6 -5
- package/direct-tools.ts +18 -4
- package/elicitation-handler.ts +252 -258
- package/index.ts +27 -2
- package/init.ts +7 -6
- package/mcp-auth-flow.ts +127 -30
- package/mcp-oauth-provider.ts +48 -1
- package/mcp-panel.ts +12 -9
- package/mcp-setup-panel.ts +13 -9
- package/package.json +3 -2
- package/panel-keys.ts +37 -0
- package/proxy-modes.ts +119 -4
- package/server-manager.ts +88 -39
- package/tool-metadata.ts +98 -34
- package/types.ts +0 -1
- package/ui-resource-handler.ts +2 -1
- package/ui-session.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.10.0] - 2026-06-13
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added manual remote/headless OAuth proxy actions for copying authorization URLs and completing pasted redirect URLs or codes. Thanks @Gabrielgvl for PR #120.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Honored user `tui.select.*` keybindings in MCP management, setup, and auth panels. Thanks @owenniles for PR #138.
|
|
17
|
+
- Included configured OAuth scopes in authorization-code flows while preserving token endpoint authentication method selection. Thanks @carlosdagos for PR #140.
|
|
18
|
+
- Fixed MCP elicitation on stock Pi, including form dialogs with validation and review, consent-based URL handling, URL-required errors, completion notifications, and TUI-only browser navigation. Thanks @dmmulroy for PR #139.
|
|
19
|
+
- Expanded MCP schema formatting for nested `anyOf`/`oneOf` variants, `const` discriminators, nested object properties, and array items.
|
|
20
|
+
|
|
10
21
|
## [2.9.0] - 2026-06-04
|
|
11
22
|
|
|
12
23
|
### Added
|
package/README.md
CHANGED
|
@@ -142,6 +142,26 @@ Pi-specific files are the write targets for imported or shared global servers wh
|
|
|
142
142
|
|
|
143
143
|
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
144
|
|
|
145
|
+
### Remote/headless OAuth
|
|
146
|
+
|
|
147
|
+
If Pi is running on a remote server and cannot open a local browser, start OAuth through the proxy tool:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
mcp({ action: "auth-start", server: "linear-server" })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Open the returned authorization URL in your local browser. After approval, your browser redirects to a localhost URL. On a remote server that local page may fail to load; copy the full URL from the browser address bar anyway and complete the flow in the same Pi session:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
mcp({
|
|
157
|
+
action: "auth-complete",
|
|
158
|
+
server: "linear-server",
|
|
159
|
+
args: '{"redirectUrl":"http://localhost:19876/callback?code=...&state=..."}'
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
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.
|
|
164
|
+
|
|
145
165
|
### Lifecycle Modes
|
|
146
166
|
|
|
147
167
|
- **`lazy`** (default) — Don't connect at startup. Connect on first tool call. Disconnect after idle timeout. Cached metadata keeps search/list working without connections.
|
|
@@ -169,14 +189,15 @@ For pre-registered browser OAuth clients, set `oauth.redirectUri` to the exact c
|
|
|
169
189
|
| `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
|
|
170
190
|
| `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
|
|
171
191
|
| `samplingAutoApprove` | Skip sampling confirmation prompts. Required for sampling in non-UI sessions (default: false). |
|
|
172
|
-
| `elicitation` | Allow MCP servers to request user input through Pi
|
|
173
|
-
| `elicitationAutoOpenUrls` | Automatically open URL elicitations without prompting first (default: false). |
|
|
192
|
+
| `elicitation` | Allow MCP servers to request user input through Pi dialogs (default: true when Pi UI is available). |
|
|
174
193
|
|
|
175
194
|
Per-server `idleTimeout` overrides the global setting.
|
|
176
195
|
|
|
177
196
|
### MCP Elicitation
|
|
178
197
|
|
|
179
|
-
When Pi exposes UI, the adapter advertises
|
|
198
|
+
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`.
|
|
199
|
+
|
|
200
|
+
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.
|
|
180
201
|
|
|
181
202
|
### Direct Tools
|
|
182
203
|
|
|
@@ -345,6 +366,8 @@ Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only
|
|
|
345
366
|
| Call | `mcp({ tool: "...", args: '{"key": "value"}' })` |
|
|
346
367
|
| Connect | `mcp({ connect: "server-name" })` |
|
|
347
368
|
| UI messages | `mcp({ action: "ui-messages" })` |
|
|
369
|
+
| Auth start | `mcp({ action: "auth-start", server: "name" })` |
|
|
370
|
+
| Auth complete | `mcp({ action: "auth-complete", server: "name", args: '{"redirectUrl":"..."}' })` |
|
|
348
371
|
|
|
349
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.
|
|
350
373
|
|
|
@@ -367,7 +390,7 @@ Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_li
|
|
|
367
390
|
|
|
368
391
|
If `settings.autoAuth` is `true`, `mcp({ connect: ... })`, `mcp({ tool: ... })`, and direct tool calls automatically run OAuth when needed and retry once.
|
|
369
392
|
|
|
370
|
-
In interactive sessions, you can also authenticate from `/mcp` with `ctrl+a` or Enter on a server that needs auth. In
|
|
393
|
+
In interactive sessions, you can also authenticate from `/mcp` with `ctrl+a` or Enter on a server that needs auth. In remote/headless sessions, use the proxy tool's `auth-start` and `auth-complete` actions to copy the authorization URL locally and paste the redirect URL back into Pi. `/mcp-auth` without a server only opens a picker in the interactive UI.
|
|
371
394
|
|
|
372
395
|
## How It Works
|
|
373
396
|
|
package/commands.ts
CHANGED
|
@@ -286,8 +286,8 @@ export async function openMcpSetup(
|
|
|
286
286
|
|
|
287
287
|
return new Promise<PanelFlowResult>((resolve) => {
|
|
288
288
|
ctx.ui.custom(
|
|
289
|
-
(tui, _theme,
|
|
290
|
-
return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState }, tui, () => {
|
|
289
|
+
(tui, _theme, keybindings, done) => {
|
|
290
|
+
return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState, keybindings }, tui, () => {
|
|
291
291
|
done(undefined);
|
|
292
292
|
resolve({ configChanged });
|
|
293
293
|
});
|
|
@@ -358,7 +358,7 @@ export async function openMcpPanel(
|
|
|
358
358
|
|
|
359
359
|
await new Promise<void>((resolve) => {
|
|
360
360
|
ctx.ui.custom(
|
|
361
|
-
(tui, _theme,
|
|
361
|
+
(tui, _theme, keybindings, done) => {
|
|
362
362
|
return createMcpPanel(config, cache, provenanceMap, callbacks, tui, (result: McpPanelResult) => {
|
|
363
363
|
if (!result.cancelled && result.changes.size > 0) {
|
|
364
364
|
writeDirectToolsConfig(result.changes, provenanceMap, config);
|
|
@@ -367,7 +367,7 @@ export async function openMcpPanel(
|
|
|
367
367
|
}
|
|
368
368
|
done(undefined);
|
|
369
369
|
resolve();
|
|
370
|
-
}, { noticeLines });
|
|
370
|
+
}, { noticeLines, keybindings });
|
|
371
371
|
},
|
|
372
372
|
{ overlay: true, overlayOptions: { anchor: "center", width: 82 } },
|
|
373
373
|
);
|
|
@@ -403,12 +403,13 @@ export async function openMcpAuthPanel(
|
|
|
403
403
|
|
|
404
404
|
await new Promise<void>((resolve) => {
|
|
405
405
|
ctx.ui.custom(
|
|
406
|
-
(tui, _theme,
|
|
406
|
+
(tui, _theme, keybindings, done) => {
|
|
407
407
|
return createMcpPanel(config, cache, provenanceMap, callbacks, tui, () => {
|
|
408
408
|
done(undefined);
|
|
409
409
|
resolve();
|
|
410
410
|
}, {
|
|
411
411
|
authOnly: true,
|
|
412
|
+
keybindings,
|
|
412
413
|
noticeLines: ["Select an OAuth MCP server and press Enter or ctrl+a to authenticate."],
|
|
413
414
|
});
|
|
414
415
|
},
|
package/direct-tools.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { UrlElicitationRequiredError } from "@modelcontextprotocol/sdk/types.js";
|
|
2
3
|
import type { McpExtensionState } from "./state.ts";
|
|
3
4
|
import type { DirectToolSpec, McpConfig, McpContent } from "./types.ts";
|
|
4
5
|
import type { MetadataCache } from "./metadata-cache.ts";
|
|
@@ -22,7 +23,7 @@ type DirectAutoAuthResult =
|
|
|
22
23
|
function getDirectAuthRequiredMessage(
|
|
23
24
|
state: McpExtensionState,
|
|
24
25
|
serverName: string,
|
|
25
|
-
defaultMessage = `MCP server "${serverName}" requires OAuth authentication. Run /mcp-auth ${serverName}
|
|
26
|
+
defaultMessage = `MCP server "${serverName}" requires OAuth authentication. Run mcp({ action: "auth-start", server: "${serverName}" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`,
|
|
26
27
|
): string {
|
|
27
28
|
return formatAuthRequiredMessage(state.config, serverName, defaultMessage);
|
|
28
29
|
}
|
|
@@ -32,7 +33,7 @@ function getDirectAuthFailedMessage(state: McpExtensionState, serverName: string
|
|
|
32
33
|
if (customGuidance) {
|
|
33
34
|
return `OAuth authentication failed for "${serverName}": ${message}. ${getDirectAuthRequiredMessage(state, serverName)}`;
|
|
34
35
|
}
|
|
35
|
-
return `OAuth authentication failed for "${serverName}": ${message}. Run /mcp-auth ${serverName}
|
|
36
|
+
return `OAuth authentication failed for "${serverName}": ${message}. Run mcp({ action: "auth-start", server: "${serverName}" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
async function attemptDirectAutoAuth(
|
|
@@ -55,7 +56,7 @@ async function attemptDirectAutoAuth(
|
|
|
55
56
|
message: getDirectAuthRequiredMessage(
|
|
56
57
|
state,
|
|
57
58
|
serverName,
|
|
58
|
-
`MCP server "${serverName}" requires OAuth authentication. Run /mcp-auth ${serverName} in an interactive session.`,
|
|
59
|
+
`MCP server "${serverName}" requires OAuth authentication. Run mcp({ action: "auth-start", server: "${serverName}" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`,
|
|
59
60
|
),
|
|
60
61
|
};
|
|
61
62
|
}
|
|
@@ -255,7 +256,9 @@ export function buildProxyDescription(
|
|
|
255
256
|
desc += ` mcp({ connect: "server-name" }) → Connect to a server and refresh metadata\n`;
|
|
256
257
|
desc += ` mcp({ tool: "name", args: '{"key": "value"}' }) → Call a tool (args is JSON string)\n`;
|
|
257
258
|
desc += ` mcp({ action: "ui-messages" }) → Retrieve accumulated messages from completed UI sessions\n`;
|
|
258
|
-
desc +=
|
|
259
|
+
desc += ` mcp({ action: "auth-start", server: "name" }) → Start manual OAuth and get a browser URL\n`;
|
|
260
|
+
desc += ` mcp({ action: "auth-complete", server: "name", args: '{"redirectUrl":"..."}' }) → Complete manual OAuth\n`;
|
|
261
|
+
desc += `\nMode: action > tool (call) > connect > describe > search > server (list) > nothing (status)`;
|
|
259
262
|
|
|
260
263
|
return desc;
|
|
261
264
|
}
|
|
@@ -406,6 +409,17 @@ export function createDirectToolExecutor(
|
|
|
406
409
|
details: { server: spec.serverName, tool: spec.originalName },
|
|
407
410
|
};
|
|
408
411
|
} catch (error) {
|
|
412
|
+
if (error instanceof UrlElicitationRequiredError) {
|
|
413
|
+
const action = await state.manager.handleUrlElicitationRequired(spec.serverName, error);
|
|
414
|
+
const message = action === "accept"
|
|
415
|
+
? "The original MCP tool did not run. Complete the opened browser interaction, then retry the tool."
|
|
416
|
+
: `The URL interaction was ${action === "decline" ? "declined" : "cancelled"}.`;
|
|
417
|
+
uiSession?.sendToolCancelled(message);
|
|
418
|
+
return {
|
|
419
|
+
content: [{ type: "text" as const, text: message }],
|
|
420
|
+
details: { error: "url_elicitation_required", server: spec.serverName, action },
|
|
421
|
+
};
|
|
422
|
+
}
|
|
409
423
|
const message = error instanceof Error ? error.message : String(error);
|
|
410
424
|
uiSession?.sendToolCancelled(message);
|
|
411
425
|
let errorText = `Failed to call tool: ${message}`;
|