@fitchmultz/pi-mcp-adapter 8.0.6 → 8.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -8
- package/OAUTH.md +13 -16
- package/README.md +8 -8
- package/cli.js +67 -28
- package/dist/abort.js +18 -9
- package/dist/client-operations.js +4 -2
- package/dist/commands.js +77 -43
- package/dist/config.js +194 -105
- package/dist/connection-runtime.d.ts +2 -2
- package/dist/connection-runtime.js +29 -18
- package/dist/consent-manager.d.ts +2 -2
- package/dist/consent-manager.js +5 -2
- package/dist/direct-tools.d.ts +1 -1
- package/dist/direct-tools.js +13 -7
- package/dist/elicitation-handler.d.ts +2 -3
- package/dist/elicitation-handler.js +113 -58
- package/dist/gateway-arguments.d.ts +1 -1
- package/dist/gateway-arguments.js +90 -23
- package/dist/glimpse-ui.js +10 -5
- package/dist/host-html-template.d.ts +3 -1
- package/dist/host-html-template.js +9 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +264 -119
- package/dist/init.d.ts +1 -3
- package/dist/init.js +91 -51
- package/dist/legacy-migration.d.ts +1 -1
- package/dist/legacy-migration.js +17 -12
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +33 -17
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +4 -2
- package/dist/mcp-auth-flow.js +150 -58
- package/dist/mcp-auth.d.ts +7 -7
- package/dist/mcp-auth.js +189 -138
- package/dist/mcp-callback-server.js +19 -8
- package/dist/mcp-code.js +168 -54
- package/dist/mcp-keyring-helper.cjs +57 -23
- package/dist/mcp-oauth-provider.d.ts +6 -7
- package/dist/mcp-oauth-provider.js +146 -72
- package/dist/mcp-oauth-runtime.js +29 -16
- package/dist/mcp-output-guard.js +193 -65
- package/dist/mcp-output-preview.js +41 -20
- package/dist/mcp-panel.d.ts +9 -9
- package/dist/mcp-panel.js +141 -70
- package/dist/mcp-script-worker.mjs +135 -43
- package/dist/mcp-setup-panel.d.ts +8 -8
- package/dist/mcp-setup-panel.js +130 -44
- package/dist/mcp-status.js +14 -7
- package/dist/mcp-trace.d.ts +10 -5
- package/dist/mcp-trace.js +127 -61
- package/dist/metadata-cache.d.ts +3 -4
- package/dist/metadata-cache.js +83 -43
- package/dist/npx-resolver.js +101 -60
- package/dist/onboarding-state.js +4 -2
- package/dist/plain-result-reader.js +78 -31
- package/dist/prompts.js +36 -20
- package/dist/proxy-modes.js +670 -193
- package/dist/resource-tools.js +30 -9
- package/dist/runtime-owner.js +36 -18
- package/dist/sampling-handler.d.ts +1 -2
- package/dist/sampling-handler.js +41 -21
- package/dist/search-ranking.d.ts +1 -1
- package/dist/search-ranking.js +67 -34
- package/dist/server-manager.d.ts +11 -11
- package/dist/server-manager.js +283 -166
- package/dist/session-recovery.js +113 -61
- package/dist/tool-approval.js +9 -3
- package/dist/tool-loader.js +154 -55
- package/dist/tool-metadata.d.ts +1 -1
- package/dist/tool-metadata.js +51 -23
- package/dist/tool-registrar.js +32 -10
- package/dist/tool-result-renderer.js +58 -33
- package/dist/types.d.ts +2 -2
- package/dist/types.js +39 -18
- package/dist/ui-message-history.js +33 -13
- package/dist/ui-resource-handler.d.ts +3 -3
- package/dist/ui-resource-handler.js +37 -21
- package/dist/ui-server.js +101 -43
- package/dist/ui-session.js +66 -29
- package/dist/ui-stream-types.js +14 -4
- package/dist/ui-tool-visibility.js +12 -6
- package/dist/unix-socket-transport.d.ts +1 -2
- package/dist/unix-socket-transport.js +19 -12
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +60 -35
- package/package.json +28 -2
- 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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
}): {
|
package/dist/legacy-migration.js
CHANGED
|
@@ -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
|
-
|
|
13
|
+
}
|
|
14
|
+
if (existsSync(destination)) {
|
|
15
15
|
return { source, destination, status: "existing" };
|
|
16
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
}
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -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) {
|