@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
package/dist/init.d.ts CHANGED
@@ -3,8 +3,7 @@ import type { McpExtensionState } from "./state.ts";
3
3
  import { type McpAdapterOptions, type McpConfig } from "./types.ts";
4
4
  import { type McpOAuthRuntime } from "./mcp-oauth-runtime.ts";
5
5
  import { type McpRuntimeOwner } from "./runtime-owner.ts";
6
- /** Most servers one multi-server pass connects at once. */
7
- export declare const MAX_CONCURRENT_SERVER_CONNECTS = 10;
6
+ export { MAX_CONCURRENT_SERVER_CONNECTS } from "./utils.ts";
8
7
  export declare function clearFailure(state: McpExtensionState, serverName: string): void;
9
8
  export declare function recordFailure(state: McpExtensionState, serverName: string, message: string): void;
10
9
  type McpInitializationOptions = McpAdapterOptions & {
@@ -24,4 +23,3 @@ export declare function updateStatusBar(state: McpExtensionState): void;
24
23
  export declare function getFailureAgeSeconds(state: McpExtensionState, serverName: string): number | null;
25
24
  export declare function getFailureMessage(state: McpExtensionState, serverName: string): string | null;
26
25
  export declare function lazyConnect(state: McpExtensionState, serverName: string, signal?: AbortSignal): Promise<boolean>;
27
- export {};
package/dist/init.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isServerDisabled } from "./types.js";
1
+ import { isServerDisabled, } from "./types.js";
2
2
  import { cloneMcpConfig, isPathInsideProject, loadMcpConfig } from "./config.js";
3
3
  import { ConsentManager } from "./consent-manager.js";
4
4
  import { McpLifecycleManager } from "./lifecycle.js";
@@ -6,17 +6,16 @@ import { computeServerHash, getMetadataCachePath, getMissingConfiguredDirectTool
6
6
  import { McpServerManager } from "./server-manager.js";
7
7
  import { buildToolMetadata, totalToolCount } from "./tool-metadata.js";
8
8
  import { UiResourceHandler } from "./ui-resource-handler.js";
9
- import { formatMcpStatus, openUrl, parallelLimit, sanitizeTerminalText } from "./utils.js";
9
+ import { formatMcpStatus, MAX_CONCURRENT_SERVER_CONNECTS, openUrl, parallelLimit, sanitizeTerminalText, } from "./utils.js";
10
10
  import { logger } from "./logger.js";
11
11
  import { throwIfAborted } from "./abort.js";
12
12
  import { getAuthStorageOptions } from "./mcp-auth.js";
13
- import { createOAuthRuntime, hasPendingAuth, shutdownOAuth } from "./mcp-oauth-runtime.js";
13
+ import { createOAuthRuntime, hasPendingAuth, shutdownOAuth, } from "./mcp-oauth-runtime.js";
14
14
  import { combineAbortSignals, createMcpRuntimeOwner, createOwnedUi, isAbortError, } from "./runtime-owner.js";
15
15
  import { publishMcpStatusSnapshot } from "./mcp-status.js";
16
16
  const FAILURE_BACKOFF_MS = 60 * 1000;
17
17
  const MAX_FAILURE_MESSAGE_CHARS = 8 * 1024;
18
- /** Most servers one multi-server pass connects at once. */
19
- export const MAX_CONCURRENT_SERVER_CONNECTS = 10;
18
+ export { MAX_CONCURRENT_SERVER_CONNECTS } from "./utils.js";
20
19
  const failureExpiryTimers = new WeakMap();
21
20
  function getFailureExpiryTimers(state) {
22
21
  let timers = failureExpiryTimers.get(state);
@@ -31,8 +30,9 @@ export function clearFailure(state, serverName) {
31
30
  state.failureMessages?.delete(serverName);
32
31
  const timers = failureExpiryTimers.get(state);
33
32
  const timer = timers?.get(serverName);
34
- if (timer)
33
+ if (timer) {
35
34
  clearTimeout(timer);
35
+ }
36
36
  timers?.delete(serverName);
37
37
  }
38
38
  export function recordFailure(state, serverName, message) {
@@ -72,8 +72,7 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
72
72
  ? cloneMcpConfig(options.resolvedConfig)
73
73
  : loadMcpConfig(options.configPath ?? pi.getFlag("mcp-config"), cwd);
74
74
  const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd);
75
- const metadataCacheEnabled = (ctx.isProjectTrusted?.() ?? true)
76
- || !isPathInsideProject(getMetadataCachePath(), cwd);
75
+ const metadataCacheEnabled = (ctx.isProjectTrusted?.() ?? true) || !isPathInsideProject(getMetadataCachePath(), cwd);
77
76
  const ownsOAuthRuntime = options.oauthRuntime === undefined;
78
77
  const oauthRuntime = options.oauthRuntime ?? createOAuthRuntime(owner.signal);
79
78
  const manager = new McpServerManager(cwd);
@@ -88,10 +87,8 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
88
87
  autoApprove: samplingAutoApprove,
89
88
  ...(ui !== undefined ? { ui } : {}),
90
89
  modelRegistry,
91
- getCurrentModel: () => owner.isActive() ? ctx.model : undefined,
92
- getSignal: () => owner.isActive()
93
- ? combineAbortSignals(owner.signal, ctx.signal)
94
- : owner.signal,
90
+ getCurrentModel: () => (owner.isActive() ? ctx.model : undefined),
91
+ getSignal: () => owner.isActive() ? combineAbortSignals(owner.signal, ctx.signal) : owner.signal,
95
92
  });
