@fitchmultz/pi-mcp-adapter 8.0.5 → 8.0.7

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 (88) hide show
  1. package/CHANGELOG.md +140 -7
  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.d.ts +3 -3
  73. package/dist/tool-result-renderer.js +65 -44
  74. package/dist/types.d.ts +2 -2
  75. package/dist/types.js +39 -18
  76. package/dist/ui-message-history.js +33 -13
  77. package/dist/ui-resource-handler.d.ts +3 -3
  78. package/dist/ui-resource-handler.js +37 -21
  79. package/dist/ui-server.js +101 -43
  80. package/dist/ui-session.js +66 -29
  81. package/dist/ui-stream-types.js +14 -4
  82. package/dist/ui-tool-visibility.js +12 -6
  83. package/dist/unix-socket-transport.d.ts +1 -2
  84. package/dist/unix-socket-transport.js +19 -12
  85. package/dist/utils.d.ts +2 -0
  86. package/dist/utils.js +60 -35
  87. package/package.json +29 -5
  88. package/skills/mcp-scripting/SKILL.md +6 -6
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import { Type } from "typebox";
2
- import { cloneMcpConfig, isPathInsideProject, loadMcpConfig, writeProjectServerDisabledOverride } from "./config.js";
3
- import { buildProxyDescription, directToolSelection, getMissingConfiguredDirectToolServers } from "./direct-tools.js";
4
- import { getMetadataCachePath, isServerCacheValid, loadMetadataCache, reconstructToolMetadata } from "./metadata-cache.js";
2
+ import { cloneMcpConfig, isPathInsideProject, loadMcpConfig, writeProjectServerDisabledOverride, } from "./config.js";
3
+ import { buildProxyDescription, directToolSelection, getMissingConfiguredDirectToolServers, } from "./direct-tools.js";
4
+ import { getMetadataCachePath, isServerCacheValid, loadMetadataCache, reconstructToolMetadata, } from "./metadata-cache.js";
5
5
  import { createPromptCommand, resolveCachedPrompts } from "./prompts.js";
6
6
  import { logger } from "./logger.js";
7
- import { DEFAULT_MCP_SCRIPT_TIMEOUT_MS, formatTerminalError } from "./utils.js";
7
+ import { DEFAULT_MCP_SCRIPT_TIMEOUT_MS, formatTerminalError, MAX_CONCURRENT_SERVER_CONNECTS, parallelLimit, } from "./utils.js";
8
8
  import { createOAuthRuntime, shutdownOAuth } from "./mcp-oauth-runtime.js";
9
9
  import { renderMcpProxyToolCall, renderMcpToolResult } from "./tool-result-renderer.js";
10
- import { createMcpRuntimeOwner, createOwnedUi, isAbortError } from "./runtime-owner.js";
10
+ import { createMcpRuntimeOwner, createOwnedUi, isAbortError, } from "./runtime-owner.js";
11
11
  import { publishMcpStatusShutdown } from "./mcp-status.js";
12
12
  import { MAX_PAGE_SIZE, paginate, rankToolMatches } from "./search-ranking.js";
13
13
  import { createToolLoader } from "./tool-loader.js";
14
- import { gatewayParameters, prepareGatewayArguments } from "./gateway-arguments.js";
15
- import { guardMcpOutput, guardedMcpDetails, readMcpResult, resolveMcpOutputGuardOptions } from "./mcp-output-guard.js";
14
+ import { gatewayParameters, prepareGatewayArguments, } from "./gateway-arguments.js";
15
+ import { guardMcpOutput, guardedMcpDetails, readMcpResult, resolveMcpOutputGuardOptions, } from "./mcp-output-guard.js";
16
16
  import { abortable, throwIfAborted } from "./abort.js";
17
17
  export { MCP_STATUS_EVENT, MCP_STATUS_SNAPSHOT_VERSION, } from "./types.js";
18
18
  const INIT_WAIT_TIMEOUT_MS = 30_000;
@@ -71,43 +71,49 @@ function installMcpAdapter(pi, options) {
71
71
  failures.push(error);
72
72
  }
73
73
  try {
74
- if (currentState.owner)
74
+ if (currentState.owner) {
75
75
  await currentState.owner.stop(reason);
76
- else
76
+ }
77
+ else {
77
78
  await currentState.lifecycle.gracefulShutdown();
79
+ }
78
80
  }
79
81
  catch (error) {
80
82
  failures.push(error);
81
83
  }
82
- if (failures.length)
84
+ if (failures.length) {
83
85
  throw new AggregateError(failures, "MCP state persistence/cleanup failed");
86
+ }
84
87
  }
85
- const earlyConfig = programmaticConfig
86
- ? cloneMcpConfig(sessionConfig)
87
- : { mcpServers: {} };
88
+ const earlyConfig = programmaticConfig ? cloneMcpConfig(sessionConfig) : { mcpServers: {} };
88
89
  const earlyCache = null;
89
90
  let currentConfig = earlyConfig;
90
91
  const envRaw = process.env.MCP_DIRECT_TOOLS;
91
- const envDirectToolOverride = envRaw?.split(",").map(s => s.trim()).filter(Boolean);
92
+ const envDirectToolOverride = envRaw
93
+ ?.split(",")
94
+ .map((s) => s.trim())
95
+ .filter(Boolean);
92
96
  const toolLoader = createToolLoader(pi, () => state, () => initPromise, beforeExecute);
93
97
  let runtimeCwd;
