pi-mcp-adapter 2.5.4 → 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/mcp-auth-flow.ts CHANGED
@@ -10,24 +10,26 @@ import {
10
10
  } from "@modelcontextprotocol/sdk/client/auth.js"
11
11
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
12
12
  import open from "open"
13
- import { McpOAuthProvider, type McpOAuthConfig } from "./mcp-oauth-provider.js"
13
+ import { McpOAuthProvider, type McpOAuthConfig } from "./mcp-oauth-provider.ts"
14
14
  import {
15
15
  ensureCallbackServer,
16
16
  waitForCallback,
17
17
  cancelPendingCallback,
18
18
  stopCallbackServer,
19
- } from "./mcp-callback-server.js"
19
+ } from "./mcp-callback-server.ts"
20
20
  import {
21
21
  getAuthForUrl,
22
22
  isTokenExpired,
23
23
  hasStoredTokens,
24
24
  clearAllCredentials,
25
+ clearClientInfo,
26
+ clearCodeVerifier,
25
27
  updateOAuthState,
26
28
  getOAuthState,
27
29
  clearOAuthState,
28
30
  type StoredTokens,
29
- } from "./mcp-auth.js"
30
- import type { ServerEntry } from "./types.js"
31
+ } from "./mcp-auth.ts"
32
+ import type { ServerEntry } from "./types.ts"
31
33
 
32
34
  /** Auth status for a server */
33
35
  export type AuthStatus = "authenticated" | "expired" | "not_authenticated"