96
93
  }
97
94
  const elicitationEnabled = config.settings?.elicitation !== false && hasUI;
@@ -142,18 +139,21 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
142
139
  },
143
140
  ...(ui !== undefined ? { ui } : {}),
144
141
  sendMessage: (message, options) => {
145
- if (!owner.isActive())
142
+ if (!owner.isActive()) {
146
143
  return;
144
+ }
147
145
  pi.sendMessage(message, options);
148
146
  },
149
147
  ...(options.statusEvents !== undefined ? { statusEvents: options.statusEvents } : {}),
150
148
  ...(options.onToolCall ? { onToolCall: options.onToolCall } : {}),
151
149
  };
152
- if (ownsOAuthRuntime)
150
+ if (ownsOAuthRuntime) {
153
151
  owner.addCleanup(() => shutdownOAuth(oauthRuntime));
152
+ }
154
153
  manager.setMetadataListChangedListener?.((serverName, reason) => {
155
- if (!owner.isActive())
154
+ if (!owner.isActive()) {
156
155
  return;
156
+ }
157
157
  updateServerMetadata(state, serverName);
158
158
  updateMetadataCache(state, serverName, { preserveEmptyResources: false });
159
159
  notifyToolMetadataUpdated(state, serverName, reason);
@@ -214,33 +214,42 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
214
214
  try {
215
215
  const connection = await manager.connect(name, definition, runtimeSignal);
216
216
  if (connection.status === "needs-auth") {
217
- return { name, definition, connection: null, error: `OAuth authentication required. Run /mcp-auth ${name}.` };
217
+ return {
218
+ name,
219
+ definition,
220
+ connection: null,
221
+ error: `OAuth authentication required. Run /mcp-auth ${name}.`,
222
+ };
218
223
  }
219
224
  return { name, definition, connection, error: null };
220
225
  }
221
226
  catch (error) {
222
227
  if (isAbortError(error, runtimeSignal)) {
223
- if (owner.signal.aborted)
228
+ if (owner.signal.aborted) {
224
229
  throw error;
230
+ }
225
231
  return { name, definition, connection: null, error: null };
226
232
  }
227
233
  const message = error instanceof Error ? error.message : String(error);
228
234
  return { name, definition, connection: null, error: message };
229
235
  }
230
236
  });
231
- if (initialSignal?.aborted)
237
+ if (initialSignal?.aborted) {
232
238
  return state;
239
+ }
233
240
  owner.throwIfInactive();
234
241
  const startupCache = { entries: new Map() };