94
98
  let proxyToolRegistered = false;
95
99
  let proxyToolDescription = null;
96
100
  let directToolsFrozen = false;
97
101
  function applyDirectToolConfigChanges(changes) {
98
- if (!state)
102
+ if (!state) {
99
103
  return;
104
+ }
100
105
  for (const [serverName, value] of changes) {
101
106
  const definition = state.config.mcpServers[serverName];
102
- if (!definition)
107
+ if (!definition) {
103
108
  continue;
109
+ }
104
110
  state.config.mcpServers[serverName] = { ...definition, directTools: value };
105
111
  }
106
112
  }
107
113
  function syncToolSurface(ctx) {
108
114
  const config = state?.config ?? currentConfig;
109
115
  const cache = loadMetadataCache(state?.metadataCacheEnabled ?? false);
110
- toolLoader.sync(config, cache, envRaw === "__none__" ? null : envDirectToolOverride, specs => syncGateways(config, cache, specs));
116
+ toolLoader.sync(config, cache, envRaw === "__none__" ? null : envDirectToolOverride, (specs) => syncGateways(config, cache, specs));
111
117
  }
112
118
  const registeredPromptCommands = new Set();
113
119
  function registerPromptCommands(specs) {
@@ -123,8 +129,9 @@ function installMcpAdapter(pi, options) {
123
129
  function syncPromptCommands() {
124
130
  registerPromptCommands([...(state?.promptMetadata?.values() ?? [])].flat());
125
131
  }
126
- if (!options.transformConfig)
132
+ if (!options.transformConfig) {
127
133
  registerPromptCommands(resolveCachedPrompts(earlyConfig, false));
134
+ }
128
135
  const getPiTools = () => pi.getAllTools();
129
136
  pi.registerFlag("mcp-config", {
130
137
  description: "Path to MCP config file",
@@ -133,13 +140,16 @@ function installMcpAdapter(pi, options) {
133
140
  function startInitialization(init, ctx, owner, oauthRuntime, runtimeConfig, generation) {
134
141
  const promise = init.initializeMcp(pi, ctx, owner, {
135
142
  ...(programmaticConfig ? { config: runtimeConfig } : { resolvedConfig: runtimeConfig }),
136
- ...(options.outputDirectory !== undefined ? { outputDirectory: options.outputDirectory } : {}),
143
+ ...(options.outputDirectory !== undefined
144
+ ? { outputDirectory: options.outputDirectory }
145
+ : {}),
137
146
  oauthRuntime,
138
147
  statusEvents: pi.events,
139
148
  ...(options.onToolCall ? { onToolCall: options.onToolCall } : {}),
140
149
  });
141
150
  initPromise = promise;
142
- return promise.then(async (nextState) => {
151
+ return promise
152
+ .then(async (nextState) => {
143
153
  if (!owner.isActive() || generation !== lifecycleGeneration || initPromise !== promise) {
144
154
  try {
145
155
  await shutdownState(nextState, "stale_session_start");
@@ -151,8 +161,9 @@ function installMcpAdapter(pi, options) {
151
161
  }
152
162
  state = nextState;
153
163
  nextState.onToolMetadataUpdated = (_serverName, _reason) => {
154
- if (state !== nextState || !owner.isActive())
164
+ if (state !== nextState || !owner.isActive()) {
155
165
  return;
166
+ }
156
167
  syncPromptCommands();
157
168
  if (directToolsFrozen) {
158
169
  logger.debug(`MCP: metadata update for ${_serverName} (${_reason}) skipped — directTools frozen`);
@@ -160,9 +171,11 @@ function installMcpAdapter(pi, options) {
160
171
  }
161
172
  syncToolSurface(ctx);
162
173
  };
163
- for (const server of toolLoader.selectedServers()) {
164
- if (!nextState.toolMetadata.has(server) && nextState.config.mcpServers[server])
165
- await init.lazyConnect(nextState, server, owner.signal);
174
+ const uncachedSelectedServers = toolLoader
175
+ .selectedServers()
176
+ .filter((server) => !nextState.toolMetadata.has(server) && nextState.config.mcpServers[server]);
177
+ if (uncachedSelectedServers.length > 0) {
178
+ await parallelLimit(uncachedSelectedServers, MAX_CONCURRENT_SERVER_CONNECTS, (server) => init.lazyConnect(nextState, server, owner.signal));
166
179
  }
167
180
  owner.throwIfInactive();
168
181
  syncPromptCommands();
@@ -171,9 +184,10 @@ function installMcpAdapter(pi, options) {
171
184
  initPromise = null;
172
185
  if (nextState.config.settings?.freezeDirectTools === true) {
173
186
  directToolsFrozen = true;
174
- logger.info("MCP: direct tools frozen after initial sync — reconnects won't rebuild the system prompt; use mcp({ action: \"connect\", server: \"server\" }) to rediscover");
187
+ logger.info('MCP: direct tools frozen after initial sync — reconnects won\'t rebuild the system prompt; use mcp({ action: "connect", server: "server" }) to rediscover');
175
188
  }
176
- }).catch(async (err) => {
189
+ })
190
+ .catch(async (err) => {
177
191
  if (!owner.isActive() || generation !== lifecycleGeneration) {
178
192
  return;
179
193
  }
@@ -182,13 +196,11 @@ function installMcpAdapter(pi, options) {
182
196
  }
183
197
  console.error(`MCP initialization failed: ${formatTerminalError(err)}`);
184
198
  initPromise = null;
185
- if (state)
199
+ if (state) {
186
200
  return;
201
+ }
187
202
  try {
188
- await Promise.all([
189
- owner.stop("MCP initialization failed"),
190
- shutdownOAuth(oauthRuntime),
191
- ]);
203
+ await Promise.all([owner.stop("MCP initialization failed"), shutdownOAuth(oauthRuntime)]);
192
204
  }
193
205
  catch (error) {
194
206
  console.error(`MCP: failed to clean rejected initialization: ${formatTerminalError(error)}`);
@@ -221,8 +233,9 @@ function installMcpAdapter(pi, options) {
221
233
  catch (error) {
222
234
  console.error(`MCP: failed to shut down previous session state: ${formatTerminalError(error)}`);
223
235
  }
224
- if (generation !== lifecycleGeneration || !owner.isActive())
236
+ if (generation !== lifecycleGeneration || !owner.isActive()) {
225
237
  return;
238
+ }
226
239
  let runtimeConfig;
227
240
  if (programmaticConfig) {
228
241
  currentConfigPath = undefined;
@@ -230,24 +243,27 @@ function installMcpAdapter(pi, options) {
230
243
  }
231
244
  else {
232
245
  const registeredConfigPath = pi.getFlag("mcp-config");
233
- currentConfigPath = options.configPath
234
- ?? (typeof registeredConfigPath === "string" ? registeredConfigPath : undefined);
235
- runtimeConfig = loadMcpConfig(currentConfigPath, ctx.cwd, { includeProject: ctx.isProjectTrusted() });
246
+ currentConfigPath =
247
+ options.configPath ??
248
+ (typeof registeredConfigPath === "string" ? registeredConfigPath : undefined);
249
+ runtimeConfig = loadMcpConfig(currentConfigPath, ctx.cwd, {
250
+ includeProject: ctx.isProjectTrusted(),
251
+ });
236
252
  }
237
253
  if (options.transformConfig) {
238
254
  runtimeConfig = cloneMcpConfig(options.transformConfig(cloneMcpConfig(runtimeConfig), ctx));
239
255
  }
240
256
  currentConfig = runtimeConfig;
241
- const metadataCacheEnabled = ctx.isProjectTrusted()
242
- || !isPathInsideProject(getMetadataCachePath(), ctx.cwd);
257
+ const metadataCacheEnabled = ctx.isProjectTrusted() || !isPathInsideProject(getMetadataCachePath(), ctx.cwd);
243
258
  toolLoader.restore(ctx);
244
259
  registerPromptCommands(resolveCachedPrompts(runtimeConfig, metadataCacheEnabled));
245
260
  const runtimeCache = loadMetadataCache(metadataCacheEnabled);
246
- toolLoader.sync(runtimeConfig, runtimeCache, envRaw === "__none__" ? null : envDirectToolOverride, specs => syncGateways(runtimeConfig, runtimeCache, specs));
261
+ toolLoader.sync(runtimeConfig, runtimeCache, envRaw === "__none__" ? null : envDirectToolOverride, (specs) => syncGateways(runtimeConfig, runtimeCache, specs));
247
262
  const init = await import("./init.js");
248
263
  initModule = init;
249
- if (generation !== lifecycleGeneration || !owner.isActive())
264
+ if (generation !== lifecycleGeneration || !owner.isActive()) {
250
265
  return;
266
+ }
251
267
  const initialization = startInitialization(init, ctx, owner, oauthRuntime, runtimeConfig, generation);
252
268
  if (envRaw !== undefined && envRaw !== "__none__") {
253
269
  const missingEnvDirectTools = getMissingConfiguredDirectToolServers(runtimeConfig, runtimeCache, envDirectToolOverride);
@@ -262,12 +278,14 @@ function installMcpAdapter(pi, options) {
262
278
  syncToolSurface(ctx);
263
279
  });
264
280
  pi.on("model_select", (_event, ctx) => {
265
- if (state)
281
+ if (state) {
266
282
  syncToolSurface(ctx);
283
+ }
267
284
  });
268
285
  pi.on("before_agent_start", async (_event, ctx) => {
269
- if (ctx.cwd !== runtimeCwd)
286
+ if (ctx.cwd !== runtimeCwd) {
270
287
  await startSession(ctx);
288
+ }
271
289
  });
272
290
  pi.on("session_shutdown", async () => {
273
291
  // Cached tool selections exist before background server initialization finishes.
@@ -288,10 +306,11 @@ function installMcpAdapter(pi, options) {
288
306
  shutdownState(currentState, "session_shutdown"),
289
307
  oauthRuntime ? shutdownOAuth(oauthRuntime) : Promise.resolve(),
290
308
  ]);
291
- const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : []);
309
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
292
310
  // Ordinary extension error handling owns reporting. Strict clean-exit hosts must see failure.
293
- if (failures.length)
311
+ if (failures.length) {
294
312
  throw new AggregateError(failures, "MCP session shutdown persistence/cleanup failed");
313
+ }
295
314
  });
296
315
  function createCommandContext(ctx) {
297
316
  const owner = currentOwner;
@@ -322,8 +341,9 @@ function installMcpAdapter(pi, options) {
322
341
  return null;
323
342
  }
324
343
  }
325
- if (!state)
344
+ if (!state) {
326
345
  ctx.ui?.notify("MCP not initialized", "error");
346
+ }
327
347
  return state;
328
348
  }
329
349
  pi.registerCommand("mcp", {
@@ -345,10 +365,14 @@ function installMcpAdapter(pi, options) {
345
365
  return subcommands.length > 0 ? subcommands : null;
346
366
  }
347
367
  const [, subcommand, argumentPrefix] = argumentMatch;
348
- if ((subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable")
349
- || argumentPrefix === undefined
350
- || !state)
368
+ if ((subcommand !== "reconnect" &&
369
+ subcommand !== "logout" &&
370
+ subcommand !== "disable" &&
371
+ subcommand !== "enable") ||
372
+ argumentPrefix === undefined ||
373
+ !state) {
351
374
  return null;
375
+ }
352
376
  const servers = Object.keys(state.config.mcpServers)
353
377
  .filter((serverName) => serverName.startsWith(argumentPrefix.trimStart()))
354
378
  .map((serverName) => ({ value: `${subcommand} ${serverName}`, label: serverName }));
@@ -358,9 +382,10 @@ function installMcpAdapter(pi, options) {
358
382
  const { owner: commandOwner, context: commandCtx } = createCommandContext(ctx);
359
383
  const commandProjectTrusted = commandCtx.isProjectTrusted();
360
384
  const commandState = await ensureCommandState(commandOwner, commandCtx);
361
- if (!commandState)
385
+ if (!commandState) {
362
386
  return;
363
- const { showStatus, showTools, showPrompts, reconnectServers, logoutServer, openMcpPanel, openMcpSetup } = await import("./commands.js");
387
+ }
388
+ const { showStatus, showTools, showPrompts, reconnectServers, logoutServer, openMcpPanel, openMcpSetup, } = await import("./commands.js");
364
389
  const parts = args?.trim()?.split(/\s+/) ?? [];
365
390
  const subcommand = parts[0] ?? "";
366
391
  const targetServer = parts[1];
@@ -369,8 +394,9 @@ function installMcpAdapter(pi, options) {
369
394
  case "reconnect":
370
395
  commandOwner?.throwIfInactive();
371
396
  await reconnectServers(commandState, commandCtx, targetServer);
372
- if (directToolsFrozen)
397
+ if (directToolsFrozen) {
373
398
  syncToolSurface(commandCtx);
399
+ }
374
400
  break;
375
401
  case "tools":
376
402
  await showTools(commandState, commandCtx);
@@ -395,8 +421,9 @@ function installMcpAdapter(pi, options) {
395
421
  case "logout": {
396
422
  const serverName = rest;
397
423
  if (!serverName) {
398
- if (commandCtx.hasUI)
424
+ if (commandCtx.hasUI) {
399
425
  commandCtx.ui?.notify("Usage: /mcp logout <server>", "error");
426
+ }
400
427
  return;
401
428
  }
402
429
  commandOwner?.throwIfInactive();
@@ -468,8 +495,9 @@ function installMcpAdapter(pi, options) {
468
495
  return;
469
496
  }
470
497
  const commandState = await ensureCommandState(commandOwner, commandCtx);
471
- if (!commandState)
498
+ if (!commandState) {
472
499
  return;
500
+ }
473
501
  const { authenticateServer, openMcpAuthPanel, reconnectServer } = await import("./commands.js");
474
502
  if (!serverName) {
475
503
  if (programmaticConfig) {
@@ -491,76 +519,143 @@ function installMcpAdapter(pi, options) {
491
519
  if (!state && initPromise) {
492
520
  try {
493
521
  const initialized = await awaitWithTimeout(abortable(initPromise, signal), INIT_WAIT_TIMEOUT_MS);
494
- if (initialized === INIT_WAIT_TIMED_OUT)
522
+ if (initialized === INIT_WAIT_TIMED_OUT) {
495
523
  return {
496
- content: [{ type: "text", text: "MCP initialization is still in progress. Try again shortly." }],
524
+ content: [
525
+ {
526
+ type: "text",
527
+ text: "MCP initialization is still in progress. Try again shortly.",
528
+ },
529
+ ],
497
530
  details: { error: "init_timeout", timeoutMs: INIT_WAIT_TIMEOUT_MS },
498
531
  };
532
+ }
499
533
  owner?.throwIfInactive();
500
534
  state = initialized;
501
535
  }
502
536
  catch (error) {
503
- if (signal?.aborted || (owner && isAbortError(error, owner.signal)))
537
+ if (signal?.aborted || (owner && isAbortError(error, owner.signal))) {
504
538
  throw error;
539
+ }
505
540
  const message = error instanceof Error ? error.message : String(error);
506
- return { content: [{ type: "text", text: `MCP initialization failed: ${message}` }], details: { error: "init_failed", message } };
541
+ return {
542
+ content: [{ type: "text", text: `MCP initialization failed: ${message}` }],
543
+ details: { error: "init_failed", message },
544
+ };
507
545
  }
508
546
  }
509
547
  owner?.throwIfInactive();
510
- return state ?? { content: [{ type: "text", text: "MCP not initialized" }], details: { error: "not_initialized" } };
548
+ return (state ?? {
549
+ content: [{ type: "text", text: "MCP not initialized" }],
550
+ details: { error: "not_initialized" },
551
+ });
511
552
  }
512
- const unavailableFeatures = (enable = [], tools) => enable.filter(feature => !tools.some(tool => tool.name === (feature === "gateway" ? "mcp" : "mcp_script")));
553
+ const unavailableFeatures = (enable = [], tools) => enable.filter((feature) => !tools.some((tool) => tool.name === (feature === "gateway" ? "mcp" : "mcp_script")));
513
554
  async function searchAndLoad(params, signal, ctx) {
514
555
  throwIfAborted(signal);
515
- if (params.query === undefined && !params.enable?.length)
556
+ if (params.query === undefined && !params.enable?.length) {
516
557
  throw new Error("Provide query or enable.");
558
+ }
517
559
  // Loading front doors does not require initialization or connect to a server.
518
560
  if (params.query === undefined) {
519
561
  const tools = toolLoader.activate([], params.enable);
520
562
  const unavailable = unavailableFeatures(params.enable, tools);
521
- return { content: [{ type: "text", text: `Loaded for the next request: ${JSON.stringify(tools)}${unavailable.length ? `. Unavailable under configuration or host policy: ${unavailable.join(", ")}` : ""}` }], details: { loaded: tools, unavailable }, tools };
563
+ return {
564
+ content: [
565
+ {
566
+ type: "text",
567
+ text: `Loaded for the next request: ${JSON.stringify(tools)}${unavailable.length ? `. Unavailable under configuration or host policy: ${unavailable.join(", ")}` : ""}`,
568
+ },
569
+ ],
570
+ details: { loaded: tools, unavailable },
571
+ tools,
572
+ };
522
573
  }
523
574
  const ready = await getToolState(signal);
524
- if ("content" in ready)
575
+ if ("content" in ready) {
525
576
  return { ...ready, tools: [] };
577
+ }
526
578
  const proxy = await import("./proxy-modes.js");
527
579
  if (params.server && !ready.toolMetadata.has(params.server)) {
528
580
  const discovery = await proxy.executeConnect(ready, params.server, signal);
529
- if (discovery.details.error)
581
+ if (discovery.details.error) {
530
582
  return { ...discovery, tools: [] };
583
+ }
531
584
  }
532
585
  syncToolSurface(ctx);
533
- const matches = params.query.trim() ? rankToolMatches(ready, params.query, params.server)
534
- : params.server ? (ready.toolMetadata.get(params.server) ?? []).filter(tool => !tool.resourceUri).map(tool => ({ server: params.server, tool })).sort((a, b) => a.tool.name.localeCompare(b.tool.name)) : [];
586
+ const matches = params.query.trim()
587
+ ? rankToolMatches(ready, params.query, params.server)
588
+ : params.server
589
+ ? (ready.toolMetadata.get(params.server) ?? [])
590
+ .filter((tool) => !tool.resourceUri)
591
+ .map((tool) => ({ server: params.server, tool }))
592
+ .sort((a, b) => a.tool.name.localeCompare(b.tool.name))
593
+ : [];
535
594
  const page = paginate(matches, params.offset ?? 0, params.limit ?? 5);
536
595
  const tools = toolLoader.activate(page.items, params.enable);
537
596
  const unavailable = unavailableFeatures(params.enable, tools);
538
- const typedCount = tools.filter(tool => tool.name !== "mcp" && tool.name !== "mcp_script").length;
597
+ const typedCount = tools.filter((tool) => tool.name !== "mcp" && tool.name !== "mcp_script").length;
539
598
  const result = proxy.executeSearch(ready, params.query, params.server, typedCount < page.items.length, params.limit ?? 5, params.offset, "mcp_search");
540
599
  const guarded = await guardMcpOutput([
541
600
  ...result.content,
542
- { type: "text", text: tools.length ? `Loaded for the next request: ${JSON.stringify(tools)}` : 'No direct tools loaded. Enable gateway or script with mcp_search({ enable: ["gateway"] }) or mcp_search({ enable: ["script"] }), then use mcp action call or tools.call with the returned exact names.' },
543
- ...(unavailable.length ? [{ type: "text", text: `Unavailable under configuration or host policy: ${unavailable.join(", ")}` }] : []),
601
+ {
602
+ type: "text",
603
+ text: tools.length
604
+ ? `Loaded for the next request: ${JSON.stringify(tools)}`
605
+ : 'No direct tools loaded. Enable gateway or script with mcp_search({ enable: ["gateway"] }) or mcp_search({ enable: ["script"] }), then use mcp action call or tools.call with the returned exact names.',
606
+ },
607
+ ...(unavailable.length
608
+ ? [
609
+ {
610
+ type: "text",
611
+ text: `Unavailable under configuration or host policy: ${unavailable.join(", ")}`,
612
+ },
613
+ ]
614
+ : []),
544
615
  ], resolveMcpOutputGuardOptions(ready.config.settings, ready.outputDirectory));
545
- return { content: guarded.content, details: { ...result.details, ...guardedMcpDetails(guarded), loaded: tools, ...(unavailable.length ? { unavailable } : {}) }, tools };
616
+ return {
617
+ content: guarded.content,
618
+ details: {
619
+ ...result.details,
620
+ ...guardedMcpDetails(guarded),
621
+ loaded: tools,
622
+ ...(unavailable.length ? { unavailable } : {}),
623
+ },
624
+ tools,
625
+ };
546
626
  }
547
627
  const searchDefinition = {
548
- name: "mcp_search", label: "MCP Search",
628
+ name: "mcp_search",
629
+ label: "MCP Search",
549
630
  description: "Discover and load typed MCP tools for the next request. Use query; server discovers an uncached server (empty query browses it). Results report incomplete coverage. Or enable gateway for mcp status/list/search/describe/call/connect/instructions, resources/read-resource, read-result, auth-start/auth-complete and ui-messages; enable script for JavaScript composition, resources and saved-result readback. Enable alone performs no integration action. Loaded schemas stay available in this session/branch.",
550
631
  parameters: Type.Object({
551
632
  query: Type.Optional(Type.String()),
552
- enable: Type.Optional(Type.Array(Type.Union([Type.Literal("gateway"), Type.Literal("script")]), { minItems: 1, uniqueItems: true })),
633
+ enable: Type.Optional(Type.Array(Type.Union([Type.Literal("gateway"), Type.Literal("script")]), {
634
+ minItems: 1,
635
+ uniqueItems: true,
636
+ })),
553
637
  server: Type.Optional(Type.String()),
554
- limit: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_PAGE_SIZE, description: "Tools to load (default 5)." })),
638
+ limit: Type.Optional(Type.Integer({
639
+ minimum: 1,
640
+ maximum: MAX_PAGE_SIZE,
641
+ description: "Tools to load (default 5).",
642
+ })),
555
643
  offset: Type.Optional(Type.Integer({ minimum: 0 })),
556
- }, { anyOf: [{ type: "object", required: ["query"] }, { type: "object", required: ["enable"] }] }),
644
+ }, {
645
+ anyOf: [
646
+ { type: "object", required: ["query"] },
647
+ { type: "object", required: ["enable"] },
648
+ ],
649
+ }),
557
650
  renderResult: renderMcpToolResult,
558
651
  async execute(toolCallId, params, signal, _onUpdate, ctx) {
559
652
  if (beforeExecute) {
560
- if (currentOwner)
653
+ if (currentOwner) {
561
654
  await currentOwner.runCallback("beforeExecute", () => beforeExecute(toolCallId, ctx));
562
- else
655
+ }
656
+ else {
563
657
  await beforeExecute(toolCallId, ctx);
658
+ }
564
659
  }
565
660
  return searchAndLoad(params, signal, ctx);
566
661
  },
@@ -568,8 +663,9 @@ function installMcpAdapter(pi, options) {
568
663
  pi.registerTool(searchDefinition);
569
664
  let scriptToolRegistered = false;
570
665
  function registerScriptTool() {
571
- if (scriptToolRegistered)
666
+ if (scriptToolRegistered) {
572
667
  return;
668
+ }
573
669
  toolLoader.registerFeature("script", {
574
670
  ...(beforeExecute ? { executionMode: "sequential" } : {}),
575
671
  name: "mcp_script",
@@ -577,18 +673,26 @@ function installMcpAdapter(pi, options) {
577
673
  description: "Compose MCP calls with trusted JavaScript: await tools.search({query, server?}), tools.describe({path}), tools.call(path,args). Describe returns complete JSON schemas. Calls return {ok,data} with raw MCP results, or {ok:false,error,data?}. Resources: tools.resources({server}), tools.readResource({server,uri}). Saved results: tools.readResult({ref,path?,fields?,offset?,limit?}); never repeats a remote call. Use emit(value) to return selected data or content blocks. No Node, filesystem, or network globals. Prefer a direct tool for one call; use scripts for loops, filtering and chains.",
578
674
  promptSnippet: "Batch multiple MCP tool calls in one JavaScript request (loop, filter, chain)",
579
675
  parameters: Type.Object({
580
- code: Type.String({ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value)." }),
581
- timeoutMs: Type.Optional(Type.Number({ minimum: 1, description: options.defaultScriptTimeoutMs === null
676
+ code: Type.String({
677
+ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value).",
678
+ }),
679
+ timeoutMs: Type.Optional(Type.Number({
680
+ minimum: 1,
681
+ description: options.defaultScriptTimeoutMs === null
582
682
  ? "Execution timeout in milliseconds (no default deadline)"
583
- : `Execution timeout in milliseconds (default: ${options.defaultScriptTimeoutMs ?? DEFAULT_MCP_SCRIPT_TIMEOUT_MS})` })),
683
+ : `Execution timeout in milliseconds (default: ${options.defaultScriptTimeoutMs ?? DEFAULT_MCP_SCRIPT_TIMEOUT_MS})`,
684
+ })),
584
685
  }),
585
686
  renderResult: renderMcpToolResult,
586
687
  async execute(toolCallId, params, signal, _onUpdate, ctx) {
587
688
  const ready = await getToolState(signal);
588
- if ("content" in ready)
689
+ if ("content" in ready) {
589
690
  return ready;
691
+ }
590
692
  const { runMcpScript } = await import("./mcp-code.js");
591
- return runMcpScript(ready, params.code, params.timeoutMs ?? options.defaultScriptTimeoutMs, getPiTools, signal, toolCallId, beforeExecute ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) : undefined);
693
+ return runMcpScript(ready, params.code, params.timeoutMs ?? options.defaultScriptTimeoutMs, getPiTools, signal, toolCallId, beforeExecute
694
+ ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation)
695
+ : undefined);
592
696
  },
593
697
  });
594
698
  scriptToolRegistered = true;
@@ -596,7 +700,9 @@ function installMcpAdapter(pi, options) {
596
700
  function registerProxyTool(description) {
597
701
  toolLoader.registerFeature("gateway", {
598
702
  ...(beforeExecute ? { executionMode: "sequential" } : {}),
599
- name: "mcp", label: "MCP", description,
703
+ name: "mcp",
704
+ label: "MCP",
705
+ description,
600
706
  promptSnippet: "MCP server operations, resources, saved result readback, and fallback tool calls",
601
707
  renderCall: renderMcpProxyToolCall,
602
708
  parameters: gatewayParameters,
@@ -605,51 +711,78 @@ function installMcpAdapter(pi, options) {
605
711
  async execute(toolCallId, rawParams, signal, _onUpdate, ctx) {
606
712
  const params = prepareGatewayArguments(rawParams);
607
713
  if (beforeExecute && params.action !== "call" && params.action !== "read-resource") {
608
- if (currentOwner)
714
+ if (currentOwner) {
609
715
  await currentOwner.runCallback("beforeExecute", () => beforeExecute(toolCallId, ctx));
610
- else
716
+ }
717
+ else {
611
718
  await beforeExecute(toolCallId, ctx);
719
+ }
612
720
  }
613
721
  const ready = await getToolState(signal);
614
- if ("content" in ready)
722
+ if ("content" in ready) {
615
723
  return ready;
724
+ }
616
725
  const proxy = await import("./proxy-modes.js");
617
726
  const present = async (result) => {
618
727
  const guarded = await guardMcpOutput(result.content, resolveMcpOutputGuardOptions(ready.config.settings, ready.outputDirectory));
619
- return { content: guarded.content, details: { ...result.details, ...guardedMcpDetails(guarded) } };
728
+ return {
729
+ content: guarded.content,
730
+ details: { ...result.details, ...guardedMcpDetails(guarded) },
731
+ };
620
732
  };
621
733
  const beforeDispatch = beforeExecute
622
734
  ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation)
623
735
  : undefined;
624
- if (params.server && ["list", "search", "describe", "instructions"].includes(params.action) && !ready.toolMetadata.has(params.server)) {
736
+ if (params.server &&
737
+ ["list", "search", "describe", "instructions"].includes(params.action) &&
738
+ !ready.toolMetadata.has(params.server)) {
625
739
  const discovery = await proxy.executeConnect(ready, params.server, signal);
626
- if (discovery.details.error)
740
+ if (discovery.details.error) {
627
741
  return discovery;
742
+ }
628
743
  syncToolSurface(ctx);
629
744
  }
630
745
  switch (params.action) {
631
- case "ui-messages": return proxy.executeUiMessages(ready);
632
- case "auth-start": return proxy.executeAuthStart(ready, params.server, signal);
746
+ case "ui-messages":
747
+ return proxy.executeUiMessages(ready);
748
+ case "auth-start":
749
+ return proxy.executeAuthStart(ready, params.server, signal);
633
750
  case "auth-complete": {
634
751
  const input = params.args?.redirectUrl ?? params.args?.code ?? params.args?.input;
635
- if (input !== undefined && (typeof input !== "string" || input.trim().length === 0))
752
+ if (input !== undefined && (typeof input !== "string" || input.trim().length === 0)) {
636
753
  throw new Error("auth-complete requires a non-empty redirectUrl, code, or input");
754
+ }
637
755
  return proxy.executeAuthComplete(ready, params.server, input, signal);
638
756
  }
639
- case "call": return proxy.executeCall(ready, params.tool, params.args, params.server, getPiTools, signal, { toolCallId }, beforeDispatch);
757
+ case "call":
758
+ return proxy.executeCall(ready, params.tool, params.args, params.server, getPiTools, signal, { toolCallId }, beforeDispatch);
640
759
  case "connect": {
641
760
  const result = await proxy.executeConnect(ready, params.server, signal, params.limit, params.offset);
642
761
  syncToolSurface(ctx);
643
762
  return present(result);
644
763
  }
645
- case "describe": return present(proxy.executeDescribe(ready, params.tool, params.server));
646
- case "instructions": return present(proxy.executeInstructions(ready, params.server));
647
- case "search": return present(proxy.executeSearch(ready, params.query, params.server, params.includeSchemas, params.limit, params.offset));
648
- case "list": return present(proxy.executeList(ready, params.server, params.limit, params.offset));
649
- case "resources": return present(await (await import("./resource-tools.js")).executeResourceList(ready, params.server, params.limit, params.offset, signal));
650
- case "read-resource": return (await import("./resource-tools.js")).executeResourceRead(ready, params.server, params.uri, signal, { toolCallId }, beforeDispatch);
651
- case "read-result": return readMcpResult({ ref: params.ref, ...(params.path !== undefined ? { path: params.path } : {}), ...(params.fields ? { fields: params.fields } : {}), ...(params.offset !== undefined ? { offset: params.offset } : {}), ...(params.limit !== undefined ? { limit: params.limit } : {}) }, resolveMcpOutputGuardOptions(ready.config.settings, ready.outputDirectory));
652
- case "status": return present(proxy.executeStatus(ready));
764
+ case "describe":
765
+ return present(proxy.executeDescribe(ready, params.tool, params.server));
766
+ case "instructions":
767
+ return present(proxy.executeInstructions(ready, params.server));
768
+ case "search":
769
+ return present(proxy.executeSearch(ready, params.query, params.server, params.includeSchemas, params.limit, params.offset));
770
+ case "list":
771
+ return present(proxy.executeList(ready, params.server, params.limit, params.offset));
772
+ case "resources":
773
+ return present(await (await import("./resource-tools.js")).executeResourceList(ready, params.server, params.limit, params.offset, signal));
774
+ case "read-resource":
775
+ return (await import("./resource-tools.js")).executeResourceRead(ready, params.server, params.uri, signal, { toolCallId }, beforeDispatch);
776
+ case "read-result":
777
+ return readMcpResult({
778
+ ref: params.ref,
779
+ ...(params.path !== undefined ? { path: params.path } : {}),
780
+ ...(params.fields ? { fields: params.fields } : {}),
781
+ ...(params.offset !== undefined ? { offset: params.offset } : {}),
782
+ ...(params.limit !== undefined ? { limit: params.limit } : {}),
783
+ }, resolveMcpOutputGuardOptions(ready.config.settings, ready.outputDirectory));
784
+ case "status":
785
+ return present(proxy.executeStatus(ready));
653
786
  }
654
787
  },
655
788
  });
@@ -658,24 +791,30 @@ function installMcpAdapter(pi, options) {
658
791
  }
659
792
  function syncGateways(config, cache, directSpecs) {
660
793
  const policy = {};
661
- if (config.settings?.scriptMode !== false)
794
+ if (config.settings?.scriptMode !== false) {
662
795
  policy.script = {};
796
+ }
663
797
  const missingConfiguredDirectToolServers = getMissingConfiguredDirectToolServers(config, cache, envRaw === undefined || envRaw === "__none__" ? undefined : envDirectToolOverride);
664
- const hasPinnedResources = envRaw !== "__none__" && Object.entries(config.mcpServers).some(([server, definition]) => {
665
- if (definition.disabled === true)
666
- return false;
667
- const selection = directToolSelection(config, server, envDirectToolOverride);
668
- if (!selection)
669
- return false;
670
- const entry = cache?.servers[server];
671
- const metadata = state?.toolMetadata.get(server) ?? (entry && isServerCacheValid(entry, definition)
672
- ? reconstructToolMetadata(server, entry, config.settings?.toolPrefix ?? "server", definition) : []);
673
- return metadata.some(tool => tool.resourceUri && (selection === true || selection.includes(tool.originalName)));
674
- });
675
- const shouldRegisterProxyTool = config.settings?.disableProxyTool !== true
676
- || hasPinnedResources
677
- || directSpecs.length === 0
678
- || missingConfiguredDirectToolServers.length > 0;
798
+ const hasPinnedResources = envRaw !== "__none__" &&
799
+ Object.entries(config.mcpServers).some(([server, definition]) => {
800
+ if (definition.disabled === true) {
801
+ return false;
802
+ }
803
+ const selection = directToolSelection(config, server, envDirectToolOverride);
804
+ if (!selection) {
805
+ return false;
806
+ }
807
+ const entry = cache?.servers[server];
808
+ const metadata = state?.toolMetadata.get(server) ??
809
+ (entry && isServerCacheValid(entry, definition)
810
+ ? reconstructToolMetadata(server, entry, config.settings?.toolPrefix ?? "server", definition)
811
+ : []);
812
+ return metadata.some((tool) => tool.resourceUri && (selection === true || selection.includes(tool.originalName)));
813
+ });
814
+ const shouldRegisterProxyTool = config.settings?.disableProxyTool !== true ||
815
+ hasPinnedResources ||
816
+ directSpecs.length === 0 ||
817
+ missingConfiguredDirectToolServers.length > 0;
679
818
  if (shouldRegisterProxyTool) {
680
819
  const description = buildProxyDescription(config);
681
820
  if (!proxyToolRegistered || proxyToolDescription !== description) {
@@ -683,8 +822,9 @@ function installMcpAdapter(pi, options) {
683
822
  }
684
823
  policy.gateway = { eager: hasPinnedResources };
685
824
  }
686
- if (policy.script)
825
+ if (policy.script) {
687
826
  registerScriptTool();
827
+ }
688
828
  return policy;
689
829
  }
690
830
  // A transform needs the session context before any configured tool surface is registered.
@@ -694,8 +834,11 @@ function installMcpAdapter(pi, options) {
694
834
  }
695
835
  export function createMcpAdapter(options = {}) {
696
836
  const { defaultScriptTimeoutMs } = options;
697
- if (defaultScriptTimeoutMs !== undefined && defaultScriptTimeoutMs !== null
698
- && (!Number.isInteger(defaultScriptTimeoutMs) || defaultScriptTimeoutMs < 1 || defaultScriptTimeoutMs > 2_147_483_647)) {
837
+ if (defaultScriptTimeoutMs !== undefined &&
838
+ defaultScriptTimeoutMs !== null &&
839
+ (!Number.isInteger(defaultScriptTimeoutMs) ||
840
+ defaultScriptTimeoutMs < 1 ||
841
+ defaultScriptTimeoutMs > 2_147_483_647)) {
699
842
  throw new RangeError("defaultScriptTimeoutMs must be an integer from 1 to 2147483647, or null");
700
843
  }
701
844
  const factoryConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
@@ -703,7 +846,9 @@ export function createMcpAdapter(options = {}) {
703
846
  installMcpAdapter(pi, {
704
847
  ...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
705
848
  ...(defaultScriptTimeoutMs !== undefined ? { defaultScriptTimeoutMs } : {}),
706
- ...(options.outputDirectory !== undefined ? { outputDirectory: options.outputDirectory } : {}),
849
+ ...(options.outputDirectory !== undefined
850
+ ? { outputDirectory: options.outputDirectory }
851
+ : {}),
707
852
  ...(factoryConfig !== undefined ? { config: cloneMcpConfig(factoryConfig) } : {}),
708
853
  ...(options.beforeExecute ? { beforeExecute: options.beforeExecute } : {}),
709
854
  ...(options.transformConfig ? { transformConfig: options.transformConfig } : {}),