lsd-pi 1.3.9 → 1.3.11
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/dist/loader.js +0 -0
- package/dist/resources/agents/scout.md +1 -0
- package/dist/resources/extensions/mcp-client/index.js +191 -83
- package/dist/resources/extensions/mcp-client/mcp-manager-component.js +220 -0
- package/dist/resources/extensions/slash-commands/plan.js +67 -13
- package/dist/resources/extensions/subagent/agents.js +7 -0
- package/dist/resources/extensions/subagent/index.js +25 -8
- package/dist/resources/extensions/subagent/model-resolution.js +1 -0
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +104 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +39 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +135 -18
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +21 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +146 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +51 -13
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +75 -4
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +31 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +129 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +158 -18
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +163 -9
- package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +60 -13
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +86 -5
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -2
- package/pkg/package.json +1 -1
- package/src/resources/agents/scout.md +1 -0
- package/src/resources/extensions/mcp-client/index.ts +212 -90
- package/src/resources/extensions/mcp-client/mcp-manager-component.ts +256 -0
- package/src/resources/extensions/mcp-client/tests/mcp-manager-component.test.ts +141 -0
- package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +18 -2
- package/src/resources/extensions/slash-commands/plan.ts +70 -13
- package/src/resources/extensions/subagent/agents.ts +9 -0
- package/src/resources/extensions/subagent/index.ts +30 -8
- package/src/resources/extensions/subagent/model-resolution.ts +1 -0
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +34 -1
- package/dist/headless-query.d.ts +0 -40
- package/dist/headless-query.js +0 -77
- package/src/resources/extensions/gsd/tests/test-helpers.ts +0 -61
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Client Extension — Native MCP server integration for pi
|
|
3
3
|
*
|
|
4
|
-
* Provides on-demand access to MCP servers configured in
|
|
5
|
-
* (.mcp.json, .lsd/mcp.json, with legacy .gsd/mcp.json fallback) using the
|
|
4
|
+
* Provides on-demand access to MCP servers configured in global (~/.lsd/mcp.json)
|
|
5
|
+
* and project files (.mcp.json, .lsd/mcp.json, with legacy .gsd/mcp.json fallback) using the
|
|
6
6
|
* @modelcontextprotocol/sdk Client directly — no external CLI dependency
|
|
7
7
|
* required.
|
|
8
8
|
*
|
|
@@ -25,7 +25,9 @@ import { Client } from "@modelcontextprotocol/sdk/client";
|
|
|
25
25
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
26
26
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
27
27
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
28
|
+
import { homedir } from "node:os";
|
|
28
29
|
import { basename, dirname, join } from "node:path";
|
|
30
|
+
import { McpManagerComponent, type McpManagerServerInfo } from "./mcp-manager-component.js";
|
|
29
31
|
|
|
30
32
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
31
33
|
|
|
@@ -154,6 +156,7 @@ function readConfigs(): McpServerConfig[] {
|
|
|
154
156
|
join(process.cwd(), ".mcp.json"),
|
|
155
157
|
join(process.cwd(), ".lsd", "mcp.json"),
|
|
156
158
|
join(process.cwd(), ".gsd", "mcp.json"),
|
|
159
|
+
join(homedir(), ".lsd", "mcp.json"),
|
|
157
160
|
];
|
|
158
161
|
|
|
159
162
|
for (const configPath of configPaths) {
|
|
@@ -241,8 +244,6 @@ async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client>
|
|
|
241
244
|
if (!config) throw new Error(`Unknown MCP server: "${name}". Use mcp_servers to list available servers.`);
|
|
242
245
|
if (!config.enabled) throw new Error(`Server "${config.name}" is disabled. Use /mcp enable ${config.name}.`);
|
|
243
246
|
|
|
244
|
-
// Always use config.name as the canonical cache key so that variant
|
|
245
|
-
// casing / whitespace still hits the same connection.
|
|
246
247
|
const existing = connections.get(config.name);
|
|
247
248
|
if (existing) return existing.client;
|
|
248
249
|
|
|
@@ -271,40 +272,118 @@ async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client>
|
|
|
271
272
|
return client;
|
|
272
273
|
}
|
|
273
274
|
|
|
275
|
+
function mapToolSchemas(tools: Array<{ name: string; description?: string; inputSchema?: unknown }>): McpToolSchema[] {
|
|
276
|
+
return tools.map((tool) => ({
|
|
277
|
+
name: tool.name,
|
|
278
|
+
description: tool.description ?? "",
|
|
279
|
+
inputSchema: tool.inputSchema as Record<string, unknown> | undefined,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function shouldRetryMcpOperation(error: unknown): boolean {
|
|
284
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
285
|
+
if (/unknown mcp server/i.test(message)) return false;
|
|
286
|
+
if (/is disabled/i.test(message)) return false;
|
|
287
|
+
if (/unsupported transport/i.test(message)) return false;
|
|
288
|
+
if (/abort|cancel/i.test(message)) return false;
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function listServerTools(
|
|
293
|
+
name: string,
|
|
294
|
+
signal?: AbortSignal,
|
|
295
|
+
options?: { forceReconnect?: boolean; useCache?: boolean },
|
|
296
|
+
): Promise<{ canonicalName: string; tools: McpToolSchema[]; cached: boolean }> {
|
|
297
|
+
const canonicalName = getCanonicalServerName(name);
|
|
298
|
+
if (options?.useCache !== false) {
|
|
299
|
+
const cached = toolCache.get(canonicalName);
|
|
300
|
+
if (cached) {
|
|
301
|
+
return { canonicalName, tools: cached, cached: true };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let attempt = 0;
|
|
306
|
+
while (attempt < 2) {
|
|
307
|
+
try {
|
|
308
|
+
if (attempt === 0 && options?.forceReconnect) {
|
|
309
|
+
await closeServerConnection(canonicalName);
|
|
310
|
+
}
|
|
311
|
+
if (attempt > 0) {
|
|
312
|
+
await closeServerConnection(canonicalName);
|
|
313
|
+
}
|
|
314
|
+
const client = await getOrConnect(canonicalName, signal);
|
|
315
|
+
const result = await client.listTools(undefined, { signal, timeout: 30000 });
|
|
316
|
+
const tools = mapToolSchemas(result.tools ?? []);
|
|
317
|
+
toolCache.set(canonicalName, tools);
|
|
318
|
+
return { canonicalName, tools, cached: false };
|
|
319
|
+
} catch (error) {
|
|
320
|
+
attempt += 1;
|
|
321
|
+
await closeServerConnection(canonicalName);
|
|
322
|
+
if (attempt >= 2 || !shouldRetryMcpOperation(error)) {
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
throw new Error(`Failed to list tools for ${canonicalName}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function callServerTool(
|
|
332
|
+
serverName: string,
|
|
333
|
+
toolName: string,
|
|
334
|
+
args: Record<string, unknown>,
|
|
335
|
+
signal?: AbortSignal,
|
|
336
|
+
): Promise<{ canonicalServer: string; result: Awaited<ReturnType<Client["callTool"]>> }> {
|
|
337
|
+
const canonicalServer = getCanonicalServerName(serverName);
|
|
338
|
+
let attempt = 0;
|
|
339
|
+
while (attempt < 2) {
|
|
340
|
+
try {
|
|
341
|
+
if (attempt > 0) {
|
|
342
|
+
await closeServerConnection(canonicalServer);
|
|
343
|
+
}
|
|
344
|
+
const client = await getOrConnect(canonicalServer, signal);
|
|
345
|
+
const result = await client.callTool(
|
|
346
|
+
{ name: toolName, arguments: args },
|
|
347
|
+
undefined,
|
|
348
|
+
{ signal, timeout: 60000 },
|
|
349
|
+
);
|
|
350
|
+
return { canonicalServer, result };
|
|
351
|
+
} catch (error) {
|
|
352
|
+
attempt += 1;
|
|
353
|
+
await closeServerConnection(canonicalServer);
|
|
354
|
+
if (attempt >= 2 || !shouldRetryMcpOperation(error)) {
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
throw new Error(`Failed to call ${canonicalServer}.${toolName}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function warmupServer(name: string, signal?: AbortSignal): Promise<McpWarmupResult> {
|
|
364
|
+
try {
|
|
365
|
+
const { canonicalName, tools } = await listServerTools(name, signal, { useCache: false });
|
|
366
|
+
return {
|
|
367
|
+
name: canonicalName,
|
|
368
|
+
status: "connected",
|
|
369
|
+
toolCount: tools.length,
|
|
370
|
+
};
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return {
|
|
373
|
+
name: getCanonicalServerName(name),
|
|
374
|
+
status: "error",
|
|
375
|
+
error: error instanceof Error ? error.message : String(error),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
274
380
|
async function warmupEnabledServers(): Promise<McpWarmupResult[]> {
|
|
275
381
|
if (warmupPromise) return warmupPromise;
|
|
276
382
|
|
|
277
383
|
warmupPromise = (async () => {
|
|
278
384
|
const enabledServers = readConfigs().filter((server) => server.enabled);
|
|
279
385
|
if (enabledServers.length === 0) return [];
|
|
280
|
-
|
|
281
|
-
const results = await Promise.allSettled(enabledServers.map(async (server) => {
|
|
282
|
-
const client = await getOrConnect(server.name);
|
|
283
|
-
const result = await client.listTools(undefined, { timeout: 30000 });
|
|
284
|
-
const tools: McpToolSchema[] = (result.tools ?? []).map((tool) => ({
|
|
285
|
-
name: tool.name,
|
|
286
|
-
description: tool.description ?? "",
|
|
287
|
-
inputSchema: tool.inputSchema as Record<string, unknown> | undefined,
|
|
288
|
-
}));
|
|
289
|
-
toolCache.set(server.name, tools);
|
|
290
|
-
return {
|
|
291
|
-
name: server.name,
|
|
292
|
-
status: "connected" as const,
|
|
293
|
-
toolCount: tools.length,
|
|
294
|
-
};
|
|
295
|
-
}));
|
|
296
|
-
|
|
297
|
-
return results.map((result, index) => {
|
|
298
|
-
if (result.status === "fulfilled") {
|
|
299
|
-
return result.value;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
name: enabledServers[index]?.name ?? `server-${index + 1}`,
|
|
304
|
-
status: "error" as const,
|
|
305
|
-
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
306
|
-
};
|
|
307
|
-
});
|
|
386
|
+
return Promise.all(enabledServers.map((server) => warmupServer(server.name)));
|
|
308
387
|
})();
|
|
309
388
|
|
|
310
389
|
try {
|
|
@@ -333,10 +412,43 @@ async function reloadMcpState(): Promise<void> {
|
|
|
333
412
|
configCache = null;
|
|
334
413
|
}
|
|
335
414
|
|
|
415
|
+
function getSourceLabel(sourcePath?: string): string {
|
|
416
|
+
if (!sourcePath) return "";
|
|
417
|
+
return sourcePath.startsWith(homedir()) ? "global" : "project";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getManagerServerInfo(): McpManagerServerInfo[] {
|
|
421
|
+
return readConfigs().map((server) => ({
|
|
422
|
+
name: server.name,
|
|
423
|
+
enabled: server.enabled,
|
|
424
|
+
connected: connections.has(server.name),
|
|
425
|
+
transport: server.transport,
|
|
426
|
+
toolCount: toolCache.get(server.name)?.length ?? 0,
|
|
427
|
+
sourceLabel: getSourceLabel(server.sourcePath),
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
|
|
336
431
|
// ─── Formatters ───────────────────────────────────────────────────────────────
|
|
337
432
|
|
|
338
433
|
function formatServerList(servers: McpServerConfig[]): string {
|
|
339
|
-
if (servers.length === 0)
|
|
434
|
+
if (servers.length === 0) {
|
|
435
|
+
return [
|
|
436
|
+
"No MCP servers configured.\n",
|
|
437
|
+
"Configuration guide:",
|
|
438
|
+
" Global (all projects): ~/.lsd/mcp.json",
|
|
439
|
+
" Project-level: .mcp.json or .lsd/mcp.json\n",
|
|
440
|
+
'Example ~/.lsd/mcp.json:',
|
|
441
|
+
'{',
|
|
442
|
+
' "mcpServers": {',
|
|
443
|
+
' "my-server": {',
|
|
444
|
+
' "command": "path/to/server",',
|
|
445
|
+
' "args": ["--working-dir", "."]',
|
|
446
|
+
' }',
|
|
447
|
+
' }',
|
|
448
|
+
'}\n',
|
|
449
|
+
"After editing, run: /mcp reload",
|
|
450
|
+
].join("\n");
|
|
451
|
+
}
|
|
340
452
|
|
|
341
453
|
const lines: string[] = ["MCP servers\n"];
|
|
342
454
|
|
|
@@ -349,11 +461,14 @@ function formatServerList(servers: McpServerConfig[]): string {
|
|
|
349
461
|
lines.push(` connected: ${connected}`);
|
|
350
462
|
lines.push(` transport: ${s.transport}`);
|
|
351
463
|
lines.push(` tools: ${tools}`);
|
|
352
|
-
if (s.sourcePath)
|
|
464
|
+
if (s.sourcePath) {
|
|
465
|
+
lines.push(` source: ${getSourceLabel(s.sourcePath)} — ${basename(s.sourcePath)}`);
|
|
466
|
+
}
|
|
353
467
|
lines.push("");
|
|
354
468
|
}
|
|
355
469
|
|
|
356
470
|
lines.push("Hints:");
|
|
471
|
+
lines.push(" /mcp");
|
|
357
472
|
lines.push(" /mcp inspect <server>");
|
|
358
473
|
lines.push(" /mcp enable <server>");
|
|
359
474
|
lines.push(" /mcp disable <server>");
|
|
@@ -394,6 +509,42 @@ function formatMcpCommandHelp(): string {
|
|
|
394
509
|
].join("\n");
|
|
395
510
|
}
|
|
396
511
|
|
|
512
|
+
async function openMcpManager(ctx: ExtensionCommandContext): Promise<void> {
|
|
513
|
+
await ctx.ui.custom<void>(
|
|
514
|
+
(tui, theme, _keybindings, done) => new McpManagerComponent({
|
|
515
|
+
getServers: () => getManagerServerInfo(),
|
|
516
|
+
onToggle: async (name) => {
|
|
517
|
+
const config = getServerConfig(name);
|
|
518
|
+
if (!config) return null;
|
|
519
|
+
const result = await setServerEnabled(name, !config.enabled);
|
|
520
|
+
const updated = getServerConfig(result.canonicalName);
|
|
521
|
+
if (updated?.enabled) {
|
|
522
|
+
await warmupServer(updated.name);
|
|
523
|
+
}
|
|
524
|
+
return getManagerServerInfo().find((server) => server.name === result.canonicalName) ?? null;
|
|
525
|
+
},
|
|
526
|
+
onInspect: async (name) => {
|
|
527
|
+
const { canonicalName, tools } = await listServerTools(name, undefined, { useCache: true });
|
|
528
|
+
return formatToolList(canonicalName, tools);
|
|
529
|
+
},
|
|
530
|
+
onReconnect: async (name) => {
|
|
531
|
+
const { canonicalName } = await listServerTools(name, undefined, { forceReconnect: true, useCache: false });
|
|
532
|
+
return getManagerServerInfo().find((server) => server.name === canonicalName) ?? null;
|
|
533
|
+
},
|
|
534
|
+
onClose: () => done(undefined),
|
|
535
|
+
requestRender: () => tui.requestRender(),
|
|
536
|
+
}, theme),
|
|
537
|
+
{
|
|
538
|
+
overlay: true,
|
|
539
|
+
overlayOptions: {
|
|
540
|
+
width: "80%",
|
|
541
|
+
maxHeight: "70%",
|
|
542
|
+
anchor: "center",
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
397
548
|
async function handleMcpCommand(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
398
549
|
const trimmed = args.trim();
|
|
399
550
|
const parts = trimmed.split(/\s+/).filter(Boolean);
|
|
@@ -418,21 +569,9 @@ async function handleMcpCommand(args: string, ctx: ExtensionCommandContext): Pro
|
|
|
418
569
|
}
|
|
419
570
|
|
|
420
571
|
const canonicalName = config.name;
|
|
421
|
-
const cached = toolCache.get(canonicalName);
|
|
422
|
-
if (cached) {
|
|
423
|
-
ctx.ui.notify(formatToolList(canonicalName, cached), "info");
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
572
|
|
|
427
573
|
try {
|
|
428
|
-
const
|
|
429
|
-
const result = await client.listTools(undefined, { timeout: 30000 });
|
|
430
|
-
const tools: McpToolSchema[] = (result.tools ?? []).map((tool) => ({
|
|
431
|
-
name: tool.name,
|
|
432
|
-
description: tool.description ?? "",
|
|
433
|
-
inputSchema: tool.inputSchema as Record<string, unknown> | undefined,
|
|
434
|
-
}));
|
|
435
|
-
toolCache.set(canonicalName, tools);
|
|
574
|
+
const { tools } = await listServerTools(canonicalName, undefined, { useCache: true });
|
|
436
575
|
ctx.ui.notify(formatToolList(canonicalName, tools), "info");
|
|
437
576
|
} catch (error) {
|
|
438
577
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -456,9 +595,8 @@ async function handleMcpCommand(args: string, ctx: ExtensionCommandContext): Pro
|
|
|
456
595
|
ctx.ui.notify(`MCP server ${result.canonicalName} ${changeText}.`, "info");
|
|
457
596
|
|
|
458
597
|
if (enabled) {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
if (warmupResult?.status === "error") {
|
|
598
|
+
const warmupResult = await warmupServer(result.canonicalName);
|
|
599
|
+
if (warmupResult.status === "error") {
|
|
462
600
|
ctx.ui.notify(`Failed to connect ${result.canonicalName}: ${warmupResult.error}`, "error");
|
|
463
601
|
}
|
|
464
602
|
}
|
|
@@ -542,6 +680,10 @@ export default function(pi: ExtensionAPI) {
|
|
|
542
680
|
return [];
|
|
543
681
|
},
|
|
544
682
|
handler: async (args, ctx) => {
|
|
683
|
+
if (!args.trim() && typeof ctx.ui.custom === "function") {
|
|
684
|
+
await openMcpManager(ctx);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
545
687
|
await handleMcpCommand(args, ctx);
|
|
546
688
|
},
|
|
547
689
|
});
|
|
@@ -552,7 +694,8 @@ export default function(pi: ExtensionAPI) {
|
|
|
552
694
|
name: "mcp_servers",
|
|
553
695
|
label: "MCP Servers",
|
|
554
696
|
description:
|
|
555
|
-
"List all available MCP servers
|
|
697
|
+
"List all available MCP servers from global (~/.lsd/mcp.json) and project-level " +
|
|
698
|
+
"(.mcp.json, .lsd/mcp.json, legacy .gsd/mcp.json) config files. " +
|
|
556
699
|
"Shows server names, transport type, and connection status. Use mcp_discover to get full tool schemas for a server.",
|
|
557
700
|
promptSnippet:
|
|
558
701
|
"List available MCP servers from project configuration",
|
|
@@ -623,32 +766,7 @@ export default function(pi: ExtensionAPI) {
|
|
|
623
766
|
|
|
624
767
|
async execute(_id, params, signal) {
|
|
625
768
|
try {
|
|
626
|
-
const canonicalServer =
|
|
627
|
-
|
|
628
|
-
// Return cached tools if available
|
|
629
|
-
const cached = toolCache.get(canonicalServer);
|
|
630
|
-
if (cached) {
|
|
631
|
-
const text = formatToolList(canonicalServer, cached);
|
|
632
|
-
const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
633
|
-
let finalText = truncation.content;
|
|
634
|
-
if (truncation.truncated) {
|
|
635
|
-
finalText += `\n\n[Truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
|
|
636
|
-
}
|
|
637
|
-
return {
|
|
638
|
-
content: [{ type: "text", text: finalText }],
|
|
639
|
-
details: { server: canonicalServer, toolCount: cached.length, cached: true },
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
const client = await getOrConnect(canonicalServer, signal);
|
|
644
|
-
const result = await client.listTools(undefined, { signal, timeout: 30000 });
|
|
645
|
-
const tools: McpToolSchema[] = (result.tools ?? []).map((t) => ({
|
|
646
|
-
name: t.name,
|
|
647
|
-
description: t.description ?? "",
|
|
648
|
-
inputSchema: t.inputSchema as Record<string, unknown> | undefined,
|
|
649
|
-
}));
|
|
650
|
-
toolCache.set(canonicalServer, tools);
|
|
651
|
-
|
|
769
|
+
const { canonicalName: canonicalServer, tools, cached } = await listServerTools(params.server, signal, { useCache: true });
|
|
652
770
|
const text = formatToolList(canonicalServer, tools);
|
|
653
771
|
const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
654
772
|
let finalText = truncation.content;
|
|
@@ -658,7 +776,7 @@ export default function(pi: ExtensionAPI) {
|
|
|
658
776
|
|
|
659
777
|
return {
|
|
660
778
|
content: [{ type: "text", text: finalText }],
|
|
661
|
-
details: { server: canonicalServer, toolCount: tools.length, cached
|
|
779
|
+
details: { server: canonicalServer, toolCount: tools.length, cached },
|
|
662
780
|
};
|
|
663
781
|
} catch (err: unknown) {
|
|
664
782
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -717,15 +835,13 @@ export default function(pi: ExtensionAPI) {
|
|
|
717
835
|
|
|
718
836
|
async execute(_id, params, signal) {
|
|
719
837
|
try {
|
|
720
|
-
const canonicalServer =
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
{ signal, timeout: 60000 },
|
|
838
|
+
const { canonicalServer, result } = await callServerTool(
|
|
839
|
+
params.server,
|
|
840
|
+
params.tool,
|
|
841
|
+
params.args ?? {},
|
|
842
|
+
signal,
|
|
726
843
|
);
|
|
727
844
|
|
|
728
|
-
// Serialize result content to text
|
|
729
845
|
const contentItems = result.content as Array<{ type: string; text?: string }>;
|
|
730
846
|
const raw = contentItems
|
|
731
847
|
.map((c) => (c.type === "text" ? c.text ?? "" : JSON.stringify(c)))
|
|
@@ -800,20 +916,26 @@ export default function(pi: ExtensionAPI) {
|
|
|
800
916
|
const servers = readConfigs();
|
|
801
917
|
const enabledServers = servers.filter((server) => server.enabled);
|
|
802
918
|
if (servers.length > 0) {
|
|
803
|
-
ctx.ui.notify(`MCP client ready — ${enabledServers.length}/${servers.length} server(s) enabled
|
|
919
|
+
ctx.ui.notify(`MCP client ready — ${enabledServers.length}/${servers.length} server(s) enabled, warming up…`, "info");
|
|
804
920
|
}
|
|
805
921
|
if (enabledServers.length === 0) return;
|
|
806
922
|
|
|
807
|
-
|
|
923
|
+
try {
|
|
924
|
+
const warmupTimeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error("warmup timed out after 30s")), 30_000));
|
|
925
|
+
const results = await Promise.race([warmupEnabledServers(), warmupTimeout]);
|
|
926
|
+
const succeeded = results.filter((entry) => entry.status === "connected");
|
|
808
927
|
const failed = results.filter((entry) => entry.status === "error");
|
|
928
|
+
if (succeeded.length > 0) {
|
|
929
|
+
ctx.ui.notify(`MCP autoconnect complete — ${succeeded.length} server(s) connected`, "success");
|
|
930
|
+
}
|
|
809
931
|
if (failed.length > 0) {
|
|
810
932
|
const failureSummary = failed.map((entry) => `${entry.name}: ${entry.error}`).join("; ");
|
|
811
933
|
ctx.ui.notify(`MCP autoconnect partial failure — ${failureSummary}`, "warning");
|
|
812
934
|
}
|
|
813
|
-
}
|
|
935
|
+
} catch (error) {
|
|
814
936
|
const message = error instanceof Error ? error.message : String(error);
|
|
815
937
|
ctx.ui.notify(`MCP autoconnect failed: ${message}`, "warning");
|
|
816
|
-
}
|
|
938
|
+
}
|
|
817
939
|
});
|
|
818
940
|
|
|
819
941
|
pi.on("session_shutdown", async () => {
|
|
@@ -823,6 +945,6 @@ export default function(pi: ExtensionAPI) {
|
|
|
823
945
|
|
|
824
946
|
pi.on("session_switch", async () => {
|
|
825
947
|
await reloadMcpState();
|
|
826
|
-
|
|
948
|
+
await warmupEnabledServers();
|
|
827
949
|
});
|
|
828
950
|
}
|