@fitchmultz/pi-mcp-adapter 8.0.6 → 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 (87) hide show
  1. package/CHANGELOG.md +128 -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 +26 -2
  87. package/skills/mcp-scripting/SKILL.md +6 -6
@@ -1,4 +1,4 @@
1
- import { clientOperations, drainClientCallbacks, drainClientOperations } from "./client-operations.js";
1
+ import { clientOperations, drainClientCallbacks, drainClientOperations, } from "./client-operations.js";
2
2
  import { isServerDisabled, isNonInteractiveOAuth, validateServerProtocolConfig, } from "./types.js";
3
3
  import { resolveNpxBinary } from "./npx-resolver.js";
4
4
  import { logger } from "./logger.js";
@@ -7,7 +7,7 @@ import { getAuthBaseDir } from "./mcp-auth.js";
7
7
  import { ADAPTER_VERSION, normalizeRequestTimeoutMs, resolveBearerToken, resolveCommandSecret, resolveCommandSecretsRecord, resolveConfigPath, resolveServerUrl, } from "./utils.js";
8
8
  import { abortable, throwIfAborted } from "./abort.js";
9
9
  import { combineAbortSignals } from "./runtime-owner.js";
10
- import { createMcpTraceWriter, isMcpTraceEnabled, McpTraceWriter, traceTransportKind, wrapTransportWithMcpTrace, } from "./mcp-trace.js";
10
+ import { createMcpTraceWriter, isMcpTraceEnabled, traceTransportKind, wrapTransportWithMcpTrace, } from "./mcp-trace.js";
11
11
  const MAX_CAPTURED_STDERR_BYTES = 8 * 1024;
12
12
  const MAX_CAPTURED_STDERR_LINES = 3;
13
13
  const abortCleanupPromises = new WeakMap();
@@ -18,9 +18,7 @@ function boundedStderrChunk(chunk) {
18
18
  }
19
19
  // Limit string conversion before encoding; Buffer.from(largeString) would
20
20
  // otherwise allocate the entire stderr event before applying the cap.
21
- const suffix = chunk.length > MAX_CAPTURED_STDERR_BYTES
22
- ? chunk.slice(-MAX_CAPTURED_STDERR_BYTES)
23
- : chunk;
21
+ const suffix = chunk.length > MAX_CAPTURED_STDERR_BYTES ? chunk.slice(-MAX_CAPTURED_STDERR_BYTES) : chunk;
24
22
  const bytes = Buffer.from(suffix, "utf8");
25
23
  return bytes.byteLength > MAX_CAPTURED_STDERR_BYTES
26
24
  ? Buffer.from(bytes.subarray(bytes.byteLength - MAX_CAPTURED_STDERR_BYTES))
@@ -28,10 +26,12 @@ function boundedStderrChunk(chunk) {
28
26
  }