235
242
  try {
236
243
  for (const { name, definition, connection, error } of results) {
237
244
  owner.throwIfInactive();
238
245
  if (error || !connection) {
239
- if (initialSignal?.aborted)
246
+ if (initialSignal?.aborted) {
240
247
  continue;
248
+ }
241
249
  saveMetadataCacheUpdates(state, startupCache);
242
- if (error)
250
+ if (error) {
243
251
  recordFailure(state, name, error);
252
+ }
244
253
  const displayError = sanitizeTerminalText(error ?? "Unknown connection failure");
245
254
  if (ui) {
246
255
  ui.notify(`MCP: Failed to connect to ${name}: ${displayError}`, "error");
@@ -273,8 +282,8 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
273
282
  finally {
274
283
  saveMetadataCacheUpdates(state, startupCache);
275
284
  }
276
- const connectedCount = results.filter(r => r.connection).length;
277
- const failedCount = results.filter(r => r.error).length;
285
+ const connectedCount = results.filter((r) => r.connection).length;
286
+ const failedCount = results.filter((r) => r.error).length;
278
287
  if (ui && connectedCount > 0) {
279
288
  const totalTools = totalToolCount(state);
280
289
  const msg = failedCount > 0
@@ -285,14 +294,18 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
285
294
  const envDirect = process.env.MCP_DIRECT_TOOLS;
286
295
  if (envDirect !== "__none__") {
287
296
  const currentCache = loadMetadataCache(state.metadataCacheEnabled);
288
- const envDirectToolOverride = envDirect?.split(",").map(selector => selector.trim()).filter(Boolean);
297
+ const envDirectToolOverride = envDirect
298
+ ?.split(",")
299
+ .map((selector) => selector.trim())
300
+ .filter(Boolean);
289
301
  const missingCacheServers = getMissingConfiguredDirectToolServers(config, currentCache, envDirectToolOverride);
290
302
  if (missingCacheServers.length > 0) {
291
- const bootstrapResults = await parallelLimit(missingCacheServers.filter(name => !results.some(r => r.name === name && r.connection)), MAX_CONCURRENT_SERVER_CONNECTS, async (name) => {
303
+ const bootstrapResults = await parallelLimit(missingCacheServers.filter((name) => !results.some((r) => r.name === name && r.connection)), MAX_CONCURRENT_SERVER_CONNECTS, async (name) => {
292
304
  try {
293
305
  const definition = config.mcpServers[name];
294
- if (!definition)
306
+ if (!definition) {
295
307
  throw new Error(`MCP server "${name}" is not configured`);
308
+ }
296
309
  const connection = await manager.connect(name, definition, runtimeSignal);
297
310
  if (connection.status === "needs-auth") {
298
311
  return { name, ok: false };
@@ -306,8 +319,9 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
306
319
  }
307
320
  catch (error) {
308
321
  if (isAbortError(error, runtimeSignal)) {
309
- if (owner.signal.aborted)
322
+ if (owner.signal.aborted) {
310
323
  throw error;
324
+ }
311
325
  return { name, ok: false };
312
326
  }
313
327
  const message = error instanceof Error ? error.message : String(error);
@@ -316,7 +330,7 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
316
330
  return { name, ok: false };
317
331
  }
318
332
  });
319
- const bootstrapped = bootstrapResults.filter(r => r.ok).map(r => r.name);
333
+ const bootstrapped = bootstrapResults.filter((r) => r.ok).map((r) => r.name);
320
334
  owner.throwIfInactive();
321
335
  if (bootstrapped.length > 0 && ui) {
322
336
  ui.notify(`MCP: tools discovered for ${bootstrapped.join(", ")}`, "info");
@@ -324,8 +338,9 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
324
338
  }
325
339
  }
326
340
  lifecycle.setReconnectCallback((serverName) => {
327
- if (!owner.isActive())
341
+ if (!owner.isActive()) {
328
342
  return;
343
+ }
329
344
  updateServerMetadata(state, serverName);
330
345
  updateMetadataCache(state, serverName);
331
346
  notifyToolMetadataUpdated(state, serverName, "lifecycle-reconnect");
@@ -333,15 +348,17 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
333
348
  updateStatusBar(state);
334
349
  });
335
350
  lifecycle.setReconnectFailureCallback((serverName, error) => {
336
- if (!owner.isActive())
351
+ if (!owner.isActive()) {
337
352
  return;
353
+ }
338
354
  const message = error instanceof Error ? error.message : String(error);
339
355
  recordFailure(state, serverName, message);
340
356
  updateStatusBar(state);
341
357
  });
342
358
  lifecycle.setIdleShutdownCallback((serverName) => {
343
- if (!owner.isActive())
359
+ if (!owner.isActive()) {
344
360
  return;
361
+ }
345
362
  const idleMinutes = getEffectiveIdleTimeoutMinutes(state, serverName);
346
363
  logger.debug(`${serverName} shut down (idle ${idleMinutes}m)`);
347
364
  updateStatusBar(state);
@@ -356,19 +373,22 @@ export async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), op
356
373
  }
357
374
  export function markKeepAliveAfterConnect(state, serverName) {
358
375
  const definition = state.config.mcpServers[serverName];
359
- if (!definition || isServerDisabled(definition))
376
+ if (!definition || isServerDisabled(definition)) {
360
377
  return;
378
+ }
361
379
  if ((definition.lifecycle ?? "lazy") === "lazy-keep-alive") {
362
380
  state.lifecycle.markKeepAlive(serverName, definition);
363
381
  }
364
382
  }
365
383
  export function updateServerMetadata(state, serverName) {
366
384
  const connection = state.manager.getConnection(serverName);
367
- if (!connection || connection.status !== "connected")
385
+ if (!connection || connection.status !== "connected") {
368
386
  return;
387
+ }
369
388
  const definition = state.config.mcpServers[serverName];
370
- if (!definition)
389
+ if (!definition) {
371
390
  return;
391
+ }
372
392
  if (isServerDisabled(definition)) {
373
393
  state.toolMetadata.delete(serverName);
374
394
  state.resourceCounts?.delete(serverName);
@@ -394,19 +414,24 @@ export function updateServerMetadata(state, serverName) {
394
414
  }
395
415
  function collectMetadataCacheUpdate(state, serverName, updates, options = {}) {
396
416
  const connection = state.manager.getConnection(serverName);
397
- if (!connection || connection.status !== "connected")
417
+ if (!connection || connection.status !== "connected") {
398
418
  return;
419
+ }
399
420
  const definition = state.config.mcpServers[serverName];
400
- if (!definition || isServerDisabled(definition))
421
+ if (!definition || isServerDisabled(definition)) {
401
422
  return;
423
+ }
402
424
  const configHash = computeServerHash(definition);
403
- if (updates.existing === undefined)
425
+ if (updates.existing === undefined) {
404
426
  updates.existing = loadMetadataCache(state.metadataCacheEnabled);
427
+ }
405
428
  const existingEntry = updates.existing?.servers?.[serverName];
406
429
  const tools = serializeTools(connection.tools);
407
430
  let resources = definition.exposeResources === false ? [] : serializeResources(connection.resources);
408
431
  const prompts = connection.promptDiscoveryFailed
409
- ? existingEntry?.configHash === configHash ? existingEntry.prompts : undefined
432
+ ? existingEntry?.configHash === configHash
433
+ ? existingEntry.prompts
434
+ : undefined
410
435
  : serializePrompts(connection.prompts ?? []);
411
436
  if (definition.exposeResources !== false &&
412
437
  resources.length === 0 &&
@@ -426,8 +451,9 @@ function collectMetadataCacheUpdate(state, serverName, updates, options = {}) {
426
451
  updates.entries.set(serverName, entry);
427
452
  }
428
453
  function saveMetadataCacheUpdates(state, updates) {
429
- if (updates.entries.size === 0)
454
+ if (updates.entries.size === 0) {
430
455
  return;
456
+ }
431
457
  const servers = Object.fromEntries(updates.entries);
432
458
  updates.entries.clear();
433
459
  delete updates.existing;
@@ -469,8 +495,9 @@ export function flushMetadataCache(state) {
469
495
  export function updateStatusBar(state) {
470
496
  publishMcpStatusSnapshot(state);
471
497
  const ui = state.ui;
472
- if (!ui)
498
+ if (!ui) {
473
499
  return;
500
+ }
474
501
  const entries = Object.entries(state.config.mcpServers);
475
502
  const disabledCount = entries.filter(([, definition]) => isServerDisabled(definition)).length;
476
503
  const enabledCount = entries.length - disabledCount;
@@ -480,7 +507,7 @@ export function updateStatusBar(state) {
480
507
  }
481
508
  const connectedCount = [...state.manager.getAllConnections()].filter(([name, connection]) => {
482
509
  const definition = state.config.mcpServers[name];
483
- return connection.status === "connected" && definition !== undefined && !isServerDisabled(definition);
510
+ return (connection.status === "connected" && definition !== undefined && !isServerDisabled(definition));
484
511
  }).length;
485
512
  const footerStatus = state.config.settings?.mcpFooterStatus ?? "compact";
486
513
  if (footerStatus === "off") {
@@ -491,10 +518,12 @@ export function updateStatusBar(state) {
491
518
  ? `MCP ${connectedCount}/${enabledCount}`
492
519
  : `${enabledCount} ${enabledCount === 1 ? "server" : "servers"} enabled`;
493
520
  if (footerStatus === "full") {
494
- if (connectedCount > 0)
521
+ if (connectedCount > 0) {
495
522
  status += ` (${connectedCount} connected)`;
496
- if (disabledCount > 0)
523
+ }
524
+ if (disabledCount > 0) {
497
525
  status += ` (${disabledCount} disabled)`;
526
+ }
498
527
  }
499
528
  const formattedStatus = footerStatus === "compact" ? status : formatMcpStatus(state.config, status);
500
529
  if (formattedStatus === undefined) {
@@ -505,16 +534,19 @@ export function updateStatusBar(state) {
505
534
  }
506
535
  export function getFailureAgeSeconds(state, serverName) {
507
536
  const failedAt = state.failureTracker.get(serverName);
508
- if (!failedAt)
537
+ if (!failedAt) {
509
538
  return null;
539
+ }
510
540
  const ageMs = Date.now() - failedAt;
511
- if (ageMs > FAILURE_BACKOFF_MS)
541
+ if (ageMs > FAILURE_BACKOFF_MS) {
512
542
  return null;
543
+ }
513
544
  return Math.round(ageMs / 1000);
514
545
  }
515
546
  export function getFailureMessage(state, serverName) {
516
- if (getFailureAgeSeconds(state, serverName) === null)
547
+ if (getFailureAgeSeconds(state, serverName) === null) {
517
548
  return null;
549
+ }
518
550
  return state.failureMessages?.get(serverName) ?? null;
519
551
  }
520
552
  export async function lazyConnect(state, serverName, signal) {
@@ -530,11 +562,13 @@ export async function lazyConnect(state, serverName, signal) {
530
562
  return true;
531
563
  }
532
564
  const failedAgo = getFailureAgeSeconds(state, serverName);
533
- if (failedAgo !== null)
565
+ if (failedAgo !== null) {
534
566
  return false;
567
+ }
535
568
  const definition = state.config.mcpServers[serverName];
536
- if (!definition || isServerDisabled(definition))
569
+ if (!definition || isServerDisabled(definition)) {
537
570
  return false;
571
+ }
538
572
  try {
539
573
  if (state.ui) {
540
574
  const status = formatMcpStatus(state.config, `connecting to ${serverName}...`);
@@ -566,12 +600,18 @@ export async function lazyConnect(state, serverName, signal) {
566
600
  function getEffectiveIdleTimeoutMinutes(state, serverName) {
567
601
  const definition = state.config.mcpServers[serverName];
568
602
  if (!definition) {
569
- return typeof state.config.settings?.idleTimeout === "number" ? state.config.settings.idleTimeout : 10;
603
+ return typeof state.config.settings?.idleTimeout === "number"
604
+ ? state.config.settings.idleTimeout
605
+ : 10;
570
606
  }
571
- if (typeof definition.idleTimeout === "number")
607
+ if (typeof definition.idleTimeout === "number") {
572
608
  return definition.idleTimeout;
609
+ }
573
610
  const mode = definition.lifecycle ?? "lazy";
574
- if (mode === "eager" || mode === "lazy-keep-alive")
611
+ if (mode === "eager" || mode === "lazy-keep-alive") {
575
612
  return 0;
576
- return typeof state.config.settings?.idleTimeout === "number" ? state.config.settings.idleTimeout : 10;
613
+ }
614
+ return typeof state.config.settings?.idleTimeout === "number"
615
+ ? state.config.settings.idleTimeout
616
+ : 10;
577
617
  }
@@ -5,7 +5,7 @@ interface FileMigration {
5
5
  status: "absent" | "existing" | "would-copy" | "copied";
6
6
  }
7
7
  /** Run explicitly before starting v5. Never overwrite a destination or remove a source. */
8
- export declare function migrateLegacyState({ cwd, dryRun }?: {
8
+ export declare function migrateLegacyState({ cwd, dryRun, }?: {
9
9
  cwd?: string;
10
10
  dryRun?: boolean;
11
11
  }): {
@@ -4,40 +4,45 @@ import { getAdapterPath, getAgentPath } from "./agent-dir.js";
4
4
  import { getProjectPiConfigPath, loadMcpConfig, resolveConfiguredOAuthDir } from "./config.js";
5
5
  import { migrateLegacyAuthEntry } from "./mcp-auth.js";
6
6
  /** Run explicitly before starting v5. Never overwrite a destination or remove a source. */
7
- export function migrateLegacyState({ cwd = process.cwd(), dryRun = false } = {}) {
8
- const paths = ["mcp.json", "mcp-cache.json", "mcp-onboarding.json", "mcp-npx-cache.json"]
9
- .map((name) => ({ source: getAgentPath(name), destination: getAdapterPath(name) }));
7
+ export function migrateLegacyState({ cwd = process.cwd(), dryRun = false, } = {}) {
8
+ const paths = ["mcp.json", "mcp-cache.json", "mcp-onboarding.json", "mcp-npx-cache.json"].map((name) => ({ source: getAgentPath(name), destination: getAdapterPath(name) }));
10
9
  paths.push({ source: resolve(cwd, ".pi", "mcp.json"), destination: getProjectPiConfigPath(cwd) });
11
10
  const files = paths.map(({ source, destination }) => {
12
- if (!existsSync(source))
11
+ if (!existsSync(source)) {
13
12
  return { source, destination, status: "absent" };
14
- if (existsSync(destination))
13
+ }
14
+ if (existsSync(destination)) {
15
15
  return { source, destination, status: "existing" };
16
- if (dryRun)
16
+ }
17
+ if (dryRun) {
17
18
  return { source, destination, status: "would-copy" };
19
+ }
18
20
  const contents = readFileSync(source);
19
21
  mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
20
22
  try {
21
23
  writeFileSync(destination, contents, { flag: "wx", mode: 0o600 });
22
24
  }
23
25
  catch (error) {
24
- if (error.code === "EEXIST")
26
+ if (error.code === "EEXIST") {
25
27
  return { source, destination, status: "existing" };
28
+ }
26
29
  throw error;
27
30
  }
28
- if (!readFileSync(destination).equals(contents))
31
+ if (!readFileSync(destination).equals(contents)) {
29
32
  throw new Error(`Failed to verify migrated file: ${destination}`);
33
+ }
30
34
  return { source, destination, status: "copied" };
31
35
  });
32
36
  const credentials = [];
33
37
  if (!dryRun) {
34
38
  const config = loadMcpConfig(undefined, cwd);
35
- const legacyDirectory = process.env.MCP_OAUTH_DIR?.trim()
36
- || resolveConfiguredOAuthDir(config.settings?.oauthDir, cwd)
37
- || getAgentPath("mcp-oauth");
39
+ const legacyDirectory = process.env.MCP_OAUTH_DIR?.trim() ||
40
+ resolveConfiguredOAuthDir(config.settings?.oauthDir, cwd) ||
41
+ getAgentPath("mcp-oauth");
38
42
  for (const [server, definition] of Object.entries(config.mcpServers)) {
39
- if (typeof definition.url !== "string")
43
+ if (typeof definition.url !== "string") {
40
44
  continue;
45
+ }
41
46
  credentials.push({ server, status: migrateLegacyAuthEntry(server, legacyDirectory) });
42
47
  }
43
48
  }
@@ -4,9 +4,9 @@ import { hasPendingAuth } from "./mcp-oauth-runtime.ts";
4
4
  export type ReconnectCallback = (serverName: string) => void;
5
5
  export type ReconnectFailureCallback = (serverName: string, error: unknown) => void;
6
6
  export declare class McpLifecycleManager {
7
- private keepAliveServers;
8
- private allServers;
9
- private serverSettings;
7
+ private readonly keepAliveServers;
8
+ private readonly allServers;
9
+ private readonly serverSettings;
10
10
  private globalIdleTimeout;
11
11
  private healthCheckInterval;
12
12
  private onReconnect;
package/dist/lifecycle.js CHANGED
@@ -29,16 +29,19 @@ export class McpLifecycleManager {
29
29
  this.onReconnectFailure = callback;
30
30
  }
31
31
  markKeepAlive(name, definition) {
32
- if (isServerDisabled(definition))
32
+ if (isServerDisabled(definition)) {
33
33
  return;
34
+ }
34
35
  this.keepAliveServers.set(name, definition);
35
36
  }
36
37
  registerServer(name, definition, settings) {
37
- if (isServerDisabled(definition))
38
+ if (isServerDisabled(definition)) {
38
39
  return;
40
+ }
39
41
  this.allServers.set(name, definition);
40
- if (settings?.idleTimeout !== undefined)
42
+ if (settings?.idleTimeout !== undefined) {
41
43
  this.serverSettings.set(name, settings);
44
+ }
42
45
  }
43
46
  setGlobalIdleTimeout(minutes) {
44
47
  this.globalIdleTimeout = minutes * 60 * 1000;
@@ -49,8 +52,9 @@ export class McpLifecycleManager {
49
52
  startHealthChecks(signalOrInterval, maybeIntervalMs = 30000) {
50
53
  const signal = typeof signalOrInterval === "number" ? undefined : signalOrInterval;
51
54
  const intervalMs = typeof signalOrInterval === "number" ? signalOrInterval : maybeIntervalMs;
52
- if (this.healthCheckInterval)
55
+ if (this.healthCheckInterval) {
53
56
  clearInterval(this.healthCheckInterval);
57
+ }
54
58
  this.removeHealthAbortListener?.();
55
59
  this.stopped = false;
56
60
  this.healthSignal = signal;
@@ -61,33 +65,38 @@ export class McpLifecycleManager {
61
65
  }
62
66
  const stop = () => {
63
67
  this.stopped = true;
64
- if (this.healthCheckInterval)
68
+ if (this.healthCheckInterval) {
65
69
  clearInterval(this.healthCheckInterval);
70
+ }
66
71
  this.healthCheckInterval = undefined;
67
72
  };
68
73
  signal?.addEventListener("abort", stop, { once: true });
69
74
  this.removeHealthAbortListener = () => signal?.removeEventListener("abort", stop);
70
75
  this.healthCheckInterval = setInterval(() => {
71
- if (this.stopped || signal?.aborted || this.activeHealthCheck)
76
+ if (this.stopped || signal?.aborted || this.activeHealthCheck) {
72
77
  return;
78
+ }
73
79
  const check = this.checkConnections(signal)
74
- .catch(error => {
80
+ .catch((error) => {
75
81
  console.error(`MCP: Health check failed: ${formatTerminalError(error)}`);
76
82
  })
77
83
  .finally(() => {
78
- if (this.activeHealthCheck === check)
84
+ if (this.activeHealthCheck === check) {
79
85
  this.activeHealthCheck = undefined;
86
+ }
80
87
  });
81
88
  this.activeHealthCheck = check;
82
89
  }, intervalMs);
83
90
  this.healthCheckInterval.unref();
84
91
  }
85
92
  async checkConnections(signal) {
86
- if (this.stopped || signal?.aborted)
93
+ if (this.stopped || signal?.aborted) {
87
94
  return;
95
+ }
88
96
  for (const [name, definition] of this.keepAliveServers) {
89
- if (isServerDisabled(definition))
97
+ if (isServerDisabled(definition)) {
90
98
  continue;
99
+ }
91
100
  const connection = this.manager.getConnection(name);
92
101
  if (!connection || connection.status !== "connected") {
93
102
  if (this.hasPendingAuthForServer(name)) {
@@ -96,14 +105,16 @@ export class McpLifecycleManager {
96
105
  }
97
106
  try {
98
107
  await this.manager.connect(name, definition, signal);
99
- if (this.stopped || signal?.aborted)
108
+ if (this.stopped || signal?.aborted) {
100
109
  return;
110
+ }
101
111
  logger.debug(`Reconnected to ${name}`);
102
112
  this.onReconnect?.(name);
103
113
  }
104
114
  catch (error) {
105
- if (this.stopped || signal?.aborted)
115
+ if (this.stopped || signal?.aborted) {
106
116
  return;
117
+ }
107
118
  this.onReconnectFailure?.(name, error);
108
119
  const message = error instanceof Error ? error.message : String(error);
109
120
  console.error(`MCP: Failed to reconnect to ${name}: ${sanitizeTerminalText(message)}`);
@@ -111,33 +122,38 @@ export class McpLifecycleManager {
111
122
  }
112
123
  }
113
124
  for (const [name] of this.allServers) {
114
- if (this.keepAliveServers.has(name))
125
+ if (this.keepAliveServers.has(name)) {
115
126
  continue;
127
+ }
116
128
  const timeout = this.getIdleTimeout(name);
117
129
  if (timeout > 0 && this.manager.isIdle(name, timeout)) {
118
130
  await this.manager.close(name);
119
- if (this.stopped || signal?.aborted)
131
+ if (this.stopped || signal?.aborted) {
120
132
  return;
133
+ }
121
134
  this.onIdleShutdown?.(name);
122
135
  }
123
136
  }
124
137
  }
125
138
  getIdleTimeout(name) {
126
139
  const perServer = this.serverSettings.get(name)?.idleTimeout;
127
- if (perServer !== undefined)
140
+ if (perServer !== undefined) {
128
141
  return perServer * 60 * 1000;
142
+ }
129
143
  return this.globalIdleTimeout;
130
144
  }
131
145
  async gracefulShutdown() {
132
- if (this.shutdownPromise)
146
+ if (this.shutdownPromise) {
133
147
  return this.shutdownPromise;
148
+ }
134
149
  this.shutdownPromise = this.shutdownOnce();
135
150
  return this.shutdownPromise;
136
151
  }
137
152
  async shutdownOnce() {
138
153
  this.stopped = true;
139
- if (this.healthCheckInterval)
154
+ if (this.healthCheckInterval) {
140
155
  clearInterval(this.healthCheckInterval);
156
+ }
141
157
  this.healthCheckInterval = undefined;
142
158
  this.removeHealthAbortListener?.();
143
159
  this.removeHealthAbortListener = undefined;
package/dist/logger.d.ts CHANGED
@@ -25,8 +25,8 @@ declare class Logger {
25
25
  child(context: LogContext): ChildLogger;
26
26
  }
27
27
  declare class ChildLogger {
28
- private parent;
29
- private context;
28
+ private readonly parent;
29
+ private readonly context;
30
30
  constructor(parent: Logger, context: LogContext);
31
31
  debug(message: string, context?: LogContext): void;
32
32
  info(message: string, context?: LogContext): void;
package/dist/logger.js CHANGED
@@ -23,8 +23,9 @@ class Logger {
23
23
  return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.minLevel];
24
24
  }
25
25
  emit(level, message, context, error) {
26
- if (!this.shouldLog(level))
26
+ if (!this.shouldLog(level)) {
27
27
  return;
28
+ }
28
29
  const prefix = LEVEL_PREFIX[level];
29
30
  const contextStr = formatContext(context);
30
31
  const fullMessage = contextStr ? `${prefix} ${message} ${contextStr}` : `${prefix} ${message}`;
@@ -84,8 +85,9 @@ class ChildLogger {
84
85
  }
85
86
  }
86
87
  function formatContext(context) {
87
- if (!context || Object.keys(context).length === 0)
88
+ if (!context || Object.keys(context).length === 0) {
88
89
  return "";
90
+ }
89
91
  const parts = [];
90
92
  for (const [key, value] of Object.entries(context)) {
91
93
  if (value !== undefined && value !== null) {