pi-mcp-adapter 2.5.3 → 2.6.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 +24 -0
- package/README.md +11 -5
- package/banner.png +0 -0
- package/commands.ts +109 -55
- package/config.ts +12 -12
- package/consent-manager.ts +2 -2
- package/direct-tools.ts +21 -15
- package/host-html-template.ts +1 -1
- package/index.ts +22 -15
- package/init.ts +15 -15
- package/lifecycle.ts +3 -3
- package/mcp-auth-flow.ts +14 -5
- package/mcp-auth.ts +25 -5
- package/mcp-callback-server.ts +1 -1
- package/mcp-oauth-provider.ts +6 -7
- package/mcp-panel.ts +114 -38
- package/mcp-setup-panel.ts +4 -4
- package/metadata-cache.ts +6 -6
- package/npx-resolver.ts +1 -1
- package/oauth-handler.ts +1 -1
- package/onboarding-state.ts +1 -1
- package/package.json +7 -3
- package/proxy-modes.ts +11 -11
- package/sampling-handler.ts +38 -16
- package/server-manager.ts +9 -10
- package/state.ts +8 -8
- package/tool-metadata.ts +5 -5
- package/tool-registrar.ts +1 -1
- package/tool-result-renderer.ts +65 -0
- package/types.ts +11 -4
- package/ui-resource-handler.ts +4 -4
- package/ui-server.ts +6 -6
- package/ui-session.ts +5 -5
- package/utils.ts +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.6.0] - 2026-05-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added a no-argument `/mcp-auth` OAuth picker and in-panel auth shortcut for OAuth-capable MCP servers.
|
|
14
|
+
- Added compact collapsed rendering for MCP proxy and direct-tool result rows while keeping full tool results available when expanded.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Migrated Pi runtime dependencies and imports from deprecated `@mariozechner/*` packages to `@earendil-works/*` packages.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Re-register dynamic OAuth clients during fresh auth when cached DCR client info exists without tokens, avoiding dead authorization URLs after server-side client invalidation.
|
|
21
|
+
- Kept OAuth tokens, dynamic client info, PKCE verifiers, and OAuth state bound to the server URL so stale credentials cannot be reused after a server URL changes.
|
|
22
|
+
- Kept the `/mcp-auth` OAuth picker search focused on OAuth server rows and prevented hidden panel shortcuts from unexpectedly launching auth.
|
|
23
|
+
- Kept long MCP error results expanded in compact tool result rendering.
|
|
24
|
+
|
|
25
|
+
## [2.5.4] - 2026-05-04
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Ignored npm lockfiles and removed checked-in `package-lock.json` files.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- Resolved `${VAR}` and `$env:VAR` placeholders in HTTP bearer tokens.
|
|
32
|
+
- Honored MCP sampling `modelPreferences.hints` before falling back to the current/default model.
|
|
33
|
+
|
|
10
34
|
## [2.5.3] - 2026-05-01
|
|
11
35
|
|
|
12
36
|
### Added
|
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
|
|
|
126
126
|
| `headers` | HTTP headers; supports `${VAR}` and `$env:VAR` interpolation |
|
|
127
127
|
| `auth` | `"bearer"` or `"oauth"` |
|
|
128
128
|
| `oauth.grantType` | `"authorization_code"` (default) or `"client_credentials"` for non-interactive machine auth |
|
|
129
|
-
| `bearerToken` / `bearerTokenEnv` | Token or env var name |
|
|
129
|
+
| `bearerToken` / `bearerTokenEnv` | Token or env var name; `bearerToken` supports `${VAR}` and `$env:VAR` interpolation |
|
|
130
130
|
| `lifecycle` | `"lazy"` (default), `"eager"`, or `"keep-alive"` |
|
|
131
131
|
| `idleTimeout` | Minutes before idle disconnect (overrides global) |
|
|
132
132
|
| `exposeResources` | Expose MCP resources as tools (default: true) |
|
|
@@ -159,7 +159,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
|
|
|
159
159
|
| `directTools` | Global default for all servers (default: false). Per-server overrides this. |
|
|
160
160
|
| `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
|
|
161
161
|
| `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
|
|
162
|
-
| `sampling` | Allow MCP servers to
|
|
162
|
+
| `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
|
|
163
163
|
| `samplingAutoApprove` | Skip sampling confirmation prompts. Required for sampling in non-UI sessions (default: false). |
|
|
164
164
|
|
|
165
165
|
Per-server `idleTimeout` overrides the global setting.
|
|
@@ -236,7 +236,7 @@ Direct tools register from the metadata cache in the Pi agent dir (`~/.pi/agent/
|
|
|
236
236
|
|
|
237
237
|
When you change direct-tool toggles in `/mcp` or write new config through `/mcp setup`, the extension triggers Pi's normal reload flow automatically. That refreshes extensions, prompts, skills, and MCP tool registration in one shot, so newly configured direct tools can appear without a manual restart.
|
|
238
238
|
|
|
239
|
-
**Interactive configuration:** Run `/mcp` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. You can reconnect servers
|
|
239
|
+
**Interactive configuration:** Run `/mcp` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. You can reconnect servers and toggle tools between direct and proxy from the same overlay. For OAuth, press Enter on a server that needs auth or `ctrl+a` on any OAuth server.
|
|
240
240
|
|
|
241
241
|
**Guided first-run setup:** Run `/mcp setup` to inspect detected shared MCP files, adopt compatibility imports from other hosts, open discovered config paths, preview exact before/after file diffs for writes, scaffold a minimal project `.mcp.json`, or quick-add RepoPrompt into a standard/shared MCP file.
|
|
242
242
|
|
|
@@ -332,6 +332,8 @@ Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only
|
|
|
332
332
|
| Connect | `mcp({ connect: "server-name" })` |
|
|
333
333
|
| UI messages | `mcp({ action: "ui-messages" })` |
|
|
334
334
|
|
|
335
|
+
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.
|
|
336
|
+
|
|
335
337
|
Search includes both MCP tools and Pi tools (from extensions). Pi tools appear first with `[pi tool]` prefix. Space-separated words are OR'd.
|
|
336
338
|
|
|
337
339
|
Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_library_id` finds `context7_resolve-library-id`.
|
|
@@ -345,9 +347,12 @@ Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_li
|
|
|
345
347
|
| `/mcp tools` | List all tools |
|
|
346
348
|
| `/mcp reconnect` | Reconnect all servers |
|
|
347
349
|
| `/mcp reconnect <server>` | Connect or reconnect a single server |
|
|
348
|
-
| `/mcp-auth
|
|
350
|
+
| `/mcp-auth` | Open an OAuth server picker in interactive UI sessions |
|
|
351
|
+
| `/mcp-auth <server>` | OAuth setup for a specific server |
|
|
352
|
+
|
|
353
|
+
If `settings.autoAuth` is `true`, `mcp({ connect: ... })`, `mcp({ tool: ... })`, and direct tool calls automatically run OAuth when needed and retry once.
|
|
349
354
|
|
|
350
|
-
|
|
355
|
+
In interactive sessions, you can also authenticate from `/mcp` with `ctrl+a` or Enter on a server that needs auth. In non-interactive sessions, browser-based OAuth still requires `/mcp-auth <server>`. `/mcp-auth` without a server only opens a picker in the interactive UI.
|
|
351
356
|
|
|
352
357
|
## How It Works
|
|
353
358
|
|
|
@@ -363,4 +368,5 @@ If `settings.autoAuth` is `true`, `mcp({ connect: ... })`, `mcp({ tool: ... })`,
|
|
|
363
368
|
## Limitations
|
|
364
369
|
|
|
365
370
|
- Cross-session server sharing not yet implemented (each Pi session runs its own server processes)
|
|
371
|
+
- 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.
|
|
366
372
|
- MCP sampling support is text-only; context inclusion, tools, stop sequences, audio, and image content are rejected with explicit errors.
|
package/banner.png
ADDED
|
Binary file
|
package/commands.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
2
|
-
import type { McpExtensionState } from "./state.
|
|
3
|
-
import type { McpConfig, ServerEntry, McpPanelCallbacks, McpPanelResult, ImportKind } from "./types.
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { McpExtensionState } from "./state.ts";
|
|
3
|
+
import type { McpAuthResult, McpConfig, ServerEntry, McpPanelCallbacks, McpPanelResult, ImportKind } from "./types.ts";
|
|
4
4
|
import {
|
|
5
5
|
ensureCompatibilityImports,
|
|
6
6
|
getMcpDiscoverySummary,
|
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
writeDirectToolsConfig,
|
|
12
12
|
writeSharedServerEntry,
|
|
13
13
|
writeStarterProjectConfig,
|
|
14
|
-
} from "./config.
|
|
15
|
-
import { lazyConnect, updateMetadataCache, updateStatusBar, getFailureAgeSeconds } from "./init.
|
|
16
|
-
import { loadMetadataCache } from "./metadata-cache.
|
|
17
|
-
import { buildToolMetadata } from "./tool-metadata.
|
|
18
|
-
import { supportsOAuth, authenticate } from "./mcp-auth-flow.
|
|
19
|
-
import {
|
|
20
|
-
import { loadOnboardingState, markSetupCompleted as persistSetupCompleted, markSharedConfigHintShown } from "./onboarding-state.
|
|
21
|
-
import { openPath } from "./utils.
|
|
14
|
+
} from "./config.ts";
|
|
15
|
+
import { lazyConnect, updateMetadataCache, updateStatusBar, getFailureAgeSeconds } from "./init.ts";
|
|
16
|
+
import { loadMetadataCache } from "./metadata-cache.ts";
|
|
17
|
+
import { buildToolMetadata } from "./tool-metadata.ts";
|
|
18
|
+
import { supportsOAuth, authenticate } from "./mcp-auth-flow.ts";
|
|
19
|
+
import { getAuthForUrl } from "./mcp-auth.ts";
|
|
20
|
+
import { loadOnboardingState, markSetupCompleted as persistSetupCompleted, markSharedConfigHintShown } from "./onboarding-state.ts";
|
|
21
|
+
import { openPath } from "./utils.ts";
|
|
22
22
|
|
|
23
23
|
export async function showStatus(state: McpExtensionState, ctx: ExtensionContext): Promise<void> {
|
|
24
24
|
if (!ctx.hasUI) return;
|
|
@@ -140,30 +140,30 @@ export async function authenticateServer(
|
|
|
140
140
|
serverName: string,
|
|
141
141
|
config: McpConfig,
|
|
142
142
|
ctx: ExtensionContext
|
|
143
|
-
): Promise<
|
|
144
|
-
if (!ctx.hasUI) return;
|
|
143
|
+
): Promise<McpAuthResult> {
|
|
144
|
+
if (!ctx.hasUI) return { ok: false, message: "OAuth authentication requires an interactive session." };
|
|
145
145
|
|
|
146
146
|
const definition = config.mcpServers[serverName];
|
|
147
147
|
if (!definition) {
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
const message = `Server "${serverName}" not found in config`;
|
|
149
|
+
ctx.ui.notify(message, "error");
|
|
150
|
+
return { ok: false, message };
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
if (!supportsOAuth(definition)) {
|
|
154
|
+
const message = `Server "${serverName}" does not use OAuth authentication. Set "auth": "oauth" or omit auth for auto-detection.`;
|
|
153
155
|
ctx.ui.notify(
|
|
154
156
|
`Server "${serverName}" does not use OAuth authentication.\n` +
|
|
155
157
|
`Set "auth": "oauth" or omit auth for auto-detection.`,
|
|
156
158
|
"error"
|
|
157
159
|
);
|
|
158
|
-
return;
|
|
160
|
+
return { ok: false, message };
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
if (!definition.url) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
return;
|
|
164
|
+
const message = `Server "${serverName}" has no URL configured (OAuth requires HTTP transport)`;
|
|
165
|
+
ctx.ui.notify(message, "error");
|
|
166
|
+
return { ok: false, message };
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
try {
|
|
@@ -171,20 +171,22 @@ export async function authenticateServer(
|
|
|
171
171
|
const status = await authenticate(serverName, definition.url, definition);
|
|
172
172
|
|
|
173
173
|
if (status === "authenticated") {
|
|
174
|
+
const message = `OAuth authentication successful for "${serverName}"! Run /mcp reconnect ${serverName} to connect with the new token.`;
|
|
174
175
|
ctx.ui.notify(
|
|
175
176
|
`OAuth authentication successful for "${serverName}"!\n` +
|
|
176
177
|
`Run /mcp reconnect ${serverName} to connect with the new token.`,
|
|
177
|
-
"
|
|
178
|
-
);
|
|
179
|
-
} else {
|
|
180
|
-
ctx.ui.notify(
|
|
181
|
-
`OAuth authentication failed for "${serverName}".`,
|
|
182
|
-
"error"
|
|
178
|
+
"info"
|
|
183
179
|
);
|
|
180
|
+
return { ok: true, message };
|
|
184
181
|
}
|
|
182
|
+
|
|
183
|
+
const message = `OAuth authentication failed for "${serverName}".`;
|
|
184
|
+
ctx.ui.notify(message, "error");
|
|
185
|
+
return { ok: false, message };
|
|
185
186
|
} catch (error) {
|
|
186
187
|
const message = error instanceof Error ? error.message : String(error);
|
|
187
188
|
ctx.ui.notify(`Failed to authenticate "${serverName}": ${message}`, "error");
|
|
189
|
+
return { ok: false, message };
|
|
188
190
|
} finally {
|
|
189
191
|
ctx.ui.setStatus("mcp-auth", undefined);
|
|
190
192
|
}
|
|
@@ -194,8 +196,8 @@ export interface PanelFlowResult {
|
|
|
194
196
|
configChanged: boolean;
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
function buildSharedConfigNoticeLines(configOverridePath
|
|
198
|
-
const discovery = getMcpDiscoverySummary(configOverridePath);
|
|
199
|
+
function buildSharedConfigNoticeLines(configOverridePath: string | undefined, cwd: string): { lines: string[]; fingerprint: string | null } {
|
|
200
|
+
const discovery = getMcpDiscoverySummary(configOverridePath, cwd);
|
|
199
201
|
const onboardingState = loadOnboardingState();
|
|
200
202
|
if (!discovery.hasSharedServers || onboardingState.sharedConfigHintShown) {
|
|
201
203
|
return { lines: [], fingerprint: null };
|
|
@@ -221,16 +223,16 @@ export async function openMcpSetup(
|
|
|
221
223
|
): Promise<PanelFlowResult> {
|
|
222
224
|
if (!ctx.hasUI) return { configChanged: false };
|
|
223
225
|
|
|
224
|
-
const discovery = getMcpDiscoverySummary(configOverridePath);
|
|
226
|
+
const discovery = getMcpDiscoverySummary(configOverridePath, ctx.cwd);
|
|
225
227
|
const onboardingState = loadOnboardingState();
|
|
226
|
-
const { createMcpSetupPanel } = await import("./mcp-setup-panel.
|
|
228
|
+
const { createMcpSetupPanel } = await import("./mcp-setup-panel.ts");
|
|
227
229
|
let configChanged = false;
|
|
228
230
|
|
|
229
231
|
const callbacks = {
|
|
230
232
|
previewImports: (imports: ImportKind[]) => previewCompatibilityImports(imports, configOverridePath),
|
|
231
|
-
previewStarterProject: () => previewStarterProjectConfig(),
|
|
233
|
+
previewStarterProject: () => previewStarterProjectConfig(ctx.cwd),
|
|
232
234
|
previewRepoPrompt: () => {
|
|
233
|
-
const repoPrompt = getMcpDiscoverySummary(configOverridePath).repoPrompt;
|
|
235
|
+
const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd).repoPrompt;
|
|
234
236
|
if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) return null;
|
|
235
237
|
return previewSharedServerEntry(repoPrompt.targetPath, repoPrompt.serverName, repoPrompt.entry);
|
|
236
238
|
},
|
|
@@ -240,12 +242,12 @@ export async function openMcpSetup(
|
|
|
240
242
|
return result;
|
|
241
243
|
},
|
|
242
244
|
scaffoldProjectConfig: async () => {
|
|
243
|
-
const path = writeStarterProjectConfig();
|
|
245
|
+
const path = writeStarterProjectConfig(ctx.cwd);
|
|
244
246
|
configChanged = true;
|
|
245
247
|
return { path };
|
|
246
248
|
},
|
|
247
249
|
addRepoPrompt: async () => {
|
|
248
|
-
const repoPrompt = getMcpDiscoverySummary(configOverridePath).repoPrompt;
|
|
250
|
+
const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd).repoPrompt;
|
|
249
251
|
if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) {
|
|
250
252
|
throw new Error("RepoPrompt is not available to add from this setup screen.");
|
|
251
253
|
}
|
|
@@ -265,7 +267,7 @@ export async function openMcpSetup(
|
|
|
265
267
|
ctx.ui.custom(
|
|
266
268
|
(tui, _theme, _keybindings, done) => {
|
|
267
269
|
return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState }, tui, () => {
|
|
268
|
-
done();
|
|
270
|
+
done(undefined);
|
|
269
271
|
resolve({ configChanged });
|
|
270
272
|
});
|
|
271
273
|
},
|
|
@@ -274,25 +276,18 @@ export async function openMcpSetup(
|
|
|
274
276
|
});
|
|
275
277
|
}
|
|
276
278
|
|
|
277
|
-
|
|
279
|
+
function buildMcpPanelCallbacks(
|
|
278
280
|
state: McpExtensionState,
|
|
279
|
-
|
|
281
|
+
config: McpConfig,
|
|
280
282
|
ctx: ExtensionContext,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const config = state.config;
|
|
288
|
-
const cache = loadMetadataCache();
|
|
289
|
-
const provenanceMap = getServerProvenance(pi.getFlag("mcp-config") as string | undefined ?? configOverridePath);
|
|
290
|
-
const { lines: noticeLines, fingerprint } = buildSharedConfigNoticeLines(pi.getFlag("mcp-config") as string | undefined ?? configOverridePath);
|
|
291
|
-
|
|
292
|
-
const callbacks: McpPanelCallbacks = {
|
|
293
|
-
reconnect: async (serverName: string) => {
|
|
294
|
-
return lazyConnect(state, serverName);
|
|
283
|
+
): McpPanelCallbacks {
|
|
284
|
+
return {
|
|
285
|
+
reconnect: (serverName: string) => lazyConnect(state, serverName),
|
|
286
|
+
canAuthenticate: (serverName: string) => {
|
|
287
|
+
const definition = config.mcpServers[serverName];
|
|
288
|
+
return definition ? supportsOAuth(definition) : false;
|
|
295
289
|
},
|
|
290
|
+
authenticate: (serverName: string) => authenticateServer(serverName, config, ctx),
|
|
296
291
|
getConnectionStatus: (serverName: string) => {
|
|
297
292
|
const definition = config.mcpServers[serverName];
|
|
298
293
|
const connection = state.manager.getConnection(serverName);
|
|
@@ -301,9 +296,10 @@ export async function openMcpPanel(
|
|
|
301
296
|
}
|
|
302
297
|
if (
|
|
303
298
|
definition?.auth === "oauth"
|
|
299
|
+
&& definition.url
|
|
304
300
|
&& definition.oauth !== false
|
|
305
301
|
&& definition.oauth?.grantType !== "client_credentials"
|
|
306
|
-
&& !
|
|
302
|
+
&& !getAuthForUrl(serverName, definition.url)?.tokens
|
|
307
303
|
) {
|
|
308
304
|
return "needs-auth";
|
|
309
305
|
}
|
|
@@ -316,8 +312,27 @@ export async function openMcpPanel(
|
|
|
316
312
|
return freshCache?.servers?.[serverName] ?? null;
|
|
317
313
|
},
|
|
318
314
|
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export async function openMcpPanel(
|
|
318
|
+
state: McpExtensionState,
|
|
319
|
+
pi: ExtensionAPI,
|
|
320
|
+
ctx: ExtensionContext,
|
|
321
|
+
configOverridePath?: string,
|
|
322
|
+
): Promise<PanelFlowResult> {
|
|
323
|
+
if (Object.keys(state.config.mcpServers).length === 0) {
|
|
324
|
+
return openMcpSetup(state, pi, ctx, configOverridePath, "empty");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const config = state.config;
|
|
328
|
+
const cache = loadMetadataCache();
|
|
329
|
+
const configPath = pi.getFlag("mcp-config") as string | undefined ?? configOverridePath;
|
|
330
|
+
const provenanceMap = getServerProvenance(configPath, ctx.cwd);
|
|
331
|
+
const { lines: noticeLines, fingerprint } = buildSharedConfigNoticeLines(configPath, ctx.cwd);
|
|
332
|
+
|
|
333
|
+
const callbacks = buildMcpPanelCallbacks(state, config, ctx);
|
|
319
334
|
|
|
320
|
-
const { createMcpPanel } = await import("./mcp-panel.
|
|
335
|
+
const { createMcpPanel } = await import("./mcp-panel.ts");
|
|
321
336
|
let configChanged = false;
|
|
322
337
|
|
|
323
338
|
await new Promise<void>((resolve) => {
|
|
@@ -329,7 +344,7 @@ export async function openMcpPanel(
|
|
|
329
344
|
configChanged = true;
|
|
330
345
|
ctx.ui.notify("Direct tools updated. Pi will reload after this panel closes.", "info");
|
|
331
346
|
}
|
|
332
|
-
done();
|
|
347
|
+
done(undefined);
|
|
333
348
|
resolve();
|
|
334
349
|
}, { noticeLines });
|
|
335
350
|
},
|
|
@@ -343,3 +358,42 @@ export async function openMcpPanel(
|
|
|
343
358
|
|
|
344
359
|
return { configChanged };
|
|
345
360
|
}
|
|
361
|
+
|
|
362
|
+
export async function openMcpAuthPanel(
|
|
363
|
+
state: McpExtensionState,
|
|
364
|
+
pi: ExtensionAPI,
|
|
365
|
+
ctx: ExtensionContext,
|
|
366
|
+
configOverridePath?: string,
|
|
367
|
+
): Promise<PanelFlowResult> {
|
|
368
|
+
if (!ctx.hasUI) return { configChanged: false };
|
|
369
|
+
|
|
370
|
+
const config = state.config;
|
|
371
|
+
const oauthServers = Object.entries(config.mcpServers).filter(([, definition]) => supportsOAuth(definition));
|
|
372
|
+
if (oauthServers.length === 0) {
|
|
373
|
+
ctx.ui.notify("No OAuth-capable MCP servers are configured.", "warning");
|
|
374
|
+
return { configChanged: false };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const cache = loadMetadataCache();
|
|
378
|
+
const configPath = pi.getFlag("mcp-config") as string | undefined ?? configOverridePath;
|
|
379
|
+
const provenanceMap = getServerProvenance(configPath, ctx.cwd);
|
|
380
|
+
const callbacks = buildMcpPanelCallbacks(state, config, ctx);
|
|
381
|
+
const { createMcpPanel } = await import("./mcp-panel.ts");
|
|
382
|
+
|
|
383
|
+
await new Promise<void>((resolve) => {
|
|
384
|
+
ctx.ui.custom(
|
|
385
|
+
(tui, _theme, _keybindings, done) => {
|
|
386
|
+
return createMcpPanel(config, cache, provenanceMap, callbacks, tui, () => {
|
|
387
|
+
done(undefined);
|
|
388
|
+
resolve();
|
|
389
|
+
}, {
|
|
390
|
+
authOnly: true,
|
|
391
|
+
noticeLines: ["Select an OAuth MCP server and press Enter or ctrl+a to authenticate."],
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
{ overlay: true, overlayOptions: { anchor: "center", width: 82 } },
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
return { configChanged: false };
|
|
399
|
+
}
|
package/config.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import { getAgentPath } from "./agent-dir.
|
|
6
|
-
import type { McpConfig, ServerEntry, McpSettings, ImportKind, ServerProvenance } from "./types.
|
|
5
|
+
import { getAgentPath } from "./agent-dir.ts";
|
|
6
|
+
import type { McpConfig, ServerEntry, McpSettings, ImportKind, ServerProvenance } from "./types.ts";
|
|
7
7
|
|
|
8
8
|
const GENERIC_GLOBAL_CONFIG_PATH = join(homedir(), ".config", "mcp", "mcp.json");
|
|
9
9
|
const PROJECT_CONFIG_NAME = ".mcp.json";
|
|
@@ -105,8 +105,8 @@ export function getProjectPiConfigPath(cwd = process.cwd()): string {
|
|
|
105
105
|
return resolve(cwd, PROJECT_PI_CONFIG_NAME);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
export function getConfigDiscoveryPaths(overridePath?: string): ConfigDiscoveryPath[] {
|
|
109
|
-
return getConfigSources(overridePath).map((source) => ({
|
|
108
|
+
export function getConfigDiscoveryPaths(overridePath?: string, cwd = process.cwd()): ConfigDiscoveryPath[] {
|
|
109
|
+
return getConfigSources(overridePath, cwd).map((source) => ({
|
|
110
110
|
label: source.label,
|
|
111
111
|
path: source.readPath,
|
|
112
112
|
exists: existsSync(source.readPath),
|
|
@@ -180,13 +180,13 @@ export function getMcpDiscoverySummary(overridePath?: string, cwd = process.cwd(
|
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
export function loadMcpConfig(overridePath?: string): McpConfig {
|
|
183
|
+
export function loadMcpConfig(overridePath?: string, cwd = process.cwd()): McpConfig {
|
|
184
184
|
let config: McpConfig = { mcpServers: {} };
|
|
185
185
|
|
|
186
|
-
for (const source of getConfigSources(overridePath)) {
|
|
186
|
+
for (const source of getConfigSources(overridePath, cwd)) {
|
|
187
187
|
const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
|
|
188
188
|
if (!loaded) continue;
|
|
189
|
-
config = mergeConfigs(config, expandImports(loaded));
|
|
189
|
+
config = mergeConfigs(config, expandImports(loaded, cwd));
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
return config;
|
|
@@ -262,12 +262,12 @@ function mergeImports(left: ImportKind[] | undefined, right: ImportKind[] | unde
|
|
|
262
262
|
return [...new Set(merged)];
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
function expandImports(config: McpConfig): McpConfig {
|
|
265
|
+
function expandImports(config: McpConfig, cwd = process.cwd()): McpConfig {
|
|
266
266
|
if (!config.imports?.length) return config;
|
|
267
267
|
|
|
268
268
|
const importedServers: Record<string, ServerEntry> = {};
|
|
269
269
|
for (const importKind of config.imports) {
|
|
270
|
-
const importPath = resolveImportPath(importKind);
|
|
270
|
+
const importPath = resolveImportPath(importKind, cwd);
|
|
271
271
|
if (!importPath) continue;
|
|
272
272
|
|
|
273
273
|
try {
|
|
@@ -591,17 +591,17 @@ export function writeSharedServerEntry(filePath: string, serverName: string, ent
|
|
|
591
591
|
return filePath;
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
-
export function getServerProvenance(overridePath?: string): Map<string, ServerProvenance> {
|
|
594
|
+
export function getServerProvenance(overridePath?: string, cwd = process.cwd()): Map<string, ServerProvenance> {
|
|
595
595
|
const provenance = new Map<string, ServerProvenance>();
|
|
596
596
|
const userPath = getPiGlobalConfigPath(overridePath);
|
|
597
597
|
|
|
598
|
-
for (const source of getConfigSources(overridePath)) {
|
|
598
|
+
for (const source of getConfigSources(overridePath, cwd)) {
|
|
599
599
|
const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
|
|
600
600
|
if (!loaded) continue;
|
|
601
601
|
|
|
602
602
|
if (loaded.imports?.length) {
|
|
603
603
|
for (const importKind of loaded.imports) {
|
|
604
|
-
const importPath = resolveImportPath(importKind);
|
|
604
|
+
const importPath = resolveImportPath(importKind, cwd);
|
|
605
605
|
if (!importPath) continue;
|
|
606
606
|
|
|
607
607
|
try {
|
package/consent-manager.ts
CHANGED
package/direct-tools.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { McpExtensionState } from "./state.
|
|
3
|
-
import type { DirectToolSpec, McpConfig, McpContent } from "./types.
|
|
4
|
-
import type { MetadataCache } from "./metadata-cache.
|
|
5
|
-
import { lazyConnect, getFailureAgeSeconds } from "./init.
|
|
6
|
-
import { isServerCacheValid } from "./metadata-cache.
|
|
7
|
-
import { formatSchema } from "./tool-metadata.
|
|
8
|
-
import { transformMcpContent } from "./tool-registrar.
|
|
9
|
-
import { maybeStartUiSession, type UiSessionRuntime } from "./ui-session.
|
|
10
|
-
import { formatToolName, isToolExcluded } from "./types.
|
|
11
|
-
import { resourceNameToToolName } from "./resource-tools.
|
|
12
|
-
import { authenticate, supportsOAuth } from "./mcp-auth-flow.
|
|
13
|
-
import { formatAuthRequiredMessage } from "./utils.
|
|
1
|
+
import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { McpExtensionState } from "./state.ts";
|
|
3
|
+
import type { DirectToolSpec, McpConfig, McpContent } from "./types.ts";
|
|
4
|
+
import type { MetadataCache } from "./metadata-cache.ts";
|
|
5
|
+
import { lazyConnect, getFailureAgeSeconds } from "./init.ts";
|
|
6
|
+
import { isServerCacheValid } from "./metadata-cache.ts";
|
|
7
|
+
import { formatSchema } from "./tool-metadata.ts";
|
|
8
|
+
import { transformMcpContent } from "./tool-registrar.ts";
|
|
9
|
+
import { maybeStartUiSession, type UiSessionRuntime } from "./ui-session.ts";
|
|
10
|
+
import { formatToolName, isToolExcluded } from "./types.ts";
|
|
11
|
+
import { resourceNameToToolName } from "./resource-tools.ts";
|
|
12
|
+
import { authenticate, supportsOAuth } from "./mcp-auth-flow.ts";
|
|
13
|
+
import { formatAuthRequiredMessage } from "./utils.ts";
|
|
14
14
|
|
|
15
15
|
const BUILTIN_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "mcp"]);
|
|
16
16
|
|
|
@@ -48,7 +48,7 @@ async function attemptDirectAutoAuth(
|
|
|
48
48
|
return { status: "skipped" };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const grantType = definition.oauth
|
|
51
|
+
const grantType = definition.oauth ? definition.oauth.grantType ?? "authorization_code" : "authorization_code";
|
|
52
52
|
if (!state.ui && grantType !== "client_credentials") {
|
|
53
53
|
return {
|
|
54
54
|
status: "failed",
|
|
@@ -260,7 +260,13 @@ export function buildProxyDescription(
|
|
|
260
260
|
return desc;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
type DirectToolExecute =
|
|
263
|
+
type DirectToolExecute = (
|
|
264
|
+
toolCallId: string,
|
|
265
|
+
params: Record<string, unknown>,
|
|
266
|
+
signal: AbortSignal | undefined,
|
|
267
|
+
onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined,
|
|
268
|
+
ctx: ExtensionContext,
|
|
269
|
+
) => Promise<AgentToolResult<Record<string, unknown>>>;
|
|
264
270
|
|
|
265
271
|
export function createDirectToolExecutor(
|
|
266
272
|
getState: () => McpExtensionState | null,
|
package/host-html-template.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UiHostContext, UiResourceContent, UiResourceCsp } from "./types.
|
|
1
|
+
import type { UiHostContext, UiResourceContent, UiResourceCsp } from "./types.ts";
|
|
2
2
|
|
|
3
3
|
// Use locally bundled AppBridge to avoid CDN Zod bundling issues
|
|
4
4
|
const DEFAULT_APP_BRIDGE_MODULE_URL = "/app-bridge.bundle.js";
|
package/index.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import type { ExtensionAPI, ToolInfo } from "@
|
|
2
|
-
import type { McpExtensionState } from "./state.
|
|
1
|
+
import type { ExtensionAPI, ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { McpExtensionState } from "./state.ts";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import { showStatus, showTools, reconnectServers, authenticateServer, openMcpPanel, openMcpSetup } from "./commands.
|
|
5
|
-
import { loadMcpConfig } from "./config.
|
|
6
|
-
import { buildProxyDescription, createDirectToolExecutor, getMissingConfiguredDirectToolServers, resolveDirectTools } from "./direct-tools.
|
|
7
|
-
import { flushMetadataCache, initializeMcp, updateStatusBar } from "./init.
|
|
8
|
-
import { loadMetadataCache } from "./metadata-cache.
|
|
9
|
-
import { executeCall, executeConnect, executeDescribe, executeList, executeSearch, executeStatus, executeUiMessages } from "./proxy-modes.
|
|
10
|
-
import { getConfigPathFromArgv, truncateAtWord } from "./utils.
|
|
11
|
-
import { initializeOAuth, shutdownOAuth } from "./mcp-auth-flow.
|
|
4
|
+
import { showStatus, showTools, reconnectServers, authenticateServer, openMcpAuthPanel, openMcpPanel, openMcpSetup } from "./commands.ts";
|
|
5
|
+
import { loadMcpConfig } from "./config.ts";
|
|
6
|
+
import { buildProxyDescription, createDirectToolExecutor, getMissingConfiguredDirectToolServers, resolveDirectTools } from "./direct-tools.ts";
|
|
7
|
+
import { flushMetadataCache, initializeMcp, updateStatusBar } from "./init.ts";
|
|
8
|
+
import { loadMetadataCache } from "./metadata-cache.ts";
|
|
9
|
+
import { executeCall, executeConnect, executeDescribe, executeList, executeSearch, executeStatus, executeUiMessages } from "./proxy-modes.ts";
|
|
10
|
+
import { getConfigPathFromArgv, truncateAtWord } from "./utils.ts";
|
|
11
|
+
import { initializeOAuth, shutdownOAuth } from "./mcp-auth-flow.ts";
|
|
12
|
+
import { renderMcpToolResult } from "./tool-result-renderer.ts";
|
|
12
13
|
|
|
13
14
|
export default function mcpAdapter(pi: ExtensionAPI) {
|
|
14
15
|
let state: McpExtensionState | null = null;
|
|
@@ -66,13 +67,14 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
66
67
|
|| missingConfiguredDirectToolServers.length > 0;
|
|
67
68
|
|
|
68
69
|
for (const spec of directSpecs) {
|
|
69
|
-
pi.registerTool({
|
|
70
|
+
(pi.registerTool as (tool: unknown) => unknown)({
|
|
70
71
|
name: spec.prefixedName,
|
|
71
72
|
label: `MCP: ${spec.originalName}`,
|
|
72
73
|
description: spec.description || "(no description)",
|
|
73
74
|
promptSnippet: truncateAtWord(spec.description, 100) || `MCP tool from ${spec.serverName}`,
|
|
74
|
-
parameters: Type.Unsafe
|
|
75
|
+
parameters: Type.Unsafe((spec.inputSchema || { type: "object", properties: {} }) as never),
|
|
75
76
|
execute: createDirectToolExecutor(() => state, () => initPromise, spec),
|
|
77
|
+
renderResult: renderMcpToolResult,
|
|
76
78
|
});
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -207,8 +209,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
207
209
|
description: "Authenticate with an MCP server (OAuth)",
|
|
208
210
|
handler: async (args, ctx) => {
|
|
209
211
|
const serverName = args?.trim();
|
|
210
|
-
if (!serverName) {
|
|
211
|
-
if (ctx.hasUI) ctx.ui.notify("Usage: /mcp-auth <server-name>", "error");
|
|
212
|
+
if (!serverName && !ctx.hasUI) {
|
|
212
213
|
return;
|
|
213
214
|
}
|
|
214
215
|
|
|
@@ -226,12 +227,17 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
226
227
|
return;
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
if (!serverName) {
|
|
231
|
+
await openMcpAuthPanel(state, pi, ctx, earlyConfigPath);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
229
235
|
await authenticateServer(serverName, state.config, ctx);
|
|
230
236
|
},
|
|
231
237
|
});
|
|
232
238
|
|
|
233
239
|
if (shouldRegisterProxyTool) {
|
|
234
|
-
pi.registerTool({
|
|
240
|
+
(pi.registerTool as (tool: unknown) => unknown)({
|
|
235
241
|
name: "mcp",
|
|
236
242
|
label: "MCP",
|
|
237
243
|
description: buildProxyDescription(earlyConfig, earlyCache, directSpecs),
|
|
@@ -247,6 +253,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
247
253
|
server: Type.Optional(Type.String({ description: "Filter to specific server (also disambiguates tool calls)" })),
|
|
248
254
|
action: Type.Optional(Type.String({ description: "Action: 'ui-messages' to retrieve prompts/intents from UI sessions" })),
|
|
249
255
|
}),
|
|
256
|
+
renderResult: renderMcpToolResult,
|
|
250
257
|
async execute(_toolCallId, params: {
|
|
251
258
|
tool?: string;
|
|
252
259
|
args?: string;
|
package/init.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
2
|
-
import type { McpExtensionState } from "./state.
|
|
3
|
-
import type { ToolMetadata } from "./types.
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { McpExtensionState } from "./state.ts";
|
|
3
|
+
import type { ToolMetadata } from "./types.ts";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
|
-
import { loadMcpConfig } from "./config.
|
|
6
|
-
import { ConsentManager } from "./consent-manager.
|
|
7
|
-
import { McpLifecycleManager } from "./lifecycle.
|
|
5
|
+
import { loadMcpConfig } from "./config.ts";
|
|
6
|
+
import { ConsentManager } from "./consent-manager.ts";
|
|
7
|
+
import { McpLifecycleManager } from "./lifecycle.ts";
|
|
8
8
|
import {
|
|
9
9
|
computeServerHash,
|
|
10
10
|
getMetadataCachePath,
|
|
@@ -15,13 +15,13 @@ import {
|
|
|
15
15
|
serializeResources,
|
|
16
16
|
serializeTools,
|
|
17
17
|
type ServerCacheEntry,
|
|
18
|
-
} from "./metadata-cache.
|
|
19
|
-
import { McpServerManager } from "./server-manager.
|
|
20
|
-
import { buildToolMetadata, totalToolCount } from "./tool-metadata.
|
|
21
|
-
import { UiResourceHandler } from "./ui-resource-handler.
|
|
22
|
-
import { openUrl, parallelLimit } from "./utils.
|
|
23
|
-
import { logger } from "./logger.
|
|
24
|
-
import { getMissingConfiguredDirectToolServers } from "./direct-tools.
|
|
18
|
+
} from "./metadata-cache.ts";
|
|
19
|
+
import { McpServerManager } from "./server-manager.ts";
|
|
20
|
+
import { buildToolMetadata, totalToolCount } from "./tool-metadata.ts";
|
|
21
|
+
import { UiResourceHandler } from "./ui-resource-handler.ts";
|
|
22
|
+
import { openUrl, parallelLimit } from "./utils.ts";
|
|
23
|
+
import { logger } from "./logger.ts";
|
|
24
|
+
import { getMissingConfiguredDirectToolServers } from "./direct-tools.ts";
|
|
25
25
|
|
|
26
26
|
const FAILURE_BACKOFF_MS = 60 * 1000;
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ export async function initializeMcp(
|
|
|
30
30
|
ctx: ExtensionContext
|
|
31
31
|
): Promise<McpExtensionState> {
|
|
32
32
|
const configPath = pi.getFlag("mcp-config") as string | undefined;
|
|
33
|
-
const config = loadMcpConfig(configPath);
|
|
33
|
+
const config = loadMcpConfig(configPath, ctx.cwd);
|
|
34
34
|
|
|
35
35
|
const manager = new McpServerManager();
|
|
36
36
|
const samplingAutoApprove = config.settings?.samplingAutoApprove === true;
|
|
@@ -61,7 +61,7 @@ export async function initializeMcp(
|
|
|
61
61
|
completedUiSessions: [],
|
|
62
62
|
openBrowser: (url: string) => openUrl(pi, url, process.env.BROWSER),
|
|
63
63
|
ui,
|
|
64
|
-
sendMessage: (message, options) => pi.sendMessage(message, options),
|
|
64
|
+
sendMessage: (message, options) => pi.sendMessage(message as unknown as Parameters<typeof pi.sendMessage>[0], options),
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
const serverEntries = Object.entries(config.mcpServers);
|