@@ -74,6 +76,13 @@ export async function startAuth(
74
76
  ): Promise<{ authorizationUrl: string }> {
75
77
  const config = definition ? extractOAuthConfig(definition) : {}
76
78
 
79
+ const storedAuth = await getAuthForUrl(serverName, serverUrl)
80
+ if (storedAuth?.clientInfo && !storedAuth.tokens && !config.clientId) {
81
+ clearClientInfo(serverName)
82
+ clearCodeVerifier(serverName)
83
+ await clearOAuthState(serverName)
84
+ }
85
+
77
86
  if (config.grantType === "client_credentials") {
78
87
  const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
79
88
  onRedirect: async () => {
@@ -92,7 +101,7 @@ export async function startAuth(
92
101
  await ensureCallbackServer({ strictPort: Boolean(config.clientId) })
93
102
 
94
103
  const oauthState = generateState()
95
- await updateOAuthState(serverName, oauthState)
104
+ await updateOAuthState(serverName, oauthState, serverUrl)
96
105
 
97
106
  let capturedUrl: URL | undefined
98
107
  const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
package/mcp-auth.ts CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from 'fs';
13
13
  import { join } from 'path';
14
- import { getAgentPath } from './agent-dir.js';
14
+ import { getAgentPath } from './agent-dir.ts';
15
15
 
16
16
  /** OAuth token storage format */
17
17
  export interface StoredTokens {
@@ -163,6 +163,11 @@ export function updateTokens(
163
163
  serverUrl?: string
164
164
  ): void {
165
165
  const entry = getAuthEntry(serverName) ?? {};
166
+ if (serverUrl && entry.serverUrl !== serverUrl) {
167
+ delete entry.clientInfo;
168
+ delete entry.codeVerifier;
169
+ delete entry.oauthState;
170
+ }
166
171
  entry.tokens = tokens;
167
172
  saveAuthEntry(serverName, entry, serverUrl);
168
173
  }
@@ -176,6 +181,11 @@ export function updateClientInfo(
176
181
  serverUrl?: string
177
182
  ): void {
178
183
  const entry = getAuthEntry(serverName) ?? {};
184
+ if (serverUrl && entry.serverUrl !== serverUrl) {
185
+ delete entry.tokens;
186
+ delete entry.codeVerifier;
187
+ delete entry.oauthState;
188
+ }
179
189
  entry.clientInfo = clientInfo;
180
190
  saveAuthEntry(serverName, entry, serverUrl);
181
191
  }
@@ -183,10 +193,15 @@ export function updateClientInfo(
183
193
  /**
184
194
  * Update code verifier for a server.
185
195
  */
186
- export function updateCodeVerifier(serverName: string, codeVerifier: string): void {
196
+ export function updateCodeVerifier(serverName: string, codeVerifier: string, serverUrl?: string): void {
187
197
  const entry = getAuthEntry(serverName) ?? {};
198
+ if (serverUrl && entry.serverUrl !== serverUrl) {
199
+ delete entry.tokens;
200
+ delete entry.clientInfo;
201
+ delete entry.oauthState;
202
+ }
188
203
  entry.codeVerifier = codeVerifier;
189
- saveAuthEntry(serverName, entry);
204
+ saveAuthEntry(serverName, entry, serverUrl);
190
205
  }
191
206
 
192
207
  /**
@@ -203,10 +218,15 @@ export function clearCodeVerifier(serverName: string): void {
203
218
  /**
204
219
  * Update OAuth state for a server.
205
220
  */
206
- export function updateOAuthState(serverName: string, state: string): void {
221
+ export function updateOAuthState(serverName: string, state: string, serverUrl?: string): void {
207
222
  const entry = getAuthEntry(serverName) ?? {};
223
+ if (serverUrl && entry.serverUrl !== serverUrl) {
224
+ delete entry.tokens;
225
+ delete entry.clientInfo;
226
+ delete entry.codeVerifier;
227
+ }
208
228
  entry.oauthState = state;
209
- saveAuthEntry(serverName, entry);
229
+ saveAuthEntry(serverName, entry, serverUrl);
210
230
  }
211
231
 
212
232
  /**
@@ -11,7 +11,7 @@ import {
11
11
  getConfiguredOAuthCallbackPort,
12
12
  getOAuthCallbackPort,
13
13
  setOAuthCallbackPort,
14
- } from "./mcp-oauth-provider.js"
14
+ } from "./mcp-oauth-provider.ts"
15
15
 
16
16
  // HTML templates for callback responses
17
17
  const HTML_SUCCESS = `<!DOCTYPE html>
@@ -14,7 +14,6 @@ import type {
14
14
  OAuthClientInformationFull,
15
15
  } from "@modelcontextprotocol/sdk/shared/auth.js"
16
16
  import {
17
- getAuthEntry,
18
17
  getAuthForUrl,
19
18
  updateTokens,
20
19
  updateClientInfo,
@@ -25,7 +24,7 @@ import {
25
24
  clearTokens,
26
25
  type StoredTokens,
27
26
  type StoredClientInfo,
28
- } from "./mcp-auth.js"
27
+ } from "./mcp-auth.ts"
29
28
 
30
29
  // Callback server configuration
31
30
  const DEFAULT_OAUTH_CALLBACK_PORT = 19876
@@ -212,7 +211,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
212
211
  throw new Error("redirectToAuthorization is not used for client_credentials flow")
213
212
  }
214
213
  // No saved oauthState means we're on the post-refresh authorize fallback.
215
- const entry = await getAuthEntry(this.serverName)
214
+ const entry = await getAuthForUrl(this.serverName, this.serverUrl)
216
215
  if (!entry?.oauthState) {
217
216
  throw new UnauthorizedError(
218
217
  `Re-authentication required for MCP server: ${this.serverName}`,
@@ -226,7 +225,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
226
225
  * Save the PKCE code verifier.
227
226
  */
228
227
  async saveCodeVerifier(codeVerifier: string): Promise<void> {
229
- updateCodeVerifier(this.serverName, codeVerifier)
228
+ updateCodeVerifier(this.serverName, codeVerifier, this.serverUrl)
230
229
  }
231
230
 
232
231
  /**
@@ -237,7 +236,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
237
236
  if (this.usesClientCredentials) {
238
237
  throw new Error("codeVerifier is not used for client_credentials flow")
239
238
  }
240
- const entry = await getAuthEntry(this.serverName)
239
+ const entry = await getAuthForUrl(this.serverName, this.serverUrl)
241
240
  if (!entry?.codeVerifier) {
242
241
  throw new Error(`No code verifier saved for MCP server: ${this.serverName}`)
243
242
  }
@@ -248,7 +247,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
248
247
  * Save the OAuth state parameter for CSRF protection.
249
248
  */
250
249
  async saveState(state: string): Promise<void> {
251
- updateOAuthState(this.serverName, state)
250
+ updateOAuthState(this.serverName, state, this.serverUrl)
252
251
  }
253
252
 
254
253
  /**
@@ -259,7 +258,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
259
258
  if (this.usesClientCredentials) {
260
259
  throw new Error("state is not used for client_credentials flow")
261
260
  }
262
- const entry = await getAuthEntry(this.serverName)
261
+ const entry = await getAuthForUrl(this.serverName, this.serverUrl)
263
262
  if (!entry?.oauthState) {
264
263
  throw new UnauthorizedError(
265
264
  `Re-authentication required for MCP server: ${this.serverName}`,
package/mcp-panel.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
2
- import { isToolExcluded } from "./types.js";
3
- import type { McpConfig, McpPanelCallbacks, McpPanelResult, ServerProvenance } from "./types.js";
4
- import { resourceNameToToolName } from "./resource-tools.js";
5
- import type { MetadataCache, ServerCacheEntry, CachedTool } from "./metadata-cache.js";
1
+ import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
+ import { isToolExcluded } from "./types.ts";
3
+ import type { McpConfig, McpPanelCallbacks, McpPanelResult, ServerProvenance } from "./types.ts";
4
+ import { resourceNameToToolName } from "./resource-tools.ts";
5
+ import type { MetadataCache, ServerCacheEntry, CachedTool } from "./metadata-cache.ts";
6
6
 
7
7
  interface PanelTheme {
8
8
  border: string;
@@ -120,10 +120,12 @@ class McpPanel {
120
120
  private discardSelected = 1;
121
121
  private importNotice: string | null = null;
122
122
  private authNotice: string | null = null;
123
+ private authInFlight: string | null = null;
123
124
  private inactivityTimeout: ReturnType<typeof setTimeout> | null = null;
124
125
  private visibleItems: VisibleItem[] = [];
125
126
  private tui: { requestRender(): void };
126
127
  private t = DEFAULT_THEME;
128
+ private authOnly: boolean;
127
129
 
128
130
  private static readonly MAX_VISIBLE = 12;
129
131
  private static readonly INACTIVITY_MS = 60_000;
@@ -135,13 +137,15 @@ class McpPanel {
135
137
  private callbacks: McpPanelCallbacks,
136
138
  tui: { requestRender(): void },
137
139
  private done: (result: McpPanelResult) => void,
138
- noticeLines: string[] = [],
140
+ options: { noticeLines?: string[]; authOnly?: boolean } = {},
139
141
  ) {
140
142
  this.tui = tui;
141
- this.noticeLines = noticeLines;
143
+ this.noticeLines = options.noticeLines ?? [];
144
+ this.authOnly = options.authOnly === true;
142
145
  this.prefix = config.settings?.toolPrefix ?? "server";
143
146
 
144
147
  for (const [serverName, definition] of Object.entries(config.mcpServers)) {
148
+ if (this.authOnly && !callbacks.canAuthenticate(serverName)) continue;
145
149
  const prov = provenance.get(serverName);
146
150
  const serverCache = cache?.servers?.[serverName];
147
151
 
@@ -154,7 +158,7 @@ class McpPanel {
154
158
  }
155
159
 
156
160
  const tools: ToolState[] = [];
157
- if (serverCache) {
161
+ if (serverCache && !this.authOnly) {
158
162
  for (const tool of serverCache.tools ?? []) {
159
163
  if (isToolExcluded(tool.name, serverName, this.prefix, definition.excludeTools)) {
160
164
  continue;
@@ -230,6 +234,14 @@ class McpPanel {
230
234
  this.visibleItems = [];
231
235
  for (let si = 0; si < this.servers.length; si++) {
232
236
  const server = this.servers[si];
237
+ if (query && this.authOnly) {
238
+ const score = mode === "name" ? fuzzyScore(query, server.name) : 0;
239
+ if (score > 0) {
240
+ this.visibleItems.push({ type: "server", serverIndex: si });
241
+ }
242
+ continue;
243
+ }
244
+
233
245
  this.visibleItems.push({ type: "server", serverIndex: si });
234
246
  if (server.expanded || query) {
235
247
  for (let ti = 0; ti < server.tools.length; ti++) {
@@ -248,7 +260,7 @@ class McpPanel {
248
260
  }
249
261
  }
250
262
 
251
- if (query) {
263
+ if (query && !this.authOnly) {
252
264
  this.visibleItems = this.visibleItems.filter((item) => {
253
265
  if (item.type === "server") {
254
266
  return this.visibleItems.some(
@@ -284,7 +296,7 @@ class McpPanel {
284
296
  handleInput(data: string): void {
285
297
  this.resetInactivityTimeout();
286
298
  this.importNotice = null;
287
- this.authNotice = null;
299
+ if (!this.authInFlight) this.authNotice = null;
288
300
 
289
301
  if (this.confirmingDiscard) {
290
302
  this.handleDiscardInput(data);
@@ -360,7 +372,7 @@ class McpPanel {
360
372
 
361
373
  if (matchesKey(data, "space")) {
362
374
  const item = this.visibleItems[this.cursorIndex];
363
- if (item) this.toggleItem(item);
375
+ if (item && !this.authOnly) this.toggleItem(item);
364
376
  return;
365
377
  }
366
378
 
@@ -369,8 +381,8 @@ class McpPanel {
369
381
  if (!item) return;
370
382
  const server = this.servers[item.serverIndex];
371
383
  if (item.type === "server") {
372
- if (server.connectionStatus === "needs-auth") {
373
- this.authNotice = `OAuth required — run /mcp-auth ${server.name} after closing this panel`;
384
+ if (this.authOnly || server.connectionStatus === "needs-auth") {
385
+ this.authenticateServer(server);
374
386
  return;
375
387
  }
376
388
  server.expanded = !server.expanded;
@@ -387,6 +399,12 @@ class McpPanel {
387
399
  return;
388
400
  }
389
401
 
402
+ if (matchesKey(data, "ctrl+a")) {
403
+ const item = this.visibleItems[this.cursorIndex];
404
+ if (item) this.authenticateSelectedServer(item);
405
+ return;
406
+ }
407
+
390
408
  if (matchesKey(data, "ctrl+r")) {
391
409
  const item = this.visibleItems[this.cursorIndex];
392
410
  if (!item) return;
@@ -413,6 +431,7 @@ class McpPanel {
413
431
  }
414
432
 
415
433
  if (data === "?") {
434
+ if (this.authOnly) return;
416
435
  this.descSearchActive = true;
417
436
  this.descQuery = "";
418
437
  this.rebuildVisibleItems();
@@ -439,7 +458,39 @@ class McpPanel {
439
458
  }
440
459
  }
441
460
 
461
+ private authenticateSelectedServer(item: VisibleItem): void {
462
+ this.authenticateServer(this.servers[item.serverIndex]);
463
+ }
464
+
465
+ private authenticateServer(server: ServerState): void {
466
+ if (this.authInFlight) return;
467
+ if (!this.callbacks.canAuthenticate(server.name)) {
468
+ this.authNotice = `${server.name} does not use OAuth authentication.`;
469
+ return;
470
+ }
471
+
472
+ this.authInFlight = server.name;
473
+ this.authNotice = `Authenticating ${server.name}...`;
474
+ this.tui.requestRender();
475
+
476
+ this.callbacks.authenticate(server.name).then((result) => {
477
+ server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
478
+ this.authNotice = result.ok
479
+ ? `OAuth finished for ${server.name}. Run reconnect if it is still idle.`
480
+ : `OAuth failed for ${server.name}${result.message ? `: ${result.message}` : ". Check the notification for details."}`;
481
+ this.authInFlight = null;
482
+ this.tui.requestRender();
483
+ }).catch((error) => {
484
+ const message = error instanceof Error ? error.message : String(error);
485
+ server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
486
+ this.authNotice = `OAuth failed for ${server.name}: ${message}`;
487
+ this.authInFlight = null;
488
+ this.tui.requestRender();
489
+ });
490
+ }
491
+
442
492
  private toggleItem(item: VisibleItem): void {
493
+ if (this.authOnly) return;
443
494
  const server = this.servers[item.serverIndex];
444
495
  if (item.type === "server") {
445
496
  const newState = !server.tools.every((t) => t.isDirect);
@@ -550,7 +601,7 @@ class McpPanel {
550
601
  const emptyRow = () => fg(t.border, "│") + " ".repeat(innerW) + fg(t.border, "│");
551
602
  const divider = () => fg(t.border, "├" + "─".repeat(innerW) + "┤");
552
603
 
553
- const titleText = " MCP Servers ";
604
+ const titleText = this.authOnly ? " MCP OAuth " : " MCP Servers ";
554
605
  const borderLen = innerW - visibleWidth(titleText);
555
606
  const leftB = Math.floor(borderLen / 2);
556
607
  const rightB = borderLen - leftB;
@@ -579,7 +630,7 @@ class McpPanel {
579
630
 
580
631
  if (this.servers.length === 0) {
581
632
  lines.push(emptyRow());
582
- lines.push(row(fg(t.hint, italic("No MCP servers configured."))));
633
+ lines.push(row(fg(t.hint, italic(this.authOnly ? "No OAuth-capable MCP servers configured." : "No MCP servers configured."))));
583
634
  lines.push(emptyRow());
584
635
  } else {
585
636
  const maxVis = McpPanel.MAX_VISIBLE;
@@ -631,27 +682,40 @@ class McpPanel {
631
682
  : fg(t.hint, " Keep ");
632
683
  lines.push(row(`Discard unsaved changes? ${discardBtn} ${keepBtn}`));
633
684
  } else {
634
- const directCount = this.servers.reduce((sum, s) => sum + s.tools.filter((t) => t.isDirect).length, 0);
635
- const totalTokens = this.servers.reduce(
636
- (sum, s) => sum + s.tools.filter((t) => t.isDirect).reduce((ts, t) => ts + t.estimatedTokens, 0),
637
- 0,
638
- );
639
- const stats =
640
- directCount > 0 ? `${directCount} direct ~${totalTokens.toLocaleString()} tokens` : "no direct tools";
641
- lines.push(row(fg(t.description, stats + (this.dirty ? fg(t.needsAuth, " (unsaved)") : ""))));
685
+ if (this.authOnly) {
686
+ lines.push(row(fg(t.description, "select a server to authenticate")));
687
+ } else {
688
+ const directCount = this.servers.reduce((sum, s) => sum + s.tools.filter((t) => t.isDirect).length, 0);
689
+ const totalTokens = this.servers.reduce(
690
+ (sum, s) => sum + s.tools.filter((t) => t.isDirect).reduce((ts, t) => ts + t.estimatedTokens, 0),
691
+ 0,
692
+ );
693
+ const stats =
694
+ directCount > 0 ? `${directCount} direct ~${totalTokens.toLocaleString()} tokens` : "no direct tools";
695
+ lines.push(row(fg(t.description, stats + (this.dirty ? fg(t.needsAuth, " (unsaved)") : ""))));
696
+ }
642
697
  }
643
698
 
644
699
  lines.push(emptyRow());
645
- const hints = [
646
- italic("↑↓") + " navigate",
647
- italic("space") + " toggle",
648
- italic("⏎") + " expand",
649
- italic("ctrl+r") + " reconnect",
650
- italic("?") + " desc search",
651
- italic("ctrl+s") + " save",
652
- italic("esc") + " clear/close",
653
- italic("ctrl+c") + " quit",
654
- ];
700
+ const hints = this.authOnly
701
+ ? [
702
+ italic("↑↓") + " navigate",
703
+ italic("⏎") + " auth",
704
+ italic("ctrl+a") + " auth",
705
+ italic("esc") + " clear/close",
706
+ italic("ctrl+c") + " quit",
707
+ ]
708
+ : [
709
+ italic("↑↓") + " navigate",
710
+ italic("space") + " toggle",
711
+ italic("⏎") + " expand/auth",
712
+ italic("ctrl+a") + " auth",
713
+ italic("ctrl+r") + " reconnect",
714
+ italic("?") + " desc search",
715
+ italic("ctrl+s") + " save",
716
+ italic("esc") + " clear/close",
717
+ italic("ctrl+c") + " quit",
718
+ ];
655
719
  const gap = " ";
656
720
  const gapW = 2;
657
721
  const maxW = innerW - 2;
@@ -685,9 +749,10 @@ class McpPanel {
685
749
 
686
750
  const nameStr = isCursor ? bold(fg(t.selected, server.name)) : server.name;
687
751
  const importLabel = server.source === "import" ? fg(t.description, ` (${server.importKind ?? "import"})`) : "";
752
+ const statusLabel = this.renderConnectionStatus(server);
688
753
 
689
- if (!server.hasCachedData) {
690
- return `${prefix} ${nameStr}${importLabel} ${fg(t.description, "(not cached)")}`;
754
+ if (!server.hasCachedData && !this.authOnly) {
755
+ return `${prefix} ${nameStr}${importLabel} ${fg(t.description, "(not cached)")}${statusLabel}`;
691
756
  }
692
757
 
693
758
  const directCount = server.tools.filter((t) => t.isDirect).length;
@@ -709,7 +774,18 @@ class McpPanel {
709
774
  toolInfo = fg(t.description, toolInfo);
710
775
  }
711
776
 
712
- return `${prefix} ${toggleIcon} ${nameStr}${importLabel} ${toolInfo}`;
777
+ return `${prefix} ${toggleIcon} ${nameStr}${importLabel} ${toolInfo}${statusLabel}`;
778
+ }
779
+
780
+ private renderConnectionStatus(server: ServerState): string {
781
+ const t = this.t;
782
+ if (this.authInFlight === server.name) return ` ${fg(t.needsAuth, "authenticating")}`;
783
+ if (server.connectionStatus === "needs-auth") return ` ${fg(t.needsAuth, "needs auth")}`;
784
+ if (server.connectionStatus === "connecting") return ` ${fg(t.needsAuth, "connecting")}`;
785
+ if (server.connectionStatus === "failed") return ` ${fg(t.cancel, "failed")}`;
786
+ if (this.authOnly && server.connectionStatus === "connected") return ` ${fg(t.direct, "connected")}`;
787
+ if (this.authOnly) return ` ${fg(t.description, "idle")}`;
788
+ return "";
713
789
  }
714
790
 
715
791
  private renderToolRow(tool: ToolState, isCursor: boolean, innerW: number): string {
@@ -744,7 +820,7 @@ export function createMcpPanel(
744
820
  callbacks: McpPanelCallbacks,
745
821
  tui: { requestRender(): void },
746
822
  done: (result: McpPanelResult) => void,
747
- options?: { noticeLines?: string[] },
823
+ options?: { noticeLines?: string[]; authOnly?: boolean },
748
824
  ): McpPanel & { dispose(): void } {
749
- return new McpPanel(config, cache, provenance, callbacks, tui, done, options?.noticeLines ?? []);
825
+ return new McpPanel(config, cache, provenance, callbacks, tui, done, options ?? {});
750
826
  }
@@ -1,7 +1,7 @@
1
- import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
2
- import type { ImportKind } from "./types.js";
3
- import type { ConfigWritePreview, McpDiscoverySummary } from "./config.js";
4
- import type { McpOnboardingState } from "./onboarding-state.js";
1
+ import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
+ import type { ImportKind } from "./types.ts";
3
+ import type { ConfigWritePreview, McpDiscoverySummary } from "./config.ts";
4
+ import type { McpOnboardingState } from "./onboarding-state.ts";
5
5
 
6
6
  interface SetupTheme {
7
7
  border: string;
package/metadata-cache.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  // metadata-cache.ts - Persistent MCP metadata cache
2
2
  import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
3
3
  import { dirname } from "node:path";
4
- import { getAgentPath } from "./agent-dir.js";
4
+ import { getAgentPath } from "./agent-dir.ts";
5
5
  import { createHash } from "node:crypto";
6
6
  import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
7
- import type { McpTool, McpResource, ServerEntry, ToolMetadata } from "./types.js";
8
- import { formatToolName, isToolExcluded } from "./types.js";
9
- import { resourceNameToToolName } from "./resource-tools.js";
10
- import { extractToolUiStreamMode, interpolateEnvRecord, resolveBearerToken, resolveConfigPath } from "./utils.js";
7
+ import type { McpTool, McpResource, ServerEntry, ToolMetadata } from "./types.ts";
8
+ import { formatToolName, isToolExcluded } from "./types.ts";
9
+ import { resourceNameToToolName } from "./resource-tools.ts";
10
+ import { extractToolUiStreamMode, interpolateEnvRecord, resolveBearerToken, resolveConfigPath } from "./utils.ts";
11
11
 
12
12
  const CACHE_VERSION = 1;
13
13
  const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
package/npx-resolver.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // npx-resolver.ts - Resolve npx/npm exec binaries to avoid npm parent processes
2
2
  import { existsSync, readFileSync, realpathSync, readdirSync, statSync, writeFileSync, renameSync, mkdirSync, openSync, readSync, closeSync } from "node:fs";
3
3
  import { join, dirname, extname, resolve, sep } from "node:path";
4
- import { getAgentPath } from "./agent-dir.js";
4
+ import { getAgentPath } from "./agent-dir.ts";
5
5
  import { spawn, spawnSync } from "node:child_process";
6
6
 
7
7
  const CACHE_VERSION = 1;
package/oauth-handler.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // oauth-handler.ts - OAuth token management for MCP servers
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { getAgentPath } from "./agent-dir.js";
4
+ import { getAgentPath } from "./agent-dir.ts";
5
5
  import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
6
6
 
7
7
  // Token storage path for a server
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
- import { getAgentPath } from "./agent-dir.js";
3
+ import { getAgentPath } from "./agent-dir.ts";
4
4
 
5
5
  export interface McpOnboardingState {
6
6
  version: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mcp-adapter",
3
- "version": "2.5.4",
3
+ "version": "2.6.0",
4
4
  "description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,6 +55,7 @@
55
55
  "server-manager.ts",
56
56
  "sampling-handler.ts",
57
57
  "tool-registrar.ts",
58
+ "tool-result-renderer.ts",
58
59
  "resource-tools.ts",
59
60
  "lifecycle.ts",
60
61
  "metadata-cache.ts",
@@ -73,22 +74,25 @@
73
74
  "logger.ts",
74
75
  "errors.ts",
75
76
  "app-bridge.bundle.js",
77
+ "banner.png",
76
78
  "README.md",
77
79
  "CHANGELOG.md",
78
80
  "LICENSE"
79
81
  ],
80
82
  "dependencies": {
83
+ "@earendil-works/pi-ai": "^0.74.0",
84
+ "@earendil-works/pi-tui": "^0.74.0",
81
85
  "@modelcontextprotocol/ext-apps": "^1.2.2",
82
86
  "@modelcontextprotocol/sdk": "^1.25.1",
83
- "@mariozechner/pi-ai": "^0.70.2",
84
- "typebox": "^1.1.24",
85
87
  "open": "^10.2.0",
88
+ "typebox": "^1.1.24",
86
89
  "zod": "^3.25.0 || ^4.0.0"
87
90
  },
88
91
  "peerDependencies": {
89
92
  "zod": "^3.25.0 || ^4.0.0"
90
93
  },
91
94
  "devDependencies": {
95
+ "@earendil-works/pi-coding-agent": "^0.74.0",
92
96
  "@types/bun": "^1.0.0",
93
97
  "@types/node": "^20.0.0",
94
98
  "@types/open": "^6.2.1",
package/proxy-modes.ts CHANGED
@@ -1,13 +1,13 @@
1
- import type { AgentToolResult, ToolInfo } from "@mariozechner/pi-coding-agent";
2
- import type { McpExtensionState } from "./state.js";
3
- import type { ToolMetadata, McpContent } from "./types.js";
4
- import { getServerPrefix, parseUiPromptHandoff } from "./types.js";
5
- import { lazyConnect, updateServerMetadata, updateMetadataCache, getFailureAgeSeconds, updateStatusBar } from "./init.js";
6
- import { buildToolMetadata, getToolNames, findToolByName, formatSchema } from "./tool-metadata.js";
7
- import { transformMcpContent } from "./tool-registrar.js";
8
- import { maybeStartUiSession, type UiSessionRuntime } from "./ui-session.js";
9
- import { formatAuthRequiredMessage, truncateAtWord } from "./utils.js";
10
- import { authenticate, supportsOAuth } from "./mcp-auth-flow.js";
1
+ import type { AgentToolResult, ToolInfo } from "@earendil-works/pi-coding-agent";
2
+ import type { McpExtensionState } from "./state.ts";
3
+ import type { ToolMetadata, McpContent } from "./types.ts";
4
+ import { getServerPrefix, parseUiPromptHandoff } from "./types.ts";
5
+ import { lazyConnect, updateServerMetadata, updateMetadataCache, getFailureAgeSeconds, updateStatusBar } from "./init.ts";
6
+ import { buildToolMetadata, getToolNames, findToolByName, formatSchema } from "./tool-metadata.ts";
7
+ import { transformMcpContent } from "./tool-registrar.ts";
8
+ import { maybeStartUiSession, type UiSessionRuntime } from "./ui-session.ts";
9
+ import { formatAuthRequiredMessage, truncateAtWord } from "./utils.ts";
10
+ import { authenticate, supportsOAuth } from "./mcp-auth-flow.ts";
11
11
 
12
12
  type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
13
13
 
@@ -45,7 +45,7 @@ async function attemptAutoAuth(
45
45
  return { status: "skipped" };
46
46
  }
47
47
 
48
- const grantType = definition.oauth?.grantType ?? "authorization_code";
48
+ const grantType = definition.oauth ? definition.oauth.grantType ?? "authorization_code" : "authorization_code";
49
49
  if (!state.ui && grantType !== "client_credentials") {
50
50
  return {
51
51
  status: "failed",
@@ -1,6 +1,6 @@
1
- import { complete, type Api, type AssistantMessage, type Message, type Model, type TextContent } from "@mariozechner/pi-ai";
2
- import { truncateAtWord } from "./utils.js";
3
- import type { ExtensionUIContext, ModelRegistry } from "@mariozechner/pi-coding-agent";
1
+ import { complete, type Api, type AssistantMessage, type Message, type Model, type TextContent } from "@earendil-works/pi-ai";
2
+ import { truncateAtWord } from "./utils.ts";
3
+ import type { ExtensionUIContext, ModelRegistry } from "@earendil-works/pi-coding-agent";
4
4
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
5
  import {
6
6
  CreateMessageRequestSchema,
@@ -147,10 +147,11 @@ async function resolveSamplingModel(
147
147
  const errors: string[] = [];
148
148
  for (const model of candidates) {
149
149
  const auth = await options.modelRegistry.getApiKeyAndHeaders(model);
150
- if (auth.ok) {
151
- return { model, apiKey: auth.apiKey, headers: auth.headers };
150
+ if (auth.ok === false) {
151
+ errors.push(`${model.provider}/${model.id}: ${auth.error}`);
152
+ continue;
152
153
  }
153
- errors.push(`${model.provider}/${model.id}: ${auth.error}`);
154
+ return { model, apiKey: auth.apiKey, headers: auth.headers };
154
155
  }
155
156
 
156
157
  if (errors.length > 0) {