@fitchmultz/pi-mcp-adapter 8.0.6 → 8.0.8

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +142 -8
  2. package/OAUTH.md +13 -16
  3. package/README.md +8 -8
  4. package/cli.js +67 -28
  5. package/dist/abort.js +18 -9
  6. package/dist/client-operations.js +4 -2
  7. package/dist/commands.js +77 -43
  8. package/dist/config.js +194 -105
  9. package/dist/connection-runtime.d.ts +2 -2
  10. package/dist/connection-runtime.js +29 -18
  11. package/dist/consent-manager.d.ts +2 -2
  12. package/dist/consent-manager.js +5 -2
  13. package/dist/direct-tools.d.ts +1 -1
  14. package/dist/direct-tools.js +13 -7
  15. package/dist/elicitation-handler.d.ts +2 -3
  16. package/dist/elicitation-handler.js +113 -58
  17. package/dist/gateway-arguments.d.ts +1 -1
  18. package/dist/gateway-arguments.js +90 -23
  19. package/dist/glimpse-ui.js +10 -5
  20. package/dist/host-html-template.d.ts +3 -1
  21. package/dist/host-html-template.js +9 -12
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +264 -119
  24. package/dist/init.d.ts +1 -3
  25. package/dist/init.js +91 -51
  26. package/dist/legacy-migration.d.ts +1 -1
  27. package/dist/legacy-migration.js +17 -12
  28. package/dist/lifecycle.d.ts +3 -3
  29. package/dist/lifecycle.js +33 -17
  30. package/dist/logger.d.ts +2 -2
  31. package/dist/logger.js +4 -2
  32. package/dist/mcp-auth-flow.js +150 -58
  33. package/dist/mcp-auth.d.ts +7 -7
  34. package/dist/mcp-auth.js +189 -138
  35. package/dist/mcp-callback-server.js +19 -8
  36. package/dist/mcp-code.js +168 -54
  37. package/dist/mcp-keyring-helper.cjs +57 -23
  38. package/dist/mcp-oauth-provider.d.ts +6 -7
  39. package/dist/mcp-oauth-provider.js +146 -72
  40. package/dist/mcp-oauth-runtime.js +29 -16
  41. package/dist/mcp-output-guard.js +193 -65
  42. package/dist/mcp-output-preview.js +41 -20
  43. package/dist/mcp-panel.d.ts +9 -9
  44. package/dist/mcp-panel.js +141 -70
  45. package/dist/mcp-script-worker.mjs +135 -43
  46. package/dist/mcp-setup-panel.d.ts +8 -8
  47. package/dist/mcp-setup-panel.js +130 -44
  48. package/dist/mcp-status.js +14 -7
  49. package/dist/mcp-trace.d.ts +10 -5
  50. package/dist/mcp-trace.js +127 -61
  51. package/dist/metadata-cache.d.ts +3 -4
  52. package/dist/metadata-cache.js +83 -43
  53. package/dist/npx-resolver.js +101 -60
  54. package/dist/onboarding-state.js +4 -2
  55. package/dist/plain-result-reader.js +78 -31
  56. package/dist/prompts.js +36 -20
  57. package/dist/proxy-modes.js +670 -193
  58. package/dist/resource-tools.js +30 -9
  59. package/dist/runtime-owner.js +36 -18
  60. package/dist/sampling-handler.d.ts +1 -2
  61. package/dist/sampling-handler.js +41 -21
  62. package/dist/search-ranking.d.ts +1 -1
  63. package/dist/search-ranking.js +67 -34
  64. package/dist/server-manager.d.ts +11 -11
  65. package/dist/server-manager.js +283 -166
  66. package/dist/session-recovery.js +113 -61
  67. package/dist/tool-approval.js +9 -3
  68. package/dist/tool-loader.js +154 -55
  69. package/dist/tool-metadata.d.ts +1 -1
  70. package/dist/tool-metadata.js +51 -23
  71. package/dist/tool-registrar.js +32 -10
  72. package/dist/tool-result-renderer.js +58 -33
  73. package/dist/types.d.ts +2 -2
  74. package/dist/types.js +39 -18
  75. package/dist/ui-message-history.js +33 -13
  76. package/dist/ui-resource-handler.d.ts +3 -3
  77. package/dist/ui-resource-handler.js +37 -21
  78. package/dist/ui-server.js +101 -43
  79. package/dist/ui-session.js +66 -29
  80. package/dist/ui-stream-types.js +14 -4
  81. package/dist/ui-tool-visibility.js +12 -6
  82. package/dist/unix-socket-transport.d.ts +1 -2
  83. package/dist/unix-socket-transport.js +19 -12
  84. package/dist/utils.d.ts +2 -0
  85. package/dist/utils.js +60 -35
  86. package/package.json +28 -2
  87. package/skills/mcp-scripting/SKILL.md +6 -6
@@ -1,13 +1,13 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
- import { getServerPrefix, isServerDisabled, isNonInteractiveOAuth, parseUiPromptHandoff, resolveToolPrefix } from "./types.js";
3
- import { lazyConnect, markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateServerMetadata, updateMetadataCache, getFailureAgeSeconds, updateStatusBar, clearFailure, recordFailure } from "./init.js";
2
+ import { getServerPrefix, isServerDisabled, isNonInteractiveOAuth, parseUiPromptHandoff, resolveToolPrefix, } from "./types.js";
3
+ import { lazyConnect, markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateServerMetadata, updateMetadataCache, getFailureAgeSeconds, updateStatusBar, clearFailure, recordFailure, } from "./init.js";
4
4
  import { abortable, throwIfAborted } from "./abort.js";
5
5
  import { combineAbortSignals, isAbortError } from "./runtime-owner.js";
6
- import { buildToolMetadata, catalogCoverage, findToolByName, formatSchema, toToolDescriptor } from "./tool-metadata.js";
6
+ import { buildToolMetadata, catalogCoverage, findToolByName, formatSchema, toToolDescriptor, } from "./tool-metadata.js";
7
7
  import { reconstructPromptMetadata } from "./metadata-cache.js";
8
8
  import { renderMcpResultContent } from "./tool-registrar.js";
9
- import { guardMcpOutput, guardedMcpDetails, retainMcpResult, resolveMcpOutputGuardOptions, formatMcpResultReference, saveMcpOutputStream } from "./mcp-output-guard.js";
10
- import { formatAuthRequiredMessage, formatMcpStatus, resolveServerUrl, truncateAtWord } from "./utils.js";
9
+ import { guardMcpOutput, guardedMcpDetails, retainMcpResult, resolveMcpOutputGuardOptions, formatMcpResultReference, saveMcpOutputStream, } from "./mcp-output-guard.js";
10
+ import { formatAuthRequiredMessage, formatMcpStatus, resolveServerUrl, truncateAtWord, } from "./utils.js";
11
11
  import { supportsOAuth } from "./mcp-oauth-runtime.js";
12
12
  import { paginate, rankSuggestions, rankToolMatches } from "./search-ranking.js";
13
13
  import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.js";
@@ -22,8 +22,9 @@ function disabledResult(mode, serverName) {
22
22
  }
23
23
  function getAuthRequiredMessage(state, serverName, defaultMessage = `Server "${serverName}" requires OAuth authentication. If mcp is inactive, first mcp_search({ enable: ["gateway"] }). Run mcp({ action: "auth-start", server: "${serverName}" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`) {
24
24
  const oauth = state.config.mcpServers[serverName]?.oauth;
25
- if (oauth && oauth.crossAppAccess)
25
+ if (oauth && oauth.crossAppAccess) {
26
26
  defaultMessage = `Server "${serverName}" requires enterprise authorization. Check oauth.crossAppAccess IdP configuration and ID-token source, then retry.`;
27
+ }
27
28
  return formatAuthRequiredMessage(state.config, serverName, defaultMessage);
28
29
  }