29
27
  function appendStderrTail(tail, chunk) {
30
28
  const bytes = boundedStderrChunk(chunk);
31
- if (bytes.length === 0)
29
+ if (bytes.length === 0) {
32
30
  return tail;
33
- if (tail.length === 0)
31
+ }
32
+ if (tail.length === 0) {
34
33
  return bytes;
34
+ }
35
35
  const combined = Buffer.concat([tail, bytes]);
36
36
  return combined.length > MAX_CAPTURED_STDERR_BYTES
37
37
  ? Buffer.from(combined.subarray(combined.length - MAX_CAPTURED_STDERR_BYTES))
@@ -62,8 +62,9 @@ export class McpServerManager {
62
62
  traceWriter;
63
63
  stopped = false;
64
64
  releaseClientWhenSettled(client) {
65
- if (this.ownedClients.has(client))
65
+ if (this.ownedClients.has(client)) {
66
66
  void drainClientOperations(client).then(() => this.ownedClients.delete(client));
67
+ }
67
68
  }
68
69
  /** Own native send/auth tails even after a public request has been cancelled. */
69
70
  ownTransportSends(transport, client) {
@@ -123,23 +124,28 @@ export class McpServerManager {
123
124
  };
124
125
  }
125
126
  async connect(name, definition, signal) {
126
- if (isServerDisabled(definition))
127
+ if (isServerDisabled(definition)) {
127
128
  throw new Error(`MCP server "${name}" is disabled`);
128
- if (this.stopped)
129
+ }
130
+ if (this.stopped) {
129
131
  throw new Error("MCP server manager is closed");
132
+ }
130
133
  const ownedSignal = combineAbortSignals(this.runtimeSignal, signal);
131
134
  throwIfAborted(ownedSignal);
132
135
  const closing = this.closePromises.get(name);
133
- if (closing)
136
+ if (closing) {
134
137
  await abortable(closing, ownedSignal);
138
+ }
135
139
  throwIfAborted(ownedSignal);
136
- if (this.stopped)
140
+ if (this.stopped) {
137
141
  throw new Error("MCP server manager is closed");
142
+ }
138
143
  const wait = (promise, attempt) => {
139
144
  attempt.waiters++;
140
145
  return abortable(promise, ownedSignal).finally(() => {
141
- if (--attempt.waiters === 0 && ownedSignal?.aborted)
146
+ if (--attempt.waiters === 0 && ownedSignal?.aborted) {
142
147
  attempt.controller.abort(ownedSignal.reason);
148
+ }
143
149
  });
144
150
  };
145
151
  // Dedupe concurrent connection attempts.
@@ -153,8 +159,9 @@ export class McpServerManager {
153
159
  }
154
160
  catch (error) {
155
161
  throwIfAborted(ownedSignal);
156
- if (this.containsCleanupFailure(error))
162
+ if (this.containsCleanupFailure(error)) {
157
163
  throw error;
164
+ }
158
165
  }
159
166
  return this.connect(name, definition, signal);
160
167
  }
@@ -168,8 +175,11 @@ export class McpServerManager {
168
175
  const generation = this.closeGenerations.get(name) ?? 0;
169
176
  const attempt = { controller: new AbortController(), waiters: 0 };
170
177
  const attemptSignal = combineAbortSignals(this.runtimeSignal, attempt.controller.signal);
171
- const promise = this.createConnection(name, definition, attemptSignal, ownedSignal ? attemptSignal : undefined).then(async (connection) => {
172
- if (connection.status === "closed" || attempt.controller.signal.aborted || (this.closeGenerations.get(name) ?? 0) !== generation) {
178
+ const promise = this.createConnection(name, definition, attemptSignal, ownedSignal ? attemptSignal : undefined)
179
+ .then(async (connection) => {
180
+ if (connection.status === "closed" ||
181
+ attempt.controller.signal.aborted ||
182
+ (this.closeGenerations.get(name) ?? 0) !== generation) {
173
183
  await this.disposeConnection(connection);
174
184
  throwIfAborted(attemptSignal);
175
185
  throw new Error(`MCP connection for ${name} was closed while connecting`);
@@ -177,11 +187,14 @@ export class McpServerManager {
177
187
  connection.inFlight = this.connections.get(name)?.inFlight ?? 0;
178
188
  this.connections.set(name, connection);
179
189
  return connection;
180
- }).finally(() => {
181
- if (this.connectPromises.get(name) === promise)
190
+ })
191
+ .finally(() => {
192
+ if (this.connectPromises.get(name) === promise) {
182
193
  this.connectPromises.delete(name);
183
- if (this.connectAttempts.get(name) === attempt)
194
+ }
195
+ if (this.connectAttempts.get(name) === attempt) {
184
196
  this.connectAttempts.delete(name);
197
+ }
185
198
  });
186
199
  this.connectPromises.set(name, promise);
187
200
  this.connectAttempts.set(name, attempt);
@@ -197,10 +210,12 @@ export class McpServerManager {
197
210
  * connection, that fresh connection is returned untouched.
198
211
  */
199
212
  async reconnect(name, definition, staleConnection, signal) {
200
- if (isServerDisabled(definition))
213
+ if (isServerDisabled(definition)) {
201
214
  throw new Error(`MCP server "${name}" is disabled`);
202
- if (this.stopped)
215
+ }
216
+ if (this.stopped) {
203
217
  throw new Error("MCP server manager is closed");
218
+ }
204
219
  const ownedSignal = combineAbortSignals(this.runtimeSignal, signal);
205
220
  throwIfAborted(ownedSignal);
206
221
  const inFlight = this.reconnectPromises.get(name);
@@ -222,8 +237,9 @@ export class McpServerManager {
222
237
  // longer holds the connection we were asked to replace, someone else
223
238
  // already reconnected (or connected) first.
224
239
  if (current !== staleConnection) {
225
- if (!current)
240
+ if (!current) {
226
241
  throw new Error(`Server "${name}" is not connected`);
242
+ }
227
243
  return current;
228
244
  }
229
245
  this.retire(name, staleConnection);
@@ -231,28 +247,34 @@ export class McpServerManager {
231
247
  }
232
248
  /** Retire only the expected current client; accepted native work drains before close. */
233
249
  retire(name, connection) {
234
- if (this.connections.get(name) !== connection)
250
+ if (this.connections.get(name) !== connection) {
235
251
  return;
252
+ }
236
253
  connection.status = "closed";
237
254
  let retired = this.retiredConnections.get(name);
238
- if (!retired)
239
- this.retiredConnections.set(name, retired = new Set());
240
- if (retired.has(connection))
255
+ if (!retired) {
256
+ this.retiredConnections.set(name, (retired = new Set()));
257
+ }
258
+ if (retired.has(connection)) {
241
259
  return;
260
+ }
242
261
  retired.add(connection);
243
262
  void (async () => {
244
263
  for (;;) {
245
264
  const pending = [...(clientOperations.get(connection.client) ?? [])]
246
- .filter(([, kind]) => kind !== "subscription").map(([operation]) => operation);
247
- if (!pending.length)
265
+ .filter(([, kind]) => kind !== "subscription")
266
+ .map(([operation]) => operation);
267
+ if (!pending.length) {
248
268
  break;
269
+ }
249
270
  await Promise.allSettled(pending);
250
271
  }
251
272
  await this.disposeConnection(connection);
252
273
  retired.delete(connection);
253
- if (retired.size === 0 && this.retiredConnections.get(name) === retired)
274
+ if (retired.size === 0 && this.retiredConnections.get(name) === retired) {
254
275
  this.retiredConnections.delete(name);
255
- })().catch(error => {
276
+ }
277
+ })().catch((error) => {
256
278
  // Keep failed retirement owned so explicit shutdown also reports it.
257
279
  logger.debug(`MCP retired connection cleanup failed for ${name}: ${String(error)}`);
258
280
  });
@@ -261,7 +283,7 @@ export class McpServerManager {
261
283
  throwIfAborted(signal);
262
284
  validateServerProtocolConfig(definition);
263
285
  const runtime = await import("./connection-runtime.js");
264
- const { StdioClientTransport, SdkHttpError, UnixSocketClientTransport, trackToolTransport, getOAuthRequest, isOAuthChallenge } = runtime;
286
+ const { StdioClientTransport, SdkHttpError, UnixSocketClientTransport, trackToolTransport, getOAuthRequest, isOAuthChallenge, } = runtime;
265
287
  throwIfAborted(signal);
266
288
  let client = this.createClient(name, definition, runtime);
267
289
  const tracingEnabled = isMcpTraceEnabled(definition, this.traceSettings);
@@ -269,17 +291,19 @@ export class McpServerManager {
269
291
  ? (this.traceWriter ??= createMcpTraceWriter(this.defaultCwd, this.traceSettings ?? {}))
270
292
  : undefined;
271
293
  const traceObserver = traceWriter
272
- ? { record: event => traceWriter.write(event) }
294
+ ? { record: (event) => traceWriter.write(event) }
273
295
  : undefined;
274
296
  let transport;
275
- let implicitOAuth = Boolean(definition.url && supportsOAuth(definition)
276
- && getOAuthRequest(name, resolveServerUrl(definition), getAuthBaseDir(this.authStorageOptions), this.oauthRuntime));
297
+ let implicitOAuth = Boolean(definition.url &&
298
+ supportsOAuth(definition) &&
299
+ getOAuthRequest(name, resolveServerUrl(definition), getAuthBaseDir(this.authStorageOptions), this.oauthRuntime));
277
300
  let oauthProvider = false;
278
301
  let authError;
279
- const onAuthChallenge = (error) => { authError = error; };
302
+ const onAuthChallenge = (error) => {
303
+ authError = error;
304
+ };
280
305
  let stderrTail = Buffer.alloc(0);
281
- const configuredTransports = [definition.command, definition.url, definition.socket]
282
- .filter(value => typeof value === "string" && value.length > 0);
306
+ const configuredTransports = [definition.command, definition.url, definition.socket].filter((value) => typeof value === "string" && value.length > 0);
283
307
  if (configuredTransports.length !== 1) {
284
308
  throw new Error(`Server ${name} must configure exactly one of command, url, or socket`);
285
309
  }
@@ -319,8 +343,9 @@ export class McpServerManager {
319
343
  else {
320
344
  transport = new UnixSocketClientTransport(resolveConfigPath(definition.socket));
321
345
  }
322
- if (!definition.url)
346
+ if (!definition.url) {
323
347
  trackToolTransport(transport);
348
+ }
324
349
  if (traceObserver) {
325
350
  const traceTransportKindValue = traceTransportKind(definition, transport);
326
351
  transport = wrapTransportWithMcpTrace(transport, name, traceTransportKindValue, traceObserver);
@@ -337,16 +362,21 @@ export class McpServerManager {
337
362
  }
338
363
  catch (error) {
339
364
  // Only handshake failures establish that the HTTP endpoint is unsupported.
340
- if (!definition.url || signal?.aborted || legacySse || !(error instanceof SdkHttpError)
341
- || ![404, 405, 406, 415].includes(error.status))
365
+ if (!definition.url ||
366
+ signal?.aborted ||
367
+ legacySse ||
368
+ !(error instanceof SdkHttpError) ||
369
+ ![404, 405, 406, 415].includes(error.status)) {
342
370
  throw error;
371
+ }
343
372
  legacySse = true;
344
373
  await client.close();
345
374
  this.releaseClientWhenSettled(client);
346
375
  client = this.createClient(name, { ...definition, protocolVersion: "legacy" }, runtime);
347
376
  ({ transport, oauthProvider } = this.createHttpTransport(runtime, definition, name, client, legacySse, implicitOAuth, onAuthChallenge));
348
- if (traceObserver)
377
+ if (traceObserver) {
349
378
  transport = wrapTransportWithMcpTrace(transport, name, traceTransportKind(definition, transport), traceObserver);
379
+ }
350
380
  continue;
351
381
  }
352
382
  this.attachAdapterNotificationHandlers(name, client, runtime.serverStreamResultPatchNotificationSchema);
@@ -376,11 +406,13 @@ export class McpServerManager {
376
406
  ];
377
407
  const [toolResult] = await Promise.allSettled(catalogs);
378
408
  throwIfAborted(signal);
379
- if (toolResult.status === "rejected" && !isOAuthChallenge(toolResult.reason))
409
+ if (toolResult.status === "rejected" && !isOAuthChallenge(toolResult.reason)) {
380
410
  throw toolResult.reason;
411
+ }
381
412
  // Optional catalogs catch their errors; the native observer still sees auth challenges.
382
- if (authError)
413
+ if (authError) {
383
414
  throw authError;
415
+ }
384
416
  const [tools, resources, promptResult] = await Promise.all(catalogs);
385
417
  connection.tools = tools;
386
418
  connection.resources = resources;
@@ -389,16 +421,22 @@ export class McpServerManager {
389
421
  return connection;
390
422
  }
391
423
  catch (error) {
392
- if (!definition.url || signal?.aborted || implicitOAuth || definition.auth !== undefined
393
- || !supportsOAuth(definition) || !isOAuthChallenge(error))
424
+ if (!definition.url ||
425
+ signal?.aborted ||
426
+ implicitOAuth ||
427
+ definition.auth !== undefined ||
428
+ !supportsOAuth(definition) ||
429
+ !isOAuthChallenge(error)) {
394
430
  throw error;
431
+ }
395
432
  implicitOAuth = true;
396
433
  await client.close();
397
434
  this.releaseClientWhenSettled(client);
398
435
  client = this.createClient(name, legacySse ? { ...definition, protocolVersion: "legacy" } : definition, runtime);
399
436
  ({ transport, oauthProvider } = this.createHttpTransport(runtime, definition, name, client, legacySse, implicitOAuth, onAuthChallenge));
400
- if (traceObserver)
437
+ if (traceObserver) {
401
438
  transport = wrapTransportWithMcpTrace(transport, name, traceTransportKind(definition, transport), traceObserver);
439
+ }
402
440
  }
403
441
  }
404
442
  }
@@ -410,12 +448,10 @@ export class McpServerManager {
410
448
  const abortCleanupFailed = error instanceof AggregateError && error.message === "MCP connection abort cleanup failed";
411
449
  const cleanupResults = abortCleanupFailed
412
450
  ? []
413
- : await Promise.allSettled([
414
- abortCleanup ?? Promise.resolve().then(() => client.close()),
415
- ]);
451
+ : await Promise.allSettled([abortCleanup ?? Promise.resolve().then(() => client.close())]);
416
452
  await drainClientCallbacks(client);
417
453
  this.releaseClientWhenSettled(client);
418
- const cleanupFailures = cleanupResults.flatMap(result => result.status === "rejected" ? [result.reason] : []);
454
+ const cleanupFailures = cleanupResults.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
419
455
  let reportedError = error;
420
456
  if (cleanupFailures.length > 0) {
421
457
  reportedError = new AggregateError([error, ...cleanupFailures], "MCP connection setup failed");
@@ -440,7 +476,10 @@ export class McpServerManager {
440
476
  }
441
477
  if (stderrTail.length > 0) {
442
478
  const stderrText = stderrTail.toString("utf8").trim();
443
- const lines = stderrText.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
479
+ const lines = stderrText
480
+ .split(/\r?\n/)
481
+ .map((line) => line.trim())
482
+ .filter(Boolean);
444
483
  if (lines.length > 0) {
445
484
  const baseMessage = reportedError instanceof Error ? reportedError.message : String(reportedError);
446
485
  const detail = lines.slice(-MAX_CAPTURED_STDERR_LINES).join(" — ");
@@ -481,8 +520,14 @@ export class McpServerManager {
481
520
  buildClientCapabilities(definition) {
482
521
  return {
483
522
  ...(supportsOAuth(definition) && isNonInteractiveOAuth(definition.oauth)
484
- ? { extensions: { [definition.oauth && definition.oauth.crossAppAccess
485
- ? "io.modelcontextprotocol/enterprise-managed-authorization" : "io.modelcontextprotocol/oauth-client-credentials"]: {} } } : {}),
523
+ ? {
524
+ extensions: {
525
+ [definition.oauth && definition.oauth.crossAppAccess
526
+ ? "io.modelcontextprotocol/enterprise-managed-authorization"
527
+ : "io.modelcontextprotocol/oauth-client-credentials"]: {},
528
+ },
529
+ }
530
+ : {}),
486
531
  ...(this.samplingConfig ? { sampling: {} } : {}),
487
532
  ...(this.elicitationConfig
488
533
  ? {
@@ -499,7 +544,9 @@ export class McpServerManager {
499
544
  const capabilities = this.buildClientCapabilities(definition);
500
545
  let client;
501
546
  client = new ManagedClient({ name: `pi-mcp-${serverName}`, version: ADAPTER_VERSION }, {
502
- versionNegotiation: { mode: definition.protocolVersion ?? (definition.url ? "auto" : "legacy") },
547
+ versionNegotiation: {
548
+ mode: definition.protocolVersion ?? (definition.url ? "auto" : "legacy"),
549
+ },
503
550
  // ponytail: reject catalogs over 1024 pages; raise this native cap if a real catalog needs more.
504
551
  listMaxPages: 1024,
505
552
  ...(Object.keys(capabilities).length > 0 ? { capabilities } : {}),
@@ -524,23 +571,26 @@ export class McpServerManager {
524
571
  client.serverName = serverName;
525
572
  this.ownedClients.add(client);
526
573
  if (this.samplingConfig) {
527
- registerSamplingHandler(client, { ...this.samplingConfig, serverName }, run => client.ownCallback("sampling callback", run));
574
+ registerSamplingHandler(client, { ...this.samplingConfig, serverName }, (run) => client.ownCallback("sampling callback", run));
528
575
  }
529
576
  if (this.elicitationConfig) {
530
577
  registerElicitationHandler(client, {
531
578
  ...this.elicitationConfig,
532
579
  serverName,
533
- onUrlAccepted: elicitationId => this.rememberUrlElicitation(serverName, elicitationId),
534
- }, run => client.ownCallback("elicitation callback", run));
580
+ onUrlAccepted: (elicitationId) => this.rememberUrlElicitation(serverName, elicitationId),
581
+ }, (run) => client.ownCallback("elicitation callback", run));
535
582
  if (this.elicitationConfig.allowUrl) {
536
- client.setNotificationHandler("notifications/elicitation/complete", notification => {
537
- if (this.runtimeSignal?.aborted)
583
+ client.setNotificationHandler("notifications/elicitation/complete", (notification) => {
584
+ if (this.runtimeSignal?.aborted) {
538
585
  return;
586
+ }
539
587
  const accepted = this.acceptedUrlElicitations.get(serverName);
540
- if (!accepted?.delete(notification.params.elicitationId))
588
+ if (!accepted?.delete(notification.params.elicitationId)) {
541
589
  return;
542
- if (accepted.size === 0)
590
+ }
591
+ if (accepted.size === 0) {
543
592
  this.acceptedUrlElicitations.delete(serverName);
593
+ }
544
594
  this.elicitationConfig?.ui.notify(`MCP browser interaction for ${serverName} completed. You can retry the tool now.`, "info");
545
595
  });
546
596
  }
@@ -552,11 +602,13 @@ export class McpServerManager {
552
602
  logger.debug(`MCP: tools/list_changed refresh failed for ${serverName}: ${error.message}`);
553
603
  return;
554
604
  }
555
- if (!tools)
605
+ if (!tools) {
556
606
  return;
607
+ }
557
608
  const connection = this.connections.get(serverName);
558
- if (!connection || connection.client !== client || connection.status !== "connected")
609
+ if (!connection || connection.client !== client || connection.status !== "connected") {
559
610
  return;
611
+ }
560
612
  connection.tools = tools;
561
613
  this.metadataListChangedListener?.(serverName, "tools-list-changed");
562
614
  }
@@ -565,11 +617,13 @@ export class McpServerManager {
565
617
  logger.debug(`MCP: prompts/list_changed refresh failed for ${serverName}: ${error.message}`);
566
618
  return;
567
619
  }
568
- if (!prompts)
620
+ if (!prompts) {
569
621
  return;
622
+ }
570
623
  const connection = this.connections.get(serverName);
571
- if (!connection || connection.client !== client || connection.status !== "connected")
624
+ if (!connection || connection.client !== client || connection.status !== "connected") {
572
625
  return;
626
+ }
573
627
  connection.prompts = prompts;
574
628
  connection.promptDiscoveryFailed = false;
575
629
  this.metadataListChangedListener?.(serverName, "prompts-list-changed");
@@ -579,35 +633,41 @@ export class McpServerManager {
579
633
  logger.debug(`MCP: resources/list_changed refresh failed for ${serverName}: ${error.message}`);
580
634
  return;
581
635
  }
582
- if (!resources)
636
+ if (!resources) {
583
637
  return;
638
+ }
584
639
  const connection = this.connections.get(serverName);
585
- if (!connection || connection.client !== client || connection.status !== "connected")
640
+ if (!connection || connection.client !== client || connection.status !== "connected") {
586
641
  return;
642
+ }
587
643
  connection.resources = resources;
588
644
  this.metadataListChangedListener?.(serverName, "resources-list-changed");
589
645
  }
590
646
  async handleUrlElicitationRequired(serverName, error) {
591
- if (this.runtimeSignal?.aborted || !this.elicitationConfig?.allowUrl)
647
+ if (this.runtimeSignal?.aborted || !this.elicitationConfig?.allowUrl) {
592
648
  return "cancel";
649
+ }
593
650
  const { handleUrlElicitation } = await import("./elicitation-handler.js");
594
651
  // The import yields; recheck cancellation and URL support before prompting.
595
- if (this.runtimeSignal?.aborted || !this.elicitationConfig?.allowUrl)
652
+ if (this.runtimeSignal?.aborted || !this.elicitationConfig?.allowUrl) {
596
653
  return "cancel";
654
+ }
597
655
  for (const params of error.elicitations) {
598
656
  const result = await handleUrlElicitation({
599
657
  ...this.elicitationConfig,
600
658
  serverName,
601
- onUrlAccepted: elicitationId => this.rememberUrlElicitation(serverName, elicitationId),
659
+ onUrlAccepted: (elicitationId) => this.rememberUrlElicitation(serverName, elicitationId),
602
660
  }, params);
603
- if (result.action !== "accept")
661
+ if (result.action !== "accept") {
604
662
  return result.action;
663
+ }
605
664
  }
606
665
  return "accept";
607
666
  }
608
667
  rememberUrlElicitation(serverName, elicitationId) {
609
- if (this.runtimeSignal?.aborted)
668
+ if (this.runtimeSignal?.aborted) {
610
669
  return;
670
+ }
611
671
  let accepted = this.acceptedUrlElicitations.get(serverName);
612
672
  if (!accepted) {
613
673
  accepted = new Set();
@@ -622,13 +682,14 @@ export class McpServerManager {
622
682
  const storageBase = getAuthBaseDir(this.authStorageOptions);
623
683
  const runtime = this.oauthRuntime;
624
684
  const generation = this.closeGenerations.get(serverName) ?? 0;
625
- const active = () => !this.stopped && !this.runtimeSignal?.aborted && !runtime?.signal.aborted
626
- && (this.closeGenerations.get(serverName) ?? 0) === generation;
685
+ const active = () => !this.stopped &&
686
+ !this.runtimeSignal?.aborted &&
687
+ !runtime?.signal.aborted &&
688
+ (this.closeGenerations.get(serverName) ?? 0) === generation;
627
689
  let issuer;
628
690
  // Resolve secret commands only for this connection attempt, without mutating config.
629
- const hasCommandHeader = Object.values(definition.headers ?? {})
630
- .some(value => value.startsWith("!") && !value.startsWith("!!"));
631
- const headers = resolveCommandSecretsRecord(definition.headers, key => `MCP server "${serverName}" HTTP header "${key}"`) ?? {};
691
+ const hasCommandHeader = Object.values(definition.headers ?? {}).some((value) => value.startsWith("!") && !value.startsWith("!!"));
692
+ const headers = resolveCommandSecretsRecord(definition.headers, (key) => `MCP server "${serverName}" HTTP header "${key}"`) ?? {};
632
693
  // For bearer auth, add the token to headers BEFORE creating requestInit
633
694
  const commandBearer = definition.bearerToken?.startsWith("!") && !definition.bearerToken.startsWith("!!")
634
695
  ? definition.bearerToken
@@ -637,8 +698,9 @@ export class McpServerManager {
637
698
  const token = commandBearer
638
699
  ? resolveCommandSecret(commandBearer, `MCP server "${serverName}" HTTP bearer token`)
639
700
  : resolveBearerToken(definition);
640
- if (token)
701
+ if (token) {
641
702
  headers["Authorization"] = `Bearer ${token}`;
703
+ }
642
704
  }
643
705
  if (hasCommandHeader || commandBearer) {
644
706
  try {
@@ -654,37 +716,45 @@ export class McpServerManager {
654
716
  onRedirect: async (_authUrl) => {
655
717
  // URL is captured by startAuth, no need to log
656
718
  },
657
- onDiscoveryState: state => {
719
+ onDiscoveryState: (state) => {
658
720
  issuer = state.authorizationServerMetadata?.issuer ?? state.authorizationServerUrl;
659
- if (active())
721
+ if (active()) {
660
722
  bindOAuthRequestIssuer(serverName, serverUrl, storageBase, issuer, runtime);
723
+ }
661
724
  },
662
725
  }, this.authStorageOptions, this.oauthRuntime?.signal);
663
726
  const authProvider = supportsOAuth(definition) && (definition.auth === "oauth" || implicitOAuth)
664
- ? createAuthProvider() : undefined;
727
+ ? createAuthProvider()
728
+ : undefined;
665
729
  client.oauthProvider = authProvider;
666
730
  let transportClosed = false;
667
731
  // The SDK launches legacy GET bootstrap/reconnect outside send(). Fence every
668
732
  // provider entry and own its ACTUAL promise (including signing/storage after
669
733
  // cancellation). Keep the OAuthClientProvider shape: native 401/403 behavior,
670
734
  // issuer validation and noninteractive grants remain SDK-owned.
671
- const ownedProvider = authProvider && new Proxy(authProvider, {
672
- get: (provider, key) => {
673
- const value = Reflect.get(provider, key, provider);
674
- if (typeof value !== "function")
675
- return value;
676
- return (...args) => client.ownNative(async () => {
677
- provider.lifetimeSignal.throwIfAborted();
678
- return Reflect.apply(value, provider, args);
679
- });
680
- },
681
- });
735
+ const ownedProvider = authProvider &&
736
+ new Proxy(authProvider, {
737
+ get: (provider, key) => {
738
+ const value = Reflect.get(provider, key, provider);
739
+ if (typeof value !== "function") {
740
+ return value;
741
+ }
742
+ return (...args) => client.ownNative(async () => {
743
+ provider.lifetimeSignal.throwIfAborted();
744
+ return Reflect.apply(value, provider, args);
745
+ });
746
+ },
747
+ });
682
748
  const fetch = (input, init) => client.ownNative(async () => {
683
- if (transportClosed)
749
+ if (transportClosed) {
684
750
  throw new Error("MCP transport is closed");
685
- const response = await trackToolTransportFailure(input, authProvider ? {
686
- ...init, signal: combineAbortSignals(init?.signal ?? undefined, init?.signal ? authProvider.lifetimeSignal : authProvider.signal),
687
- } : init);
751
+ }
752
+ const response = await trackToolTransportFailure(input, authProvider
753
+ ? {
754
+ ...init,
755
+ signal: combineAbortSignals(init?.signal ?? undefined, init?.signal ? authProvider.lifetimeSignal : authProvider.signal),
756
+ }
757
+ : init);
688
758
  // Fetch resolves at headers. Native auth/discovery subsequently consumes
689
759
  // text/JSON bodies; keep those real reads owned too. Never count the idle
690
760
  // SSE body as a perpetual request: its messages use the ingress fence.
@@ -700,43 +770,55 @@ export class McpServerManager {
700
770
  // provider calls. Noninteractive transports retain OAuthClientProvider so
701
771
  // the SDK's 403 scope-union/retry policy is unchanged; all their asynchronous
702
772
  // effects are provider calls (including JWT signing) and fetch/body reads.
703
- const transportAuth = ownedProvider
704
- && !isNonInteractiveOAuth(definition.oauth) && !legacySse ? {
705
- // Preserve transport configuration introspection; auth() uses this same provider.
706
- get redirectUrl() { return ownedProvider.redirectUrl; },
707
- get clientMetadata() { return ownedProvider.clientMetadata; },
708
- token: () => client.ownNative(async () => (await ownedProvider.tokens())?.access_token),
709
- onUnauthorized: context => client.ownNative(async () => {
710
- const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(context.response);
711
- const result = await auth(ownedProvider, {
712
- serverUrl: context.serverUrl, fetchFn: context.fetchFn,
713
- ...(resourceMetadataUrl !== undefined ? { resourceMetadataUrl } : {}),
714
- ...(scope !== undefined ? { scope } : {}),
715
- skipIssuerMetadataValidation: definition.oauth !== false && definition.oauth?.skipIssuerMetadataValidation === true,
716
- });
717
- if (result !== "AUTHORIZED")
718
- throw new UnauthorizedError();
719
- }),
720
- } : ownedProvider;
773
+ const transportAuth = ownedProvider && !isNonInteractiveOAuth(definition.oauth) && !legacySse
774
+ ? {
775
+ // Preserve transport configuration introspection; auth() uses this same provider.
776
+ get redirectUrl() {
777
+ return ownedProvider.redirectUrl;
778
+ },
779
+ get clientMetadata() {
780
+ return ownedProvider.clientMetadata;
781
+ },
782
+ token: () => client.ownNative(async () => (await ownedProvider.tokens())?.access_token),
783
+ onUnauthorized: (context) => client.ownNative(async () => {
784
+ const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(context.response);
785
+ const result = await auth(ownedProvider, {
786
+ serverUrl: context.serverUrl,
787
+ fetchFn: context.fetchFn,
788
+ ...(resourceMetadataUrl !== undefined ? { resourceMetadataUrl } : {}),
789
+ ...(scope !== undefined ? { scope } : {}),
790
+ skipIssuerMetadataValidation: definition.oauth !== false &&
791
+ definition.oauth?.skipIssuerMetadataValidation === true,
792
+ });
793
+ if (result !== "AUTHORIZED") {
794
+ throw new UnauthorizedError();
795
+ }
796
+ }),
797
+ }
798
+ : ownedProvider;
721
799
  const options = {
722
800
  ...(requestInit !== undefined ? { requestInit } : {}),
723
801
  ...(transportAuth !== undefined ? { authProvider: transportAuth } : {}),
724
802
  skipIssuerMetadataValidation: definition.oauth !== false && definition.oauth?.skipIssuerMetadataValidation === true,
725
803
  fetch,
726
804
  };
727
- const transport = legacySse ? new SSEClientTransport(url, options) : new StreamableHTTPClientTransport(url, {
728
- ...options,
729
- reconnectionScheduler: (reconnect, delay) => {
730
- const timer = setTimeout(() => {
731
- if (transportClosed)
732
- return;
733
- reconnect();
734
- }, delay);
735
- return () => clearTimeout(timer);
736
- },
737
- ...(supportsOAuth(definition) && !isNonInteractiveOAuth(definition.oauth)
738
- ? { onInsufficientScope: "throw" } : {}),
739
- });
805
+ const transport = legacySse
806
+ ? new SSEClientTransport(url, options)
807
+ : new StreamableHTTPClientTransport(url, {
808
+ ...options,
809
+ reconnectionScheduler: (reconnect, delay) => {
810
+ const timer = setTimeout(() => {
811
+ if (transportClosed) {
812
+ return;
813
+ }
814
+ reconnect();
815
+ }, delay);
816
+ return () => clearTimeout(timer);
817
+ },
818
+ ...(supportsOAuth(definition) && !isNonInteractiveOAuth(definition.oauth)
819
+ ? { onInsufficientScope: "throw" }
820
+ : {}),
821
+ });
740
822
  if (authProvider) {
741
823
  const send = transport.send.bind(transport);
742
824
  transport.send = async (message, options) => authProvider.runWithSignal(options?.requestSignal, () => send(message, options));
@@ -750,16 +832,21 @@ export class McpServerManager {
750
832
  };
751
833
  if (!legacySse && supportsOAuth(definition)) {
752
834
  const previous = transport.onerror;
753
- transport.onerror = error => {
835
+ transport.onerror = (error) => {
754
836
  previous?.(error);
755
- if (!active() || isServerDisabled(definition) || !supportsOAuth(definition) || !isOAuthChallenge(error))
837
+ if (!active() ||
838
+ isServerDisabled(definition) ||
839
+ !supportsOAuth(definition) ||
840
+ !isOAuthChallenge(error)) {
756
841
  return;
842
+ }
757
843
  recordOAuthChallenge(serverName, serverUrl, storageBase, error, runtime);
758
844
  // A known issuer was validated by the native discovery hook, never inferred from the challenge.
759
845
  if (issuer) {
760
846
  const request = getOAuthRequest(serverName, serverUrl, storageBase, runtime);
761
- if (request && !request.issuer)
847
+ if (request && !request.issuer) {
762
848
  request.issuer = issuer;
849
+ }
763
850
  }
764
851
  onAuthChallenge?.(error);
765
852
  };
@@ -772,15 +859,20 @@ export class McpServerManager {
772
859
  }
773
860
  async fetchAllPrompts(client, requestOptions) {
774
861
  const capabilities = client.getServerCapabilities?.();
775
- if (!capabilities?.prompts)
862
+ if (!capabilities?.prompts) {
776
863
  return { prompts: [], failed: false };
864
+ }
777
865
  try {
778
- const { prompts } = await client.listPrompts(undefined, { ...requestOptions, cacheMode: "refresh" });
866
+ const { prompts } = await client.listPrompts(undefined, {
867
+ ...requestOptions,
868
+ cacheMode: "refresh",
869
+ });
779
870
  return { prompts, failed: false };
780
871
  }
781
872
  catch (error) {
782
- if (requestOptions?.signal?.aborted)
873
+ if (requestOptions?.signal?.aborted) {
783
874
  throwIfAborted(requestOptions.signal);
875
+ }
784
876
  const message = error instanceof Error ? error.message : String(error);
785
877
  logger.debug(`MCP: prompts/list failed: ${message}`);
786
878
  return { prompts: [], failed: true };
@@ -788,10 +880,12 @@ export class McpServerManager {
788
880
  }
789
881
  async fetchAllResources(client, requestOptions) {
790
882
  const capabilities = client.getServerCapabilities?.();
791
- if (!capabilities?.resources)
883
+ if (!capabilities?.resources) {
792
884
  return [];
885
+ }
793
886
  try {
794
- return (await client.listResources(undefined, { ...requestOptions, cacheMode: "refresh" })).resources;
887
+ return (await client.listResources(undefined, { ...requestOptions, cacheMode: "refresh" }))
888
+ .resources;
795
889
  }
796
890
  catch {
797
891
  if (requestOptions?.signal?.aborted) {
@@ -802,7 +896,7 @@ export class McpServerManager {
802
896
  }
803
897
  }
804
898
  attachAdapterNotificationHandlers(serverName, client, schema) {
805
- client.setNotificationHandler(schema.shape.method.value, { params: schema.shape.params }, params => {
899
+ client.setNotificationHandler(schema.shape.method.value, { params: schema.shape.params }, (params) => {
806
900
  this.uiStreamListeners.get(params.streamToken)?.(serverName, params);
807
901
  });
808
902
  }
@@ -847,106 +941,125 @@ export class McpServerManager {
847
941
  }
848
942
  async close(name) {
849
943
  this.closeGenerations.set(name, (this.closeGenerations.get(name) ?? 0) + 1);
850
- this.connectAttempts.get(name)?.controller.abort(new Error(`MCP connection ${name} was closed`));
944
+ this.connectAttempts
945
+ .get(name)
946
+ ?.controller.abort(new Error(`MCP connection ${name} was closed`));
851
947
  const pendingClose = this.closePromises.get(name);
852
- if (pendingClose)
948
+ if (pendingClose) {
853
949
  return pendingClose;
950
+ }
854
951
  const owned = new Set(this.retiredConnections.get(name));
855
952
  const connection = this.connections.get(name);
856
- if (connection)
953
+ if (connection) {
857
954
  owned.add(connection);
955
+ }
858
956
  // Keep failed cleanup/callback tails owned even after removal from the active connection map.
859
- if (owned.size)
957
+ if (owned.size) {
860
958
  this.retiredConnections.set(name, owned);
959
+ }
861
960
  // Remove the placeholder before awaiting cleanup; stale callers cannot resurrect it.
862
961
  this.connections.delete(name);
863
962
  this.acceptedUrlElicitations.delete(name);
864
- for (const item of owned)
963
+ for (const item of owned) {
865
964
  item.status = "closed";
965
+ }
866
966
  const pendingConnect = this.connectPromises.get(name);
867
967
  const closing = (async () => {
868
968
  const results = await Promise.allSettled([
869
- ...[...owned].map(item => this.disposeConnection(item)),
870
- pendingConnect?.catch(error => {
871
- if (this.containsCleanupFailure(error))
969
+ ...[...owned].map((item) => this.disposeConnection(item)),
970
+ pendingConnect?.catch((error) => {
971
+ if (this.containsCleanupFailure(error)) {
872
972
  throw error;
973
+ }
873
974
  }),
874
975
  ]);
875
- const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : []);
876
- if (failures.length > 0)
976
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
977
+ if (failures.length > 0) {
877
978
  throw new AggregateError(failures, "MCP connection cleanup failed");
979
+ }
878
980
  this.retiredConnections.delete(name);
879
981
  })().finally(() => {
880
- if (this.closePromises.get(name) === closing)
982
+ if (this.closePromises.get(name) === closing) {
881
983
  this.closePromises.delete(name);
984
+ }
882
985
  });
883
986
  this.closePromises.set(name, closing);
884
987
  return closing;
885
988
  }
886
989
  disposeConnection(connection) {
887
990
  const existing = this.disposePromises.get(connection);
888
- if (existing)
991
+ if (existing) {
889
992
  return existing;
993
+ }
890
994
  const closing = (async () => {
891
995
  const results = await Promise.allSettled([
892
996
  // The SDK client owns its transport.
893
997
  Promise.resolve().then(() => connection.client.close()),
894
998
  this.traceWriter?.flush() ?? Promise.resolve(),
895
999
  ]);
896
- const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : []);
1000
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
897
1001
  // Keep ordinary per-server cancellation prompt, but retain native send/auth tails for
898
1002
  // readiness and final shutdown. Inbound handler promises still drain before disposal.
899
1003
  await drainClientCallbacks(connection.client);
900
- if (failures.length > 0)
1004
+ if (failures.length > 0) {
901
1005
  throw new AggregateError(failures, "MCP connection cleanup failed");
1006
+ }
902
1007
  this.releaseClientWhenSettled(connection.client);
903
1008
  })();
904
1009
  this.disposePromises.set(connection, closing);
905
1010
  void closing.catch(() => {
906
1011
  // Native close retains its transport until onclose. Retry only on an
907
1012
  // explicit later close, retaining the connection/operations veto meanwhile.
908
- if (connection.client.transport === connection.transport && this.disposePromises.get(connection) === closing)
1013
+ if (connection.client.transport === connection.transport &&
1014
+ this.disposePromises.get(connection) === closing) {
909
1015
  this.disposePromises.delete(connection);
1016
+ }
910
1017
  });
911
1018
  return closing;
912
1019
  }
913
1020
  async closeAll() {
914
1021
  this.stopped = true;
915
1022
  const names = new Set([
916
- ...this.connections.keys(), ...this.retiredConnections.keys(),
917
- ...this.connectPromises.keys(), ...this.closePromises.keys(),
1023
+ ...this.connections.keys(),
1024
+ ...this.retiredConnections.keys(),
1025
+ ...this.connectPromises.keys(),
1026
+ ...this.closePromises.keys(),
918
1027
  ]);
919
- const results = await Promise.allSettled([...names].map(name => this.close(name)));
920
- const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : []);
1028
+ const results = await Promise.allSettled([...names].map((name) => this.close(name)));
1029
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
921
1030
  this.uiStreamListeners.clear();
922
1031
  this.acceptedUrlElicitations.clear();
923
1032
  this.samplingConfig = undefined;
924
1033
  this.elicitationConfig = undefined;
925
- await Promise.all([...this.ownedClients].map(client => drainClientOperations(client)));
1034
+ await Promise.all([...this.ownedClients].map((client) => drainClientOperations(client)));
926
1035
  try {
927
1036
  await this.traceWriter?.flush();
928
1037
  }
929
1038
  catch (error) {
930
1039
  failures.push(error);
931
1040
  }
932
- if (failures.length > 0)
1041
+ if (failures.length > 0) {
933
1042
  throw new AggregateError(failures, "MCP manager cleanup failed");
1043
+ }
934
1044
  }
935
1045
  containsCleanupFailure(error) {
936
1046
  const pending = [error];
937
1047
  const seen = new Set();
938
1048
  while (pending.length > 0) {
939
1049
  const current = pending.pop();
940
- if (!(current instanceof Error) || seen.has(current))
1050
+ if (!(current instanceof Error) || seen.has(current)) {
941
1051
  continue;
1052
+ }
942
1053
  seen.add(current);
943
1054
  if (current instanceof AggregateError) {
944
- if (/cleanup failed|setup failed/.test(current.message))
1055
+ if (/cleanup failed|setup failed/.test(current.message)) {
945
1056
  return true;
1057
+ }
946
1058
  pending.push(...current.errors);
947
1059
  }
948
- if (current.cause !== undefined)
1060
+ if (current.cause !== undefined) {
949
1061
  pending.push(current.cause);
1062
+ }
950
1063
  }
951
1064
  return false;
952
1065
  }
@@ -976,15 +1089,18 @@ export class McpServerManager {
976
1089
  }
977
1090
  isIdle(name, timeoutMs) {
978
1091
  const connection = this.connections.get(name);
979
- if (!connection || connection.status !== "connected")
1092
+ if (!connection || connection.status !== "connected") {
980
1093
  return false;
981
- if (connection.inFlight > 0)
1094
+ }
1095
+ if (connection.inFlight > 0) {
982
1096
  return false;
1097
+ }
983
1098
  // Background subscriptions keep their existing idle policy, but accepted requests and
984
1099
  // callbacks (including cancellation tails) must not be closed as idle while unfinished.
985
- if ([...(clientOperations.get(connection.client)?.values() ?? [])].some(kind => kind !== "subscription"))
1100
+ if ([...(clientOperations.get(connection.client)?.values() ?? [])].some((kind) => kind !== "subscription")) {
986
1101
  return false;
987
- return (Date.now() - connection.lastUsedAt) > timeoutMs;
1102
+ }
1103
+ return Date.now() - connection.lastUsedAt > timeoutMs;
988
1104
  }
989
1105
  }
990
1106
  /**
@@ -993,9 +1109,10 @@ export class McpServerManager {
993
1109
  function resolveEnv(env, serverName) {
994
1110
  const resolved = {};
995
1111
  for (const [key, value] of Object.entries(process.env)) {
996
- if (value !== undefined)
1112
+ if (value !== undefined) {
997
1113
  resolved[key] = value;
1114
+ }
998
1115
  }
999
- const overrides = resolveCommandSecretsRecord(env, key => `MCP server "${serverName}" stdio env "${key}"`);
1116
+ const overrides = resolveCommandSecretsRecord(env, (key) => `MCP server "${serverName}" stdio env "${key}"`);
1000
1117
  return overrides ? { ...resolved, ...overrides } : resolved;
1001
1118
  }