29
30
  function getAuthFailedMessage(state, serverName, message) {
@@ -37,8 +38,9 @@ function getAuthFailedMessage(state, serverName, message) {
37
38
  function getRedirectPort(authorizationUrl) {
38
39
  try {
39
40
  const redirectUri = new URL(authorizationUrl).searchParams.get("redirect_uri");
40
- if (!redirectUri)
41
+ if (!redirectUri) {
41
42
  return undefined;
43
+ }
42
44
  const port = Number.parseInt(new URL(redirectUri).port, 10);
43
45
  return Number.isInteger(port) ? port : undefined;
44
46
  }
@@ -66,7 +68,9 @@ function formatManualAuthInstructions(serverName, authorizationUrl) {
66
68
  "",
67
69
  'You can also pass just the `code` query parameter as `args: { code: "PASTE_CODE_HERE" }`.',
68
70
  portNote.trimEnd(),
69
- ].filter(Boolean).join("\n");
71
+ ]
72
+ .filter(Boolean)
73
+ .join("\n");
70
74
  }
71
75
  async function attemptAutoAuth(state, serverName, signal) {
72
76
  if (state.config.settings?.autoAuth !== true) {
@@ -102,7 +106,10 @@ async function attemptAutoAuth(state, serverName, signal) {
102
106
  }
103
107
  else {
104
108
  if (signal) {
105
- await authenticate(serverName, serverUrl, definition, { signal, runtime: state.oauthRuntime });
109
+ await authenticate(serverName, serverUrl, definition, {
110
+ signal,
111
+ runtime: state.oauthRuntime,
112
+ });
106
113
  }
107
114
  else {
108
115
  await authenticate(serverName, serverUrl, definition, { runtime: state.oauthRuntime });
@@ -111,8 +118,9 @@ async function attemptAutoAuth(state, serverName, signal) {
111
118
  return { status: "success" };
112
119
  }
113
120
  catch (error) {
114
- if (isAbortError(error, signal))
121
+ if (isAbortError(error, signal)) {
115
122
  throw error;
123
+ }
116
124
  const message = error instanceof Error ? error.message : String(error);
117
125
  return {
118
126
  status: "failed",
@@ -124,8 +132,9 @@ async function* uiSessionText(session) {
124
132
  yield `\n## ${session.serverName} / ${session.toolName} (${session.completedAt.toLocaleTimeString()}, ${session.reason})`;
125
133
  let heading = false;
126
134
  for await (const prompt of uiMessages(session.messages, "prompts")) {
127
- if (parseUiPromptHandoff(prompt))
135
+ if (parseUiPromptHandoff(prompt)) {
128
136
  continue;
137
+ }
129
138
  if (!heading) {
130
139
  yield "\n\n### Prompts:";
131
140
  heading = true;
@@ -143,8 +152,9 @@ async function* uiSessionText(session) {
143
152
  }
144
153
  for await (const prompt of uiMessages(session.messages, "prompts")) {
145
154
  const handoff = parseUiPromptHandoff(prompt);
146
- if (!handoff)
155
+ if (!handoff) {
147
156
  continue;
157
+ }
148
158
  if (!heading) {
149
159
  yield "\n\n### Intents:";
150
160
  heading = true;
@@ -153,8 +163,9 @@ async function* uiSessionText(session) {
153
163
  }
154
164
  if (session.messages.contexts.length) {
155
165
  yield "\n\n### Context updates:";
156
- for (const context of session.messages.contexts)
166
+ for (const context of session.messages.contexts) {
157
167
  yield `\n- ${context.summary}${context.truncated ? " (truncated)" : ""}`;
168
+ }
158
169
  }
159
170
  heading = false;
160
171
  for await (const notification of uiMessages(session.messages, "notifications")) {
@@ -169,30 +180,69 @@ export async function executeUiMessages(state) {
169
180
  const pending = [...state.completedUiSessions];
170
181
  const options = resolveMcpOutputGuardOptions(state.config?.settings, state.outputDirectory);
171
182
  // Bound both inline output and details. Large retrieval streams; it never flattens all events.
172
- const size = pending.reduce((bytes, session) => bytes + (session.messages.historyBytes ?? 0) + Buffer.byteLength(JSON.stringify(session.messages)), 0);
183
+ const size = pending.reduce((bytes, session) => bytes +
184
+ (session.messages.historyBytes ?? 0) +
185
+ Buffer.byteLength(JSON.stringify(session.messages)), 0);
173
186
  const large = size > Math.min(options.maxBytes, options.detailsMaxBytes);
174
- const warnings = pending.flatMap(session => session.messages.historyError ? [session.messages.historyError] : []);
187
+ const warnings = pending.flatMap((session) => session.messages.historyError ? [session.messages.historyError] : []);
175
188
  const paged = async () => {
176
189
  async function* output() {
177
190
  yield `UI Session Messages (${pending.length} sessions):\n`;
178
- for (const session of pending)
191
+ for (const session of pending) {
179
192
  yield* uiSessionText(session);
193
+ }
180
194
  }
181
195
  const artifact = await saveMcpOutputStream(output(), state.outputDirectory);
182
- if (!artifact.path)
183
- return { content: [{ type: "text", text: `UI messages remain available; retrieval could not be saved: ${artifact.error}` }], isError: true, details: { error: "ui_history_delivery_failed", cleared: false } };
196
+ if (!artifact.path) {
197
+ return {
198
+ content: [
199
+ {
200
+ type: "text",
201
+ text: `UI messages remain available; retrieval could not be saved: ${artifact.error}`,
202
+ },
203
+ ],
204
+ isError: true,
205
+ details: { error: "ui_history_delivery_failed", cleared: false },
206
+ };
207
+ }
184
208
  // Newly completed sessions during I/O are not part of this drain.
185
- state.completedUiSessions = state.completedUiSessions.filter(session => !pending.includes(session));
186
- return { content: [{ type: "text", text: `UI Session Messages (${pending.length} sessions). ${formatMcpResultReference(artifact.path)}${warnings.length ? `\nHistory persistence warning: ${warnings.join("; ")}` : ""}` }], details: { sessions: pending.length, resultRef: artifact.path, historyRefs: pending.flatMap(session => session.messages.historyRef ? [session.messages.historyRef] : []), cleared: true } };
209
+ state.completedUiSessions = state.completedUiSessions.filter((session) => !pending.includes(session));
210
+ return {
211
+ content: [
212
+ {
213
+ type: "text",
214
+ text: `UI Session Messages (${pending.length} sessions). ${formatMcpResultReference(artifact.path)}${warnings.length ? `\nHistory persistence warning: ${warnings.join("; ")}` : ""}`,
215
+ },
216
+ ],
217
+ details: {
218
+ sessions: pending.length,
219
+ resultRef: artifact.path,
220
+ historyRefs: pending.flatMap((session) => session.messages.historyRef ? [session.messages.historyRef] : []),
221
+ cleared: true,
222
+ },
223
+ };
187
224
  };
188
- if (large)
225
+ if (large) {
189
226
  return paged();
227
+ }
190
228
  let sessions;
191
229
  try {
192
- sessions = await Promise.all(pending.map(async (session) => ({ ...session, messages: await materializeUiMessages(session.messages) })));
230
+ sessions = await Promise.all(pending.map(async (session) => ({
231
+ ...session,
232
+ messages: await materializeUiMessages(session.messages),
233
+ })));
193
234
  }
194
235
  catch (error) {
195
- return { content: [{ type: "text", text: `UI messages remain available; history could not be read: ${String(error)}` }], isError: true, details: { error: "ui_history_delivery_failed", cleared: false } };
236
+ return {
237
+ content: [
238
+ {
239
+ type: "text",
240
+ text: `UI messages remain available; history could not be read: ${String(error)}`,
241
+ },
242
+ ],
243
+ isError: true,
244
+ details: { error: "ui_history_delivery_failed", cleared: false },
245
+ };
196
246
  }
197
247
  if (sessions.length === 0) {
198
248
  return {
@@ -230,7 +280,7 @@ export async function executeUiMessages(state) {
230
280
  ...session.messages.intents,
231
281
  ...session.messages.prompts
232
282
  .map((prompt) => parseUiPromptHandoff(prompt))
233
- .filter((handoff) => !!handoff)
283
+ .filter((handoff) => Boolean(handoff))
234
284
  .map((handoff) => ({ intent: handoff.intent, params: handoff.params })),
235
285
  ];
236
286
  if (intentsForSession.length > 0) {
@@ -256,7 +306,13 @@ export async function executeUiMessages(state) {
256
306
  }
257
307
  const count = sessions.length;
258
308
  const inline = {
259
- content: [{ type: "text", text: output.join("\n") + (warnings.length ? `\nHistory persistence warning: ${warnings.join("; ")}` : "") }],
309
+ content: [
310
+ {
311
+ type: "text",
312
+ text: output.join("\n") +
313
+ (warnings.length ? `\nHistory persistence warning: ${warnings.join("; ")}` : ""),
314
+ },
315
+ ],
260
316
  details: {
261
317
  sessions: count,
262
318
  prompts: allPrompts,
@@ -266,10 +322,13 @@ export async function executeUiMessages(state) {
266
322
  cleared: true,
267
323
  },
268
324
  };
269
- const text = inline.content.map(block => block.type === "text" ? block.text : "").join("\n");
270
- if (Buffer.byteLength(JSON.stringify(inline.details)) > options.detailsMaxBytes || Buffer.byteLength(text) > options.maxBytes || text.split("\n").length > options.maxLines)
325
+ const text = inline.content.map((block) => (block.type === "text" ? block.text : "")).join("\n");
326
+ if (Buffer.byteLength(JSON.stringify(inline.details)) > options.detailsMaxBytes ||
327
+ Buffer.byteLength(text) > options.maxBytes ||
328
+ text.split("\n").length > options.maxLines) {
271
329
  return paged();
272
- state.completedUiSessions = state.completedUiSessions.filter(session => !pending.includes(session));
330
+ }
331
+ state.completedUiSessions = state.completedUiSessions.filter((session) => !pending.includes(session));
273
332
  return inline;
274
333
  }
275
334
  export function executeStatus(state) {
@@ -279,7 +338,7 @@ export function executeStatus(state) {
279
338
  const disabled = isServerDisabled(definition);
280
339
  const connection = disabled ? undefined : state.manager.getConnection(name);
281
340
  const metadata = disabled ? undefined : state.toolMetadata.get(name);
282
- const toolCount = metadata?.filter(tool => !tool.resourceUri).length ?? 0;
341
+ const toolCount = metadata?.filter((tool) => !tool.resourceUri).length ?? 0;
283
342
  const failedAgo = disabled ? null : getFailureAgeSeconds(state, name);
284
343
  let status = disabled ? "disabled" : "not connected";
285
344
  if (!disabled && connection?.status === "connected") {
@@ -296,14 +355,15 @@ export function executeStatus(state) {
296
355
  }
297
356
  servers.push({ name, status, toolCount, failedAgo, ...(disabled ? { disabled: true } : {}) });
298
357
  }
299
- const disabledCount = servers.filter(s => s.disabled).length;
300
- const enabledServers = servers.filter(s => !s.disabled);
358
+ const disabledCount = servers.filter((s) => s.disabled).length;
359
+ const enabledServers = servers.filter((s) => !s.disabled);
301
360
  const totalTools = enabledServers.reduce((sum, s) => sum + s.toolCount, 0);
302
- const connectedCount = enabledServers.filter(s => s.status === "connected").length;
361
+ const connectedCount = enabledServers.filter((s) => s.status === "connected").length;
303
362
  const coverage = catalogCoverage(state);
304
363
  let text = `MCP: ${connectedCount}/${enabledServers.length} connected, ${totalTools} tools available (calls connect lazily)`;
305
- if (disabledCount > 0)
364
+ if (disabledCount > 0) {
306
365
  text += ` (${disabledCount} disabled)`;
366
+ }
307
367
  text += "\n\n";
308
368
  for (const server of servers) {
309
369
  if (server.disabled) {
@@ -330,8 +390,9 @@ export function executeStatus(state) {
330
390
  }
331
391
  if (servers.length > 0) {
332
392
  text += `\nmcp_search({ query: "...", server: "name" }) to discover tools; mcp({ action: "resources", server: "name" }) for resources.`;
333
- if (!coverage.complete)
393
+ if (!coverage.complete) {
334
394
  text += `\nPartial catalog: ${coverage.unknownServers.length} undiscovered server(s).`;
395
+ }
335
396
  }
336
397
  return {
337
398
  content: [{ type: "text", text: text.trim() }],
@@ -345,42 +406,69 @@ export async function executeAuthStart(state, serverName, signal) {
345
406
  const definition = state.config.mcpServers[serverName];
346
407
  if (!definition) {
347
408
  return {
348
- content: [{ type: "text", text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).` }],
409
+ content: [
410
+ {
411
+ type: "text",
412
+ text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).`,
413
+ },
414
+ ],
349
415
  details: { mode: "auth-start", error: "not_found", server: serverName },
350
416
  };
351
417
  }
352
- if (isServerDisabled(definition))
418
+ if (isServerDisabled(definition)) {
353
419
  return disabledResult("auth-start", serverName);
420
+ }
354
421
  try {
355
422
  const serverUrl = resolveServerUrl(definition);
356
423
  if (!serverUrl || !supportsOAuth(definition)) {
357
424
  return {
358
- content: [{ type: "text", text: `Server "${serverName}" is not configured for OAuth over HTTP.` }],
425
+ content: [
426
+ {
427
+ type: "text",
428
+ text: `Server "${serverName}" is not configured for OAuth over HTTP.`,
429
+ },
430
+ ],
359
431
  details: { mode: "auth-start", error: "oauth_not_supported", server: serverName },
360
432
  };
361
433
  }
362
434
  const { authorizationUrl } = state.authStorageOptions
363
435
  ? ownedSignal
364
- ? await startAuth(serverName, serverUrl, definition, { authStorageOptions: state.authStorageOptions, signal: ownedSignal, runtime: state.oauthRuntime })
365
- : await startAuth(serverName, serverUrl, definition, { authStorageOptions: state.authStorageOptions, runtime: state.oauthRuntime })
436
+ ? await startAuth(serverName, serverUrl, definition, {
437
+ authStorageOptions: state.authStorageOptions,
438
+ signal: ownedSignal,
439
+ runtime: state.oauthRuntime,
440
+ })
441
+ : await startAuth(serverName, serverUrl, definition, {
442
+ authStorageOptions: state.authStorageOptions,
443
+ runtime: state.oauthRuntime,
444
+ })
366
445
  : ownedSignal
367
- ? await startAuth(serverName, serverUrl, definition, { signal: ownedSignal, runtime: state.oauthRuntime })
446
+ ? await startAuth(serverName, serverUrl, definition, {
447
+ signal: ownedSignal,
448
+ runtime: state.oauthRuntime,
449
+ })
368
450
  : await startAuth(serverName, serverUrl, definition, { runtime: state.oauthRuntime });
369
451
  if (!authorizationUrl) {
370
452
  return {
371
- content: [{ type: "text", text: `OAuth authentication successful for "${serverName}".` }],
453
+ content: [
454
+ { type: "text", text: `OAuth authentication successful for "${serverName}".` },
455
+ ],
372
456
  details: { mode: "auth-start", server: serverName, authenticated: true },
373
457
  };
374
458
  }
375
459
  return {
376
- content: [{ type: "text", text: formatManualAuthInstructions(serverName, authorizationUrl) }],
460
+ content: [
461
+ { type: "text", text: formatManualAuthInstructions(serverName, authorizationUrl) },
462
+ ],
377
463
  details: { mode: "auth-start", server: serverName, authorizationUrl },
378
464
  };
379
465
  }
380
466
  catch (error) {
381
467
  const message = error instanceof Error ? error.message : String(error);
382
468
  return {
383
- content: [{ type: "text", text: `Failed to start OAuth for "${serverName}": ${message}` }],
469
+ content: [
470
+ { type: "text", text: `Failed to start OAuth for "${serverName}": ${message}` },
471
+ ],
384
472
  details: { mode: "auth-start", error: "auth_start_failed", server: serverName, message },
385
473
  };
386
474
  }
@@ -392,62 +480,111 @@ export async function executeAuthComplete(state, serverName, input, signal) {
392
480
  const definition = state.config.mcpServers[serverName];
393
481
  if (!definition) {
394
482
  return {
395
- content: [{ type: "text", text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).` }],
483
+ content: [
484
+ {
485
+ type: "text",
486
+ text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).`,
487
+ },
488
+ ],
396
489
  details: { mode: "auth-complete", error: "not_found", server: serverName },
397
490
  };
398
491
  }
399
- if (isServerDisabled(definition))
492
+ if (isServerDisabled(definition)) {
400
493
  return disabledResult("auth-complete", serverName);
494
+ }
401
495
  try {
402
496
  const previousConnection = state.manager.getConnection(serverName);
403
497
  const status = state.authStorageOptions
404
498
  ? ownedSignal
405
- ? await completeAuthFromInput(serverName, input, { authStorageOptions: state.authStorageOptions, signal: ownedSignal, runtime: state.oauthRuntime })
406
- : await completeAuthFromInput(serverName, input, { authStorageOptions: state.authStorageOptions, runtime: state.oauthRuntime })
499
+ ? await completeAuthFromInput(serverName, input, {
500
+ authStorageOptions: state.authStorageOptions,
501
+ signal: ownedSignal,
502
+ runtime: state.oauthRuntime,
503
+ })
504
+ : await completeAuthFromInput(serverName, input, {
505
+ authStorageOptions: state.authStorageOptions,
506
+ runtime: state.oauthRuntime,
507
+ })
407
508
  : ownedSignal
408
- ? await completeAuthFromInput(serverName, input, { signal: ownedSignal, runtime: state.oauthRuntime })
509
+ ? await completeAuthFromInput(serverName, input, {
510
+ signal: ownedSignal,
511
+ runtime: state.oauthRuntime,
512
+ })
409
513
  : await completeAuthFromInput(serverName, input, { runtime: state.oauthRuntime });
410
514
  if (status !== "authenticated") {
411
515
  return {
412
- content: [{ type: "text", text: `OAuth authentication did not complete for "${serverName}".` }],
516
+ content: [
517
+ {
518
+ type: "text",
519
+ text: `OAuth authentication did not complete for "${serverName}".`,
520
+ },
521
+ ],
413
522
  details: { mode: "auth-complete", error: "not_authenticated", server: serverName, status },
414
523
  };
415
524
  }
416
- if (previousConnection)
525
+ if (previousConnection) {
417
526
  state.manager.retire(serverName, previousConnection);
527
+ }
418
528
  clearFailure(state, serverName);
419
529
  updateStatusBar(state);
420
530
  return {
421
- content: [{ type: "text", text: `OAuth authentication successful for "${serverName}". Run mcp({ action: "connect", server: "${serverName}" }) to connect with the new token.` }],
531
+ content: [
532
+ {
533
+ type: "text",
534
+ text: `OAuth authentication successful for "${serverName}". Run mcp({ action: "connect", server: "${serverName}" }) to connect with the new token.`,
535
+ },
536
+ ],
422
537
  details: { mode: "auth-complete", server: serverName, authenticated: true },
423
538
  };
424
539
  }
425
540
  catch (error) {
426
541
  const message = error instanceof Error ? error.message : String(error);
427
542
  return {
428
- content: [{ type: "text", text: `Failed to complete OAuth for "${serverName}": ${message}` }],
429
- details: { mode: "auth-complete", error: "auth_complete_failed", server: serverName, message },
543
+ content: [
544
+ { type: "text", text: `Failed to complete OAuth for "${serverName}": ${message}` },
545
+ ],
546
+ details: {
547
+ mode: "auth-complete",
548
+ error: "auth_complete_failed",
549
+ server: serverName,
550
+ message,
551
+ },
430
552
  };
431
553
  }
432
554
  }
433
555
  export function executeDescribe(state, toolName, serverFilter) {
434
- if (serverFilter && isServerDisabled(state.config.mcpServers[serverFilter]))
556
+ if (serverFilter && isServerDisabled(state.config.mcpServers[serverFilter])) {
435
557
  return disabledResult("describe", serverFilter);
436
- const catalog = [...state.toolMetadata].flatMap(([server, tools]) => (!serverFilter || serverFilter === server) && state.config.mcpServers[server] && !isServerDisabled(state.config.mcpServers[server])
437
- ? tools.map(tool => ({ server, tool })) : []);
438
- const originals = serverFilter ? catalog.filter(({ tool }) => tool.originalName === toolName) : [];
558
+ }
559
+ const catalog = [...state.toolMetadata].flatMap(([server, tools]) => (!serverFilter || serverFilter === server) &&
560
+ state.config.mcpServers[server] &&
561
+ !isServerDisabled(state.config.mcpServers[server])
562
+ ? tools.map((tool) => ({ server, tool }))
563
+ : []);
564
+ const originals = serverFilter
565
+ ? catalog.filter(({ tool }) => tool.originalName === toolName)
566
+ : [];
439
567
  const exact = originals.length ? originals : catalog.filter(({ tool }) => tool.name === toolName);
440
- const matches = exact.length ? exact : catalog.filter(({ tool }) => tool.name.replace(/-/g, "_") === toolName.replace(/-/g, "_"));
441
- if (matches.length > 1)
568
+ const matches = exact.length
569
+ ? exact
570
+ : catalog.filter(({ tool }) => tool.name.replace(/-/g, "_") === toolName.replace(/-/g, "_"));
571
+ if (matches.length > 1) {
442
572
  return {
443
- content: [{ type: "text", text: `Tool ${JSON.stringify(toolName)} is ambiguous. Specify server and original tool name: ${JSON.stringify(matches.map(({ server, tool }) => ({ server, tool: tool.originalName })))}` }],
573
+ content: [
574
+ {
575
+ type: "text",
576
+ text: `Tool ${JSON.stringify(toolName)} is ambiguous. Specify server and original tool name: ${JSON.stringify(matches.map(({ server, tool }) => ({ server, tool: tool.originalName })))}`,
577
+ },
578
+ ],
444
579
  details: { mode: "describe", error: "ambiguous_tool", requestedTool: toolName },
445
580
  };
581
+ }
446
582
  const match = matches[0];
447
583
  if (!match) {
448
584
  const disabled = [...state.toolMetadata].find(([server, tools]) => isServerDisabled(state.config.mcpServers[server]) && findToolByName(tools, toolName));
449
- if (disabled)
585
+ if (disabled) {
450
586
  return disabledResult("describe", disabled[0]);
587
+ }
451
588
  const suggestions = rankSuggestions(state, toolName, 5, serverFilter);
452
589
  const hint = suggestions.length > 0
453
590
  ? `Did you mean: ${suggestions.join(", ")}. Inspect with mcp({ action: "describe", tool: "${suggestions[0]}" }). Load if inactive: mcp_search({ enable: ["gateway"] }).`
@@ -461,15 +598,34 @@ export function executeDescribe(state, toolName, serverFilter) {
461
598
  const descriptor = toToolDescriptor(serverName, toolMeta);
462
599
  const approvalRequired = isToolCallApprovalRequired(state.config, serverName, toolMeta);
463
600
  return {
464
- content: [{ type: "text", text: JSON.stringify({ ...descriptor, ...(toolMeta.resourceUri ? { resourceUri: toolMeta.resourceUri, readWith: { action: "read-resource", server: serverName, uri: toolMeta.resourceUri } } : {}), ...(approvalRequired ? { approvalRequired: true } : {}) }, null, 2) }],
601
+ content: [
602
+ {
603
+ type: "text",
604
+ text: JSON.stringify({
605
+ ...descriptor,
606
+ ...(toolMeta.resourceUri
607
+ ? {
608
+ resourceUri: toolMeta.resourceUri,
609
+ readWith: {
610
+ action: "read-resource",
611
+ server: serverName,
612
+ uri: toolMeta.resourceUri,
613
+ },
614
+ }
615
+ : {}),
616
+ ...(approvalRequired ? { approvalRequired: true } : {}),
617
+ }, null, 2),
618
+ },
619
+ ],
465
620
  details: { mode: "describe", tool: toolMeta, server: serverName },
466
621
  };
467
622
  }
468
623
  export function executeSearch(state, query, server, includeSchemas, limit = 12, offset = 0, caller = "mcp") {
469
624
  const showSchemas = includeSchemas !== false;
470
625
  const searchCall = (nextOffset) => `${caller}({ ${caller === "mcp" ? 'action: "search", ' : ""}query: ${JSON.stringify(query)}${server ? `, server: ${JSON.stringify(server)}` : ""}${caller === "mcp" && !showSchemas ? ", includeSchemas: false" : ""}, limit: ${limit}, offset: ${nextOffset} })`;
471
- if (server && isServerDisabled(state.config.mcpServers[server]))
626
+ if (server && isServerDisabled(state.config.mcpServers[server])) {
472
627
  return disabledResult("search", server);
628
+ }
473
629
  let matches;
474
630
  if (query.trim().length === 0) {
475
631
  if (!server) {
@@ -478,8 +634,9 @@ export function executeSearch(state, query, server, includeSchemas, limit = 12,
478
634
  details: { mode: "search", error: "empty_query" },
479
635
  };
480
636
  }
481
- matches = (state.toolMetadata.get(server) ?? []).filter(tool => !tool.resourceUri)
482
- .map(tool => ({ server, tool, score: 0 }))
637
+ matches = (state.toolMetadata.get(server) ?? [])
638
+ .filter((tool) => !tool.resourceUri)
639
+ .map((tool) => ({ server, tool, score: 0 }))
483
640
  .sort((a, b) => a.tool.name.localeCompare(b.tool.name));
484
641
  }
485
642
  else {
@@ -487,19 +644,44 @@ export function executeSearch(state, query, server, includeSchemas, limit = 12,
487
644
  }
488
645
  const page = paginate(matches, offset, limit);
489
646
  const coverage = catalogCoverage(state, server);
490
- const coverageNotice = coverage.complete ? "" : `\nCatalog is incomplete. Undiscovered servers: ${coverage.unknownServers.join(", ")}. Search with server to discover one.`;
647
+ const coverageNotice = coverage.complete
648
+ ? ""
649
+ : `\nCatalog is incomplete. Undiscovered servers: ${coverage.unknownServers.join(", ")}. Search with server to discover one.`;
491
650
  if (page.total === 0) {
492
- const msg = server ? `No tools matching "${query}" in "${server}"` : `No tools matching "${query}"`;
651
+ const msg = server
652
+ ? `No tools matching "${query}" in "${server}"`
653
+ : `No tools matching "${query}"`;
493
654
  return {
494
655
  content: [{ type: "text", text: msg + coverageNotice }],
495
- details: { mode: "search", matches: [], count: 0, hasMore: false, nextOffset: null, query, coverage },
656
+ details: {
657
+ mode: "search",
658
+ matches: [],
659
+ count: 0,
660
+ hasMore: false,
661
+ nextOffset: null,
662
+ query,
663
+ coverage,
664
+ },
496
665
  };
497
666
  }
498
667
  if (page.items.length === 0) {
499
668
  const retry = searchCall(0);
500
669
  return {
501
- content: [{ type: "text", text: `No search results at offset ${offset}; ${page.total} tools match. Retry with ${retry}.` }],
502
- details: { mode: "search", error: "offset_out_of_range", matches: [], count: page.total, hasMore: false, nextOffset: null, query },
670
+ content: [
671
+ {
672
+ type: "text",
673
+ text: `No search results at offset ${offset}; ${page.total} tools match. Retry with ${retry}.`,
674
+ },
675
+ ],
676
+ details: {
677
+ mode: "search",
678
+ error: "offset_out_of_range",
679
+ matches: [],
680
+ count: page.total,
681
+ hasMore: false,
682
+ nextOffset: null,
683
+ query,
684
+ },
503
685
  };
504
686
  }
505
687
  let text = `Found ${page.total} tool${page.total === 1 ? "" : "s"} matching "${query}":\n\n`;
@@ -512,8 +694,9 @@ export function executeSearch(state, query, server, includeSchemas, limit = 12,
512
694
  }
513
695
  else {
514
696
  text += `- ${match.tool.name}${approvalMarker}`;
515
- if (match.tool.description)
697
+ if (match.tool.description) {
516
698
  text += ` - ${truncateAtWord(match.tool.description, 50)}`;
699
+ }
517
700
  text += "\n";
518
701
  }
519
702
  }
@@ -527,7 +710,11 @@ export function executeSearch(state, query, server, includeSchemas, limit = 12,
527
710
  details: {
528
711
  mode: "search",
529
712
  coverage,
530
- matches: page.items.map(match => ({ server: match.server, tool: match.tool.name, score: match.score })),
713
+ matches: page.items.map((match) => ({
714
+ server: match.server,
715
+ tool: match.tool.name,
716
+ score: match.score,
717
+ })),
531
718
  count: page.total,
532
719
  hasMore: page.hasMore,
533
720
  nextOffset: page.nextOffset,
@@ -539,13 +726,19 @@ export function executeList(state, server, limit = 12, offset = 0) {
539
726
  const definition = state.config.mcpServers[server];
540
727
  if (!definition) {
541
728
  return {
542
- content: [{ type: "text", text: `Server "${server}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).` }],
729
+ content: [
730
+ {
731
+ type: "text",
732
+ text: `Server "${server}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).`,
733
+ },
734
+ ],
543
735
  details: { mode: "list", server, tools: [], count: 0, error: "not_found" },
544
736
  };
545
737
  }
546
- if (isServerDisabled(definition))
738
+ if (isServerDisabled(definition)) {
547
739
  return disabledResult("list", server);
548
- const metadata = state.toolMetadata.get(server)?.filter(tool => !tool.resourceUri);
740
+ }
741
+ const metadata = state.toolMetadata.get(server)?.filter((tool) => !tool.resourceUri);
549
742
  const connection = state.manager.getConnection(server);
550
743
  const instructions = state.serverInstructions.get(server);
551
744
  let instructionsText = "";
@@ -559,26 +752,72 @@ export function executeList(state, server, limit = 12, offset = 0) {
559
752
  if (!metadata?.length) {
560
753
  if (connection?.status === "connected") {
561
754
  return {
562
- content: [{ type: "text", text: `Server "${server}" has no tools.${instructionsText}` }],
563
- details: { mode: "list", server, tools: [], count: 0, hasInstructions: Boolean(instructions) },
755
+ content: [
756
+ { type: "text", text: `Server "${server}" has no tools.${instructionsText}` },
757
+ ],
758
+ details: {
759
+ mode: "list",
760
+ server,
761
+ tools: [],
762
+ count: 0,
763
+ hasInstructions: Boolean(instructions),
764
+ },
564
765
  };
565
766
  }
566
767
  if (metadata !== undefined) {
567
768
  return {
568
- content: [{ type: "text", text: `Server "${server}" has no cached tools (not connected).${instructionsText}` }],
569
- details: { mode: "list", server, tools: [], count: 0, cached: true, hasInstructions: Boolean(instructions) },
769
+ content: [
770
+ {
771
+ type: "text",
772
+ text: `Server "${server}" has no cached tools (not connected).${instructionsText}`,
773
+ },
774
+ ],
775
+ details: {
776
+ mode: "list",
777
+ server,
778
+ tools: [],
779
+ count: 0,
780
+ cached: true,
781
+ hasInstructions: Boolean(instructions),
782
+ },
570
783
  };
571
784
  }
572
785
  return {
573
- content: [{ type: "text", text: `Server "${server}" is configured but not connected. Use mcp({ action: "connect", server: "${server}" }) or /mcp reconnect ${server} to retry.${instructionsText}` }],
574
- details: { mode: "list", server, tools: [], count: 0, error: "not_connected", hasInstructions: Boolean(instructions) },
786
+ content: [
787
+ {
788
+ type: "text",
789
+ text: `Server "${server}" is configured but not connected. Use mcp({ action: "connect", server: "${server}" }) or /mcp reconnect ${server} to retry.${instructionsText}`,
790
+ },
791
+ ],
792
+ details: {
793
+ mode: "list",
794
+ server,
795
+ tools: [],
796
+ count: 0,
797
+ error: "not_connected",
798
+ hasInstructions: Boolean(instructions),
799
+ },
575
800
  };
576
801
  }
577
802
  const page = paginate(metadata, offset, limit);
578
803
  if (page.items.length === 0) {
579
804
  return {
580
- content: [{ type: "text", text: `No tools at offset ${offset}; "${server}" has ${page.total} tools. Retry with mcp({ action: "list", server: ${JSON.stringify(server)}, limit: ${limit}, offset: 0 }).` }],
581
- details: { mode: "list", error: "offset_out_of_range", server, tools: [], count: page.total, hasMore: false, nextOffset: null, hasInstructions: Boolean(instructions) },
805
+ content: [
806
+ {
807
+ type: "text",
808
+ text: `No tools at offset ${offset}; "${server}" has ${page.total} tools. Retry with mcp({ action: "list", server: ${JSON.stringify(server)}, limit: ${limit}, offset: 0 }).`,
809
+ },
810
+ ],
811
+ details: {
812
+ mode: "list",
813
+ error: "offset_out_of_range",
814
+ server,
815
+ tools: [],
816
+ count: page.total,
817
+ hasMore: false,
818
+ nextOffset: null,
819
+ hasInstructions: Boolean(instructions),
820
+ },
582
821
  };
583
822
  }
584
823
  const cachedNote = connection?.status === "connected" ? "" : ", not connected, cached";
@@ -586,8 +825,9 @@ export function executeList(state, server, limit = 12, offset = 0) {
586
825
  for (const tool of page.items) {
587
826
  const description = truncateAtWord(tool.description, 50);
588
827
  text += `- ${tool.name}`;
589
- if (description)
828
+ if (description) {
590
829
  text += ` - ${description}`;
830
+ }
591
831
  text += "\n";
592
832
  }
593
833
  const first = (Number.isFinite(offset) ? Math.max(0, Math.trunc(offset)) : 0) + 1;
@@ -600,7 +840,7 @@ export function executeList(state, server, limit = 12, offset = 0) {
600
840
  details: {
601
841
  mode: "list",
602
842
  server,
603
- tools: page.items.map(tool => tool.name),
843
+ tools: page.items.map((tool) => tool.name),
604
844
  count: page.total,
605
845
  hasMore: page.hasMore,
606
846
  nextOffset: page.nextOffset,
@@ -612,12 +852,18 @@ export function executeInstructions(state, server) {
612
852
  const definition = state.config.mcpServers[server];
613
853
  if (!definition) {
614
854
  return {
615
- content: [{ type: "text", text: `Server "${server}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).` }],
855
+ content: [
856
+ {
857
+ type: "text",
858
+ text: `Server "${server}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).`,
859
+ },
860
+ ],
616
861
  details: { mode: "instructions", server, error: "not_found" },
617
862
  };
618
863
  }
619
- if (isServerDisabled(definition))
864
+ if (isServerDisabled(definition)) {
620
865
  return disabledResult("instructions", server);
866
+ }
621
867
  const instructions = state.serverInstructions.get(server);
622
868
  if (instructions) {
623
869
  return {
@@ -628,12 +874,19 @@ export function executeInstructions(state, server) {
628
874
  const connection = state.manager.getConnection(server);
629
875
  if (connection?.status === "connected") {
630
876
  return {
631
- content: [{ type: "text", text: `Server "${server}" does not provide instructions.` }],
877
+ content: [
878
+ { type: "text", text: `Server "${server}" does not provide instructions.` },
879
+ ],
632
880
  details: { mode: "instructions", server, error: "no_instructions" },
633
881
  };
634
882
  }
635
883
  return {
636
- content: [{ type: "text", text: `No instructions cached for "${server}". Use mcp({ action: "connect", server: "${server}" }) to connect and refresh.` }],
884
+ content: [
885
+ {
886
+ type: "text",
887
+ text: `No instructions cached for "${server}". Use mcp({ action: "connect", server: "${server}" }) to connect and refresh.`,
888
+ },
889
+ ],
637
890
  details: { mode: "instructions", server, error: "not_connected" },
638
891
  };
639
892
  }
@@ -643,12 +896,18 @@ export async function executeConnect(state, serverName, signal, limit, offset) {
643
896
  const definition = state.config.mcpServers[serverName];
644
897
  if (!definition) {
645
898
  return {
646
- content: [{ type: "text", text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).` }],
899
+ content: [
900
+ {
901
+ type: "text",
902
+ text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).`,
903
+ },
904
+ ],
647
905
  details: { mode: "connect", error: "not_found", server: serverName },
648
906
  };
649
907
  }
650
- if (isServerDisabled(definition))
908
+ if (isServerDisabled(definition)) {
651
909
  return disabledResult("connect", serverName);
910
+ }
652
911
  try {
653
912
  if (state.ui) {
654
913
  state.ui.setStatus("mcp", formatMcpStatus(state.config, `connecting to ${serverName}...`));
@@ -662,7 +921,12 @@ export async function executeConnect(state, serverName, signal, limit, offset) {
662
921
  if (autoAuth.status === "failed") {
663
922
  return {
664
923
  content: [{ type: "text", text: autoAuth.message }],
665
- details: { mode: "connect", error: "auth_required", server: serverName, message: autoAuth.message },
924
+ details: {
925
+ mode: "connect",
926
+ error: "auth_required",
927
+ server: serverName,
928
+ message: autoAuth.message,
929
+ },
666
930
  };
667
931
  }
668
932
  if (autoAuth.status === "success") {
@@ -700,12 +964,20 @@ export async function executeConnect(state, serverName, signal, limit, offset) {
700
964
  }
701
965
  catch (error) {
702
966
  const message = error instanceof Error ? error.message : String(error);
703
- if (!isAbortError(error, ownedSignal))
967
+ if (!isAbortError(error, ownedSignal)) {
704
968
  recordFailure(state, serverName, message);
969
+ }
705
970
  updateStatusBar(state);
706
971
  return {
707
- content: [{ type: "text", text: `Failed to connect to "${serverName}": ${message}` }],
708
- details: { mode: "connect", error: isAbortError(error, ownedSignal) ? "aborted" : "connect_failed", server: serverName, message },
972
+ content: [
973
+ { type: "text", text: `Failed to connect to "${serverName}": ${message}` },
974
+ ],
975
+ details: {
976
+ mode: "connect",
977
+ error: isAbortError(error, ownedSignal) ? "aborted" : "connect_failed",
978
+ server: serverName,
979
+ message,
980
+ },
709
981
  };
710
982
  }
711
983
  }
@@ -716,9 +988,11 @@ export async function executeConnect(state, serverName, signal, limit, offset) {
716
988
  * Raw scripts skip content rendering but retain protocol results and artifacts.
717
989
  */
718
990
  export async function runToolCall(state, serverName, target, args, options) {
719
- const { DEFAULT_REQUEST_TIMEOUT_MSEC, UrlElicitationRequiredError } = await import("@modelcontextprotocol/client");
720
- const { isInterruptedToolCall, isToolTransportFailure, trackToolCallOutcome, SessionRecoveryAuthRequiredError, withSessionRecovery } = await import("./session-recovery.js");
721
- const { maybeStartUiSession, summarizeUiSessionResult } = await import("./ui-session.js");
991
+ const [{ DEFAULT_REQUEST_TIMEOUT_MSEC, UrlElicitationRequiredError }, { isInterruptedToolCall, isToolTransportFailure, trackToolCallOutcome, SessionRecoveryAuthRequiredError, withSessionRecovery, }, { maybeStartUiSession, summarizeUiSessionResult },] = await Promise.all([
992
+ import("@modelcontextprotocol/client"),
993
+ import("./session-recovery.js"),
994
+ import("./ui-session.js"),
995
+ ]);
722
996
  const { detailsBase, signal, recoverAuthConnection, authRequiredMessage, autoAuthAttempted } = options;
723
997
  const configuredOptions = state.manager.getRequestOptions?.(serverName, options.ownedSignal);
724
998
  const callerSignal = combineAbortSignals(configuredOptions?.signal, options.ownedSignal);
@@ -728,15 +1002,17 @@ export async function runToolCall(state, serverName, target, args, options) {
728
1002
  const schemaSuffix = () => target.inputSchema ? `\n\nExpected parameters:\n${formatSchema(target.inputSchema)}` : "";
729
1003
  let uiSession = null;
730
1004
  const identity = {
731
- server: serverName, tool: target.originalName,
1005
+ server: serverName,
1006
+ tool: target.originalName,
732
1007
  ...(options.toolCallId !== undefined ? { toolCallId: options.toolCallId } : {}),
733
1008
  ...(options.innerCallId !== undefined ? { innerCallId: options.innerCallId } : {}),
734
1009
  };
735
1010
  let beforeDispatchPending = false;
736
1011
  let captureFailure;
737
1012
  const capture = async (event) => {
738
- if (!state.onToolCall)
1013
+ if (!state.onToolCall) {
739
1014
  return;
1015
+ }
740
1016
  try {
741
1017
  const run = () => state.onToolCall(event);
742
1018
  await abortable(state.owner ? state.owner.runCallback("onToolCall", run) : run(), event.signal);
@@ -752,31 +1028,34 @@ export async function runToolCall(state, serverName, target, args, options) {
752
1028
  state.manager.incrementInFlight(serverName);
753
1029
  // Loading the connection-only modules can yield to cancellation or shutdown.
754
1030
  throwIfAborted(callerSignal);
755
- uiSession = !target.resourceUri && target.uiResourceUri
756
- ? await abortable(maybeStartUiSession(state, {
757
- serverName,
758
- toolName: target.originalName,
759
- toolArgs: args ?? {},
760
- uiResourceUri: target.uiResourceUri,
761
- ...(target.uiStreamMode !== undefined ? { streamMode: target.uiStreamMode } : {}),
762
- ...(signal ? { signal } : {}),
763
- onNeedsAuth: recoverAuthConnection,
764
- }), callerSignal)
765
- : null;
1031
+ uiSession =
1032
+ !target.resourceUri && target.uiResourceUri
1033
+ ? await abortable(maybeStartUiSession(state, {
1034
+ serverName,
1035
+ toolName: target.originalName,
1036
+ toolArgs: args ?? {},
1037
+ uiResourceUri: target.uiResourceUri,
1038
+ ...(target.uiStreamMode !== undefined ? { streamMode: target.uiStreamMode } : {}),
1039
+ ...(signal ? { signal } : {}),
1040
+ onNeedsAuth: recoverAuthConnection,
1041
+ }), callerSignal)
1042
+ : null;
766
1043
  // UI preparation can replace the connection; use its current annotations.
767
1044
  const liveTools = state.manager.getConnection(serverName)?.tools;
768
1045
  const annotations = liveTools
769
- ? liveTools.find(tool => tool.name === target.originalName)?.annotations
1046
+ ? liveTools.find((tool) => tool.name === target.originalName)?.annotations
770
1047
  : target.annotations;
771
1048
  const operation = {
772
- ...identity, args: args ?? {},
1049
+ ...identity,
1050
+ args: args ?? {},
773
1051
  ...(annotations !== undefined ? { annotations } : {}),
774
1052
  ...(target.resourceUri ? { resourceUri: target.resourceUri } : {}),
775
1053
  };
776
1054
  if (options.beforeDispatch) {
777
1055
  beforeDispatchPending = true;
778
1056
  const run = () => options.beforeDispatch(callerSignal, {
779
- ...operation, annotationsTrusted: state.config.mcpServers[serverName]?.retryOnTransportFailure === true,
1057
+ ...operation,
1058
+ annotationsTrusted: state.config.mcpServers[serverName]?.retryOnTransportFailure === true,
780
1059
  });
781
1060
  await abortable(state.owner ? state.owner.runCallback("beforeExecute", run) : run(), callerSignal);
782
1061
  beforeDispatchPending = false;
@@ -785,7 +1064,12 @@ export async function runToolCall(state, serverName, target, args, options) {
785
1064
  // Approval and UI preparation do not consume the service deadline.
786
1065
  const ownedSignal = combineAbortSignals(callerSignal, AbortSignal.timeout(Math.ceil(configuredOptions?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC)));
787
1066
  const requestOptions = { ...configuredOptions, signal: ownedSignal };
788
- const recovery = { manager: state.manager, config: state.config, signal: ownedSignal, onNeedsAuth: recoverAuthConnection };
1067
+ const recovery = {
1068
+ manager: state.manager,
1069
+ config: state.config,
1070
+ signal: ownedSignal,
1071
+ onNeedsAuth: recoverAuthConnection,
1072
+ };
789
1073
  const call = { ...operation, signal: ownedSignal };
790
1074
  await capture({ ...call, phase: "before" });
791
1075
  throwIfAborted(ownedSignal);
@@ -802,36 +1086,71 @@ export async function runToolCall(state, serverName, target, args, options) {
802
1086
  return result;
803
1087
  };
804
1088
  const result = target.resourceUri
805
- ? await dispatch(() => withSessionRecovery(recovery, serverName, conn => abortable(conn.client.readResource({ uri: target.resourceUri }, requestOptions), ownedSignal)))
806
- : await dispatch(() => trackToolCallOutcome(() => withSessionRecovery({ ...recovery, retryOnTransportFailure: annotations?.readOnlyHint === true || annotations?.idempotentHint === true }, serverName, conn => abortable(conn.client.callTool({ name: target.originalName, arguments: args ?? {}, _meta: uiSession?.requestMeta }, requestOptions), ownedSignal)), ownedSignal));
807
- if (!target.resourceUri)
1089
+ ? await dispatch(() => withSessionRecovery(recovery, serverName, (conn) => abortable(conn.client.readResource({ uri: target.resourceUri }, requestOptions), ownedSignal)))
1090
+ : await dispatch(() => trackToolCallOutcome(() => withSessionRecovery({
1091
+ ...recovery,
1092
+ retryOnTransportFailure: annotations?.readOnlyHint === true || annotations?.idempotentHint === true,
1093
+ }, serverName, (conn) => abortable(conn.client.callTool({
1094
+ name: target.originalName,
1095
+ arguments: args ?? {},
1096
+ _meta: uiSession?.requestMeta,
1097
+ }, requestOptions), ownedSignal)), ownedSignal));
1098
+ if (!target.resourceUri) {
808
1099
  uiSession?.sendToolResult(result);
1100
+ }
809
1101
  const record = result;
810
1102
  const isError = record.isError === true;
811
1103
  const uiSummary = target.uiResourceUri ? summarizeUiSessionResult(uiSession) : undefined;
812
1104
  const details = {
813
1105
  ...detailsBase,
814
1106
  ...(isError ? { error: "tool_error" } : {}),
815
- ...(uiSummary ? { uiOpen: uiSummary.uiOpen, uiViewer: uiSummary.uiViewer, uiUrl: uiSummary.uiUrl } : {}),
1107
+ ...(uiSummary
1108
+ ? { uiOpen: uiSummary.uiOpen, uiViewer: uiSummary.uiViewer, uiUrl: uiSummary.uiUrl }
1109
+ : {}),
816
1110
  };
817
1111
  const retained = await retainMcpResult(result, outputGuardOptions, options.raw);
818
1112
  if (options.raw) {
819
1113
  const message = isError
820
- ? (Array.isArray(record.content) ? record.content.filter(block => block.type === "text").map(block => block.text).join("\n") : "") || "Tool execution failed" : undefined;
821
- return { content: [], ...(isError ? { isError: true } : {}), details: { ...details, ...retained, ...(message ? { message } : {}) } };
1114
+ ? (Array.isArray(record.content)
1115
+ ? record.content
1116
+ .filter((block) => block.type === "text")
1117
+ .map((block) => block.text)
1118
+ .join("\n")
1119
+ : "") || "Tool execution failed"
1120
+ : undefined;
1121
+ return {
1122
+ content: [],
1123
+ ...(isError ? { isError: true } : {}),
1124
+ details: { ...details, ...retained, ...(message ? { message } : {}) },
1125
+ };
822
1126
  }
823
1127
  const content = renderMcpResultContent(record, retained.payloadFiles);
824
- const guarded = await guardMcpOutput(content.length ? content : [{ type: "text", text: target.resourceUri ? "(empty resource)" : "(empty result)" }], {
825
- ...outputGuardOptions, retainedMcpResult: retained,
826
- ...(isError ? { prefix: "Error: ", suffix: schemaSuffix(), emptyTextFallback: "Tool execution failed" }
827
- : uiSummary ? { suffix: `\n\n${uiSummary.message}` } : {}),
1128
+ const guarded = await guardMcpOutput(content.length
1129
+ ? content
1130
+ : [{ type: "text", text: target.resourceUri ? "(empty resource)" : "(empty result)" }], {
1131
+ ...outputGuardOptions,
1132
+ retainedMcpResult: retained,
1133
+ ...(isError
1134
+ ? {
1135
+ prefix: "Error: ",
1136
+ suffix: schemaSuffix(),
1137
+ emptyTextFallback: "Tool execution failed",
1138
+ }
1139
+ : uiSummary
1140
+ ? { suffix: `\n\n${uiSummary.message}` }
1141
+ : {}),
828
1142
  });
829
- return { content: guarded.content, ...(isError ? { isError: true } : {}), details: { ...details, ...guardedMcpDetails(guarded) } };
1143
+ return {
1144
+ content: guarded.content,
1145
+ ...(isError ? { isError: true } : {}),
1146
+ details: { ...details, ...guardedMcpDetails(guarded) },
1147
+ };
830
1148
  }
831
1149
  catch (error) {
832
1150
  // Direct/proxy host errors throw; scripts retain their failed-call envelope.
833
- if (beforeDispatchPending && options.innerCallId === undefined)
1151
+ if (beforeDispatchPending && options.innerCallId === undefined) {
834
1152
  throw error;
1153
+ }
835
1154
  if (captureFailure) {
836
1155
  const { signal: _signal, ...context } = captureFailure;
837
1156
  const message = captureFailure.phase === "before"
@@ -839,11 +1158,27 @@ export async function runToolCall(state, serverName, target, args, options) {
839
1158
  : "MCP outcome capture failed. Use the retained outcome to continue the original work; do not repeat a completed call or rerun the script.";
840
1159
  uiSession?.sendToolCancelled(message);
841
1160
  const retained = "result" in captureFailure && captureFailure.result !== undefined
842
- ? await retainMcpResult(captureFailure.result, { ...outputGuardOptions, enabled: true, detailsMaxBytes: 1 }) : {};
1161
+ ? await retainMcpResult(captureFailure.result, {
1162
+ ...outputGuardOptions,
1163
+ enabled: true,
1164
+ detailsMaxBytes: 1,
1165
+ })
1166
+ : {};
843
1167
  return {
844
- content: [{ type: "text", text: message }, ...(retained.resultRef ? [{ type: "text", text: formatMcpResultReference(retained.resultRef) }] : [])],
1168
+ content: [
1169
+ { type: "text", text: message },
1170
+ ...(retained.resultRef
1171
+ ? [{ type: "text", text: formatMcpResultReference(retained.resultRef) }]
1172
+ : []),
1173
+ ],
845
1174
  isError: true,
846
- details: { ...detailsBase, ...retained, error: isAbortError(error, callerSignal) ? "aborted" : "call_capture_failed", message, recovery: context },
1175
+ details: {
1176
+ ...detailsBase,
1177
+ ...retained,
1178
+ error: isAbortError(error, callerSignal) ? "aborted" : "call_capture_failed",
1179
+ message,
1180
+ recovery: context,
1181
+ },
847
1182
  };
848
1183
  }
849
1184
  if (isToolTransportFailure(error) || isInterruptedToolCall(error)) {
@@ -852,7 +1187,12 @@ export async function runToolCall(state, serverName, target, args, options) {
852
1187
  return {
853
1188
  content: [{ type: "text", text: message }],
854
1189
  isError: true,
855
- details: { ...detailsBase, error: "ambiguous_outcome", message, recovery: { ...identity, action: "readback" } },
1190
+ details: {
1191
+ ...detailsBase,
1192
+ error: "ambiguous_outcome",
1193
+ message,
1194
+ recovery: { ...identity, action: "readback" },
1195
+ },
856
1196
  };
857
1197
  }
858
1198
  if (error instanceof SessionRecoveryAuthRequiredError) {
@@ -860,7 +1200,12 @@ export async function runToolCall(state, serverName, target, args, options) {
860
1200
  uiSession?.sendToolCancelled(message);
861
1201
  return {
862
1202
  content: [{ type: "text", text: message }],
863
- details: { ...detailsBase, error: "auth_required", message, autoAuthAttempted: autoAuthAttempted() },
1203
+ details: {
1204
+ ...detailsBase,
1205
+ error: "auth_required",
1206
+ message,
1207
+ autoAuthAttempted: autoAuthAttempted(),
1208
+ },
864
1209
  };
865
1210
  }
866
1211
  if (error instanceof UrlElicitationRequiredError) {
@@ -876,7 +1221,11 @@ export async function runToolCall(state, serverName, target, args, options) {
876
1221
  }
877
1222
  const message = error instanceof Error ? error.message : String(error);
878
1223
  uiSession?.sendToolCancelled(message);
879
- const guarded = await guardMcpOutput([{ type: "text", text: message }], { ...outputGuardOptions, prefix: "Failed to call tool: ", suffix: schemaSuffix() });
1224
+ const guarded = await guardMcpOutput([{ type: "text", text: message }], {
1225
+ ...outputGuardOptions,
1226
+ prefix: "Failed to call tool: ",
1227
+ suffix: schemaSuffix(),
1228
+ });
880
1229
  return {
881
1230
  content: guarded.content,
882
1231
  isError: true,
@@ -905,19 +1254,22 @@ export async function prepareMcpConnection(state, serverName, signal) {
905
1254
  const recoverAuthConnection = async (_name, recoverySignal = ownedSignal, challenge) => {
906
1255
  throwIfAborted(recoverySignal);
907
1256
  const current = state.manager.getConnection(serverName);
908
- if (current?.status === "connected" && current !== challenge?.connection)
1257
+ if (current?.status === "connected" && current !== challenge?.connection) {
909
1258
  return current;
1259
+ }
910
1260
  if (!autoAuthAttempted) {
911
1261
  autoAuthAttempted = true;
912
1262
  const auth = await attemptAutoAuth(state, serverName, recoverySignal);
913
1263
  throwIfAborted(recoverySignal);
914
- if (auth.status === "failed")
1264
+ if (auth.status === "failed") {
915
1265
  throw new SessionRecoveryAuthRequiredError(serverName, auth.message);
1266
+ }
916
1267
  if (auth.status === "success" && current) {
917
1268
  clearFailure(state, serverName);
918
1269
  const definition = state.config.mcpServers[serverName];
919
- if (!definition)
1270
+ if (!definition) {
920
1271
  return undefined;
1272
+ }
921
1273
  state.ui?.setStatus("mcp", formatMcpStatus(state.config, `connecting to ${serverName}...`));
922
1274
  await state.manager.reconnect(serverName, definition, current, recoverySignal);
923
1275
  if (await lazyConnect(state, serverName, recoverySignal)) {
@@ -933,10 +1285,12 @@ export async function prepareMcpConnection(state, serverName, signal) {
933
1285
  details: { mode: "call", error, server: serverName, message },
934
1286
  });
935
1287
  const definition = state.config.mcpServers[serverName];
936
- if (!definition)
1288
+ if (!definition) {
937
1289
  return { error: failure("server_not_found", `Server "${serverName}" not found.`) };
938
- if (isServerDisabled(definition))
1290
+ }
1291
+ if (isServerDisabled(definition)) {
939
1292
  return { error: disabledResult("call", serverName) };
1293
+ }
940
1294
  try {
941
1295
  // Connected metadata is read below, so it does not require another connection attempt.
942
1296
  let connection = state.manager.getConnection(serverName);
@@ -947,162 +1301,285 @@ export async function prepareMcpConnection(state, serverName, signal) {
947
1301
  else {
948
1302
  markKeepAliveAfterConnect(state, serverName);
949
1303
  }
950
- if (connection?.status === "needs-auth")
1304
+ if (connection?.status === "needs-auth") {
951
1305
  connection = await recoverAuthConnection(serverName, ownedSignal);
952
- if (connection?.status === "needs-auth")
1306
+ }
1307
+ if (connection?.status === "needs-auth") {
953
1308
  throw new SessionRecoveryAuthRequiredError(serverName);
1309
+ }
954
1310
  if (connection?.status !== "connected") {
955
1311
  const failedAgo = getFailureAgeSeconds(state, serverName);
956
- return { error: failure(failedAgo === null ? "server_unavailable" : "server_backoff", `Server "${serverName}" not available${failedAgo === null ? "" : ` (last failed ${failedAgo}s ago)`}`) };
1312
+ return {
1313
+ error: failure(failedAgo === null ? "server_unavailable" : "server_backoff", `Server "${serverName}" not available${failedAgo === null ? "" : ` (last failed ${failedAgo}s ago)`}`),
1314
+ };
957
1315
  }
958
1316
  // Use the live descriptor for visibility, filtering, approval, schema and UI hints.
959
1317
  if (connection.tools) {
960
1318
  const { metadata } = buildToolMetadata(connection.tools, connection.resources ?? [], definition, serverName, state.config.settings?.toolPrefix ?? "server");
961
1319
  // Search indexes by metadata-array identity, so retain it for unchanged projections.
962
- if (!isDeepStrictEqual(state.toolMetadata.get(serverName), metadata))
1320
+ if (!isDeepStrictEqual(state.toolMetadata.get(serverName), metadata)) {
963
1321
  state.toolMetadata.set(serverName, metadata);
1322
+ }
964
1323
  }
965
1324
  return { connection, recoverAuthConnection, autoAuthAttempted: () => autoAuthAttempted };
966
1325
  }
967
1326
  catch (error) {
968
- if (isAbortError(error, ownedSignal))
1327
+ if (isAbortError(error, ownedSignal)) {
969
1328
  throw error;
1329
+ }
970
1330
  const message = error instanceof SessionRecoveryAuthRequiredError
971
- ? error.authMessage ?? getAuthRequiredMessage(state, serverName)
972
- : error instanceof Error ? error.message : String(error);
1331
+ ? (error.authMessage ?? getAuthRequiredMessage(state, serverName))
1332
+ : error instanceof Error
1333
+ ? error.message
1334
+ : String(error);
973
1335
  if (!(error instanceof SessionRecoveryAuthRequiredError)) {
974
1336
  recordFailure(state, serverName, message);
975
1337
  updateStatusBar(state);
976
1338
  }
977
- return { error: failure(error instanceof SessionRecoveryAuthRequiredError ? "auth_required" : "connect_failed", message) };
1339
+ return {
1340
+ error: failure(error instanceof SessionRecoveryAuthRequiredError ? "auth_required" : "connect_failed", message),
1341
+ };
978
1342
  }
979
1343
  }
980
1344
  export async function executeCall(state, toolName, args, serverOverride, getPiTools, signal, identity = {}, beforeDispatch, options = {}) {
981
1345
  const ownedSignal = combineAbortSignals(state.owner?.signal, signal);
982
1346
  throwIfAborted(ownedSignal);
983
- if (typeof toolName !== "string" || !toolName.trim() || (serverOverride !== undefined && (typeof serverOverride !== "string" || !serverOverride)))
1347
+ if (typeof toolName !== "string" ||
1348
+ !toolName.trim() ||
1349
+ (serverOverride !== undefined && (typeof serverOverride !== "string" || !serverOverride))) {
984
1350
  return {
985
- content: [{ type: "text", text: "Specify a non-empty tool name and, when supplied, server." }],
1351
+ content: [
1352
+ { type: "text", text: "Specify a non-empty tool name and, when supplied, server." },
1353
+ ],
986
1354
  details: { mode: "call", error: "invalid_arguments" },
987
1355
  };
988
- if (args !== undefined && (args === null || typeof args !== "object" || Array.isArray(args)))
1356
+ }
1357
+ if (args !== undefined && (args === null || typeof args !== "object" || Array.isArray(args))) {
989
1358
  return {
990
1359
  content: [{ type: "text", text: "Tool arguments must be an object." }],
991
1360
  details: { mode: "call", error: "invalid_arguments", requestedTool: toolName },
992
1361
  };
1362
+ }
993
1363
  let serverName = serverOverride;
994
1364
  const matches = (metadata = []) => {
995
- if (options.resourceUri !== undefined)
996
- return metadata.filter(tool => tool.resourceUri === options.resourceUri);
997
- if (options.exactOriginalName)
998
- return metadata.filter(tool => tool.originalName === toolName && !tool.resourceUri);
1365
+ if (options.resourceUri !== undefined) {
1366
+ return metadata.filter((tool) => tool.resourceUri === options.resourceUri);
1367
+ }
1368
+ if (options.exactOriginalName) {
1369
+ return metadata.filter((tool) => tool.originalName === toolName && !tool.resourceUri);
1370
+ }
999
1371
  if (serverOverride) {
1000
- const originals = metadata.filter(tool => tool.originalName === toolName);
1001
- if (originals.length)
1372
+ const originals = metadata.filter((tool) => tool.originalName === toolName);
1373
+ if (originals.length) {
1002
1374
  return originals;
1375
+ }
1003
1376
  }
1004
- const exact = metadata.filter(tool => tool.name === toolName);
1005
- if (exact.length)
1377
+ const exact = metadata.filter((tool) => tool.name === toolName);
1378
+ if (exact.length) {
1006
1379
  return exact;
1007
- return metadata.filter(tool => tool.name.replace(/-/g, "_") === toolName.replace(/-/g, "_"));
1380
+ }
1381
+ return metadata.filter((tool) => tool.name.replace(/-/g, "_") === toolName.replace(/-/g, "_"));
1008
1382
  };
1009
1383
  const ambiguous = () => ({
1010
- content: [{ type: "text", text: `Tool "${toolName}" is ambiguous. Specify its exact server and tool name; no call was dispatched.` }],
1011
- details: { mode: "call", error: "ambiguous_tool", requestedTool: toolName, ...(serverName ? { server: serverName } : {}) },
1384
+ content: [
1385
+ {
1386
+ type: "text",
1387
+ text: `Tool "${toolName}" is ambiguous. Specify its exact server and tool name; no call was dispatched.`,
1388
+ },
1389
+ ],
1390
+ details: {
1391
+ mode: "call",
1392
+ error: "ambiguous_tool",
1393
+ requestedTool: toolName,
1394
+ ...(serverName ? { server: serverName } : {}),
1395
+ },
1012
1396
  });
1013
1397
  if (!serverName) {
1014
- const candidates = [...state.toolMetadata].flatMap(([server, metadata]) => isServerDisabled(state.config.mcpServers[server]) ? [] : matches(metadata).map(tool => ({ server, tool })));
1015
- if (candidates.length > 1)
1398
+ const candidates = [...state.toolMetadata].flatMap(([server, metadata]) => isServerDisabled(state.config.mcpServers[server])
1399
+ ? []
1400
+ : matches(metadata).map((tool) => ({ server, tool })));
1401
+ if (candidates.length > 1) {
1016
1402
  return ambiguous();
1403
+ }
1017
1404
  serverName = candidates[0]?.server;
1018
1405
  if (!serverName) {
1019
1406
  const disabled = [...state.toolMetadata].find(([server, metadata]) => isServerDisabled(state.config.mcpServers[server]) && matches(metadata).length > 0);
1020
- if (disabled)
1407
+ if (disabled) {
1021
1408
  return disabledResult("call", disabled[0]);
1409
+ }
1022
1410
  const prefixes = Object.entries(state.config.mcpServers)
1023
1411
  .filter(([, definition]) => !isServerDisabled(definition))
1024
- .map(([server, definition]) => ({ server, prefix: getServerPrefix(server, resolveToolPrefix(definition, state.config.settings?.toolPrefix ?? "server")) }))
1412
+ .map(([server, definition]) => ({
1413
+ server,
1414
+ prefix: getServerPrefix(server, resolveToolPrefix(definition, state.config.settings?.toolPrefix ?? "server")),
1415
+ }))
1025
1416
  .filter(({ prefix }) => prefix && toolName.startsWith(`${prefix}_`))
1026
1417
  .sort((a, b) => b.prefix.length - a.prefix.length);
1027
- if (prefixes.length > 1 && prefixes[0]?.prefix === prefixes[1]?.prefix)
1418
+ if (prefixes.length > 1 && prefixes[0]?.prefix === prefixes[1]?.prefix) {
1028
1419
  return ambiguous();
1420
+ }
1029
1421
  serverName = prefixes[0]?.server;
1030
1422
  }
1031
1423
  }
1032
1424
  const missing = () => {
1033
- const nativeTool = !serverOverride && getPiTools?.().find(tool => tool.name === toolName && tool.name !== "mcp");
1034
- if (nativeTool)
1425
+ const nativeTool = !serverOverride &&
1426
+ getPiTools?.().find((tool) => tool.name === toolName && tool.name !== "mcp");
1427
+ if (nativeTool) {
1035
1428
  return {
1036
- content: [{ type: "text", text: `"${toolName}" is a native Pi tool. Call ${toolName} directly instead of using mcp({ action: "call", tool: "${toolName}" }).` }],
1429
+ content: [
1430
+ {
1431
+ type: "text",
1432
+ text: `"${toolName}" is a native Pi tool. Call ${toolName} directly instead of using mcp({ action: "call", tool: "${toolName}" }).`,
1433
+ },
1434
+ ],
1037
1435
  details: { mode: "call", error: "native_tool", requestedTool: toolName },
1038
1436
  };
1437
+ }
1039
1438
  const normalizedName = toolName.replace(/-/g, "_");
1040
- const localOriginal = serverOverride && state.toolMetadata.get(serverOverride)?.some(tool => tool.originalName.replace(/-/g, "_") === normalizedName);
1439
+ const localOriginal = serverOverride &&
1440
+ state.toolMetadata
1441
+ .get(serverOverride)
1442
+ ?.some((tool) => tool.originalName.replace(/-/g, "_") === normalizedName);
1041
1443
  const owners = serverOverride && !localOriginal
1042
- ? [...state.toolMetadata].filter(([server, metadata]) => server !== serverOverride && !isServerDisabled(state.config.mcpServers[server]) && matches(metadata).length === 1)
1444
+ ? [...state.toolMetadata].filter(([server, metadata]) => server !== serverOverride &&
1445
+ !isServerDisabled(state.config.mcpServers[server]) &&
1446
+ matches(metadata).length === 1)
1043
1447
  : [];
1044
1448
  const suggestedOwner = owners.length === 1 ? owners[0] : undefined;
1045
- if (suggestedOwner)
1449
+ if (suggestedOwner) {
1046
1450
  return {
1047
- content: [{ type: "text", text: `Tool ${JSON.stringify(toolName)} is on server ${JSON.stringify(suggestedOwner[0])}, not ${JSON.stringify(serverOverride)}. Retry the same call with server: ${JSON.stringify(suggestedOwner[0])} or omit server.` }],
1048
- details: { mode: "call", error: "tool_not_found", requestedTool: toolName, hintServer: serverName, suggestedServer: suggestedOwner[0], suggestions: matches(suggestedOwner[1]).map(tool => tool.name) },
1451
+ content: [
1452
+ {
1453
+ type: "text",
1454
+ text: `Tool ${JSON.stringify(toolName)} is on server ${JSON.stringify(suggestedOwner[0])}, not ${JSON.stringify(serverOverride)}. Retry the same call with server: ${JSON.stringify(suggestedOwner[0])} or omit server.`,
1455
+ },
1456
+ ],
1457
+ details: {
1458
+ mode: "call",
1459
+ error: "tool_not_found",
1460
+ requestedTool: toolName,
1461
+ hintServer: serverName,
1462
+ suggestedServer: suggestedOwner[0],
1463
+ suggestions: matches(suggestedOwner[1]).map((tool) => tool.name),
1464
+ },
1049
1465
  };
1466
+ }
1050
1467
  const suggestions = rankSuggestions(state, toolName, 5, serverName);
1051
1468
  let hint = suggestions.length
1052
1469
  ? ` Did you mean: ${suggestions.join(", ")}. Inspect with mcp({ action: "describe", tool: ${JSON.stringify(suggestions[0])} }).`
1053
- : serverName ? ` Search with mcp({ action: "search", query: ${JSON.stringify(toolName)}, server: ${JSON.stringify(serverName)} }).` : ' Use mcp({ action: "search", query: "..." }) to search.';
1470
+ : serverName
1471
+ ? ` Search with mcp({ action: "search", query: ${JSON.stringify(toolName)}, server: ${JSON.stringify(serverName)} }).`
1472
+ : ' Use mcp({ action: "search", query: "..." }) to search.';
1054
1473
  hint += ' If mcp is inactive, load it with mcp_search({ enable: ["gateway"] }).';
1055
- if (serverName && state.toolMetadata.has(serverName) && state.manager.getConnection(serverName)?.status !== "connected")
1474
+ if (serverName &&
1475
+ state.toolMetadata.has(serverName) &&
1476
+ state.manager.getConnection(serverName)?.status !== "connected") {
1056
1477
  hint += ` Refresh a stale catalog with mcp({ action: "connect", server: ${JSON.stringify(serverName)} }).`;
1478
+ }
1057
1479
  return {
1058
1480
  content: [{ type: "text", text: `Tool "${toolName}" not found.${hint}` }],
1059
- details: { mode: "call", error: "tool_not_found", requestedTool: toolName, hintServer: serverName, suggestions },
1481
+ details: {
1482
+ mode: "call",
1483
+ error: "tool_not_found",
1484
+ requestedTool: toolName,
1485
+ hintServer: serverName,
1486
+ suggestions,
1487
+ },
1060
1488
  };
1061
1489
  };
1062
- if (!serverName)
1490
+ if (!serverName) {
1063
1491
  return missing();
1492
+ }
1064
1493
  const definition = state.config.mcpServers[serverName];
1065
- if (!definition)
1066
- return { content: [{ type: "text", text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).` }], details: { mode: "call", error: "server_not_found", server: serverName, requestedTool: toolName } };
1067
- if (isServerDisabled(definition))
1494
+ if (!definition) {
1495
+ return {
1496
+ content: [
1497
+ {
1498
+ type: "text",
1499
+ text: `Server "${serverName}" not found. Use mcp({ action: "status" }) to see available servers (load it first with mcp_search({ enable: ["gateway"] }) if inactive).`,
1500
+ },
1501
+ ],
1502
+ details: {
1503
+ mode: "call",
1504
+ error: "server_not_found",
1505
+ server: serverName,
1506
+ requestedTool: toolName,
1507
+ },
1508
+ };
1509
+ }
1510
+ if (isServerDisabled(definition)) {
1068
1511
  return disabledResult("call", serverName);
1512
+ }
1069
1513
  const cached = state.toolMetadata.get(serverName);
1070
- if (matches(cached).length > 1)
1514
+ if (matches(cached).length > 1) {
1071
1515
  return ambiguous();
1516
+ }
1072
1517
  // Misspellings in a known catalog do not cause connection churn or guessed execution.
1073
- if (cached && !matches(cached).length && !options.exactOriginalName && options.resourceUri === undefined)
1518
+ if (cached &&
1519
+ !matches(cached).length &&
1520
+ !options.exactOriginalName &&
1521
+ options.resourceUri === undefined) {
1074
1522
  return missing();
1523
+ }
1075
1524
  const prepared = await prepareMcpConnection(state, serverName, ownedSignal);
1076
1525
  if (prepared.error) {
1077
1526
  const cachedTarget = matches(cached)[0];
1078
- return { ...prepared.error, details: { ...prepared.error.details, ...(cachedTarget
1079
- ? cachedTarget.resourceUri ? { resourceUri: cachedTarget.resourceUri } : { tool: cachedTarget.originalName }
1080
- : { requestedTool: toolName }) } };
1527
+ return {
1528
+ ...prepared.error,
1529
+ details: {
1530
+ ...prepared.error.details,
1531
+ ...(cachedTarget
1532
+ ? cachedTarget.resourceUri
1533
+ ? { resourceUri: cachedTarget.resourceUri }
1534
+ : { tool: cachedTarget.originalName }
1535
+ : { requestedTool: toolName }),
1536
+ },
1537
+ };
1081
1538
  }
1082
1539
  const targets = matches(state.toolMetadata.get(serverName));
1083
- if (targets.length > 1)
1540
+ if (targets.length > 1) {
1084
1541
  return ambiguous();
1542
+ }
1085
1543
  const toolMeta = targets[0];
1086
- if (!toolMeta)
1544
+ if (!toolMeta) {
1087
1545
  return missing();
1088
- if (isServerDisabled(state.config.mcpServers[serverName]))
1546
+ }
1547
+ if (isServerDisabled(state.config.mcpServers[serverName])) {
1089
1548
  return disabledResult("call", serverName);
1549
+ }
1090
1550
  const approval = await ensureToolCallApproved(state, serverName, toolMeta, args, ownedSignal);
1091
1551
  if (!approval.ok) {
1092
1552
  const denied = approval.reason === "denied";
1093
1553
  return {
1094
- content: [{ type: "text", text: denied
1554
+ content: [
1555
+ {
1556
+ type: "text",
1557
+ text: denied
1095
1558
  ? `The user declined approval to run MCP tool "${toolMeta.originalName}" on server "${serverName}".`
1096
- : `MCP tool "${toolMeta.originalName}" on server "${serverName}" is approval-gated and requires an interactive session.` }],
1097
- details: { mode: "call", error: denied ? "approval_denied" : "approval_required", server: serverName, tool: toolMeta.originalName },
1559
+ : `MCP tool "${toolMeta.originalName}" on server "${serverName}" is approval-gated and requires an interactive session.`,
1560
+ },
1561
+ ],
1562
+ details: {
1563
+ mode: "call",
1564
+ error: denied ? "approval_denied" : "approval_required",
1565
+ server: serverName,
1566
+ tool: toolMeta.originalName,
1567
+ },
1098
1568
  };
1099
1569
  }
1100
1570
  return runToolCall(state, serverName, toolMeta, args, {
1101
1571
  ...identity,
1102
1572
  ...(beforeDispatch ? { beforeDispatch } : {}),
1103
1573
  ...(options.raw !== undefined ? { raw: options.raw } : {}),
1104
- detailsBase: { mode: "call", server: serverName, ...(toolMeta.resourceUri ? { resourceUri: toolMeta.resourceUri } : { tool: toolMeta.originalName }) },
1105
- ownedSignal, signal,
1574
+ detailsBase: {
1575
+ mode: "call",
1576
+ server: serverName,
1577
+ ...(toolMeta.resourceUri
1578
+ ? { resourceUri: toolMeta.resourceUri }
1579
+ : { tool: toolMeta.originalName }),
1580
+ },
1581
+ ownedSignal,
1582
+ signal,
1106
1583
  recoverAuthConnection: prepared.recoverAuthConnection,
1107
1584
  authRequiredMessage: () => getAuthRequiredMessage(state, serverName),
1108
1585
  autoAuthAttempted: prepared.autoAuthAttempted,