@fitchmultz/pi-mcp-adapter 8.0.5 → 8.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +140 -7
- 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.d.ts +3 -3
- package/dist/tool-result-renderer.js +65 -44
- 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 +29 -5
- package/skills/mcp-scripting/SKILL.md +6 -6
package/dist/mcp-panel.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import { createPanelKeys } from "./panel-keys.js";
|
|
3
3
|
import { sanitizeTerminalText, stripOscSequences } from "./utils.js";
|
|
4
|
-
import { reconstructToolMetadata } from "./metadata-cache.js";
|
|
4
|
+
import { reconstructToolMetadata, } from "./metadata-cache.js";
|
|
5
5
|
const DEFAULT_THEME = {
|
|
6
6
|
border: "2",
|
|
7
7
|
title: "2",
|
|
@@ -15,8 +15,9 @@ const DEFAULT_THEME = {
|
|
|
15
15
|
cancel: "31",
|
|
16
16
|
};
|
|
17
17
|
function fg(code, text) {
|
|
18
|
-
if (!code)
|
|
18
|
+
if (!code) {
|
|
19
19
|
return text;
|
|
20
|
+
}
|
|
20
21
|
return `\x1b[${code}m${text}\x1b[0m`;
|
|
21
22
|
}
|
|
22
23
|
const RAINBOW_COLORS = [
|
|
@@ -32,8 +33,9 @@ function rainbowProgress(filled, total) {
|
|
|
32
33
|
const dots = [];
|
|
33
34
|
for (let i = 0; i < total; i++) {
|
|
34
35
|
const color = RAINBOW_COLORS[i % RAINBOW_COLORS.length];
|
|
35
|
-
if (!color)
|
|
36
|
+
if (!color) {
|
|
36
37
|
continue;
|
|
38
|
+
}
|
|
37
39
|
dots.push(fg(color, i < filled ? "●" : "○"));
|
|
38
40
|
}
|
|
39
41
|
return dots.join(" ");
|
|
@@ -41,8 +43,9 @@ function rainbowProgress(filled, total) {
|
|
|
41
43
|
function fuzzyScore(query, text) {
|
|
42
44
|
const lq = query.toLowerCase();
|
|
43
45
|
const lt = text.toLowerCase();
|
|
44
|
-
if (lt.includes(lq))
|
|
46
|
+
if (lt.includes(lq)) {
|
|
45
47
|
return 100 + (lq.length / lt.length) * 50;
|
|
48
|
+
}
|
|
46
49
|
let score = 0;
|
|
47
50
|
let qi = 0;
|
|
48
51
|
let consecutive = 0;
|
|
@@ -124,8 +127,9 @@ class McpPanel {
|
|
|
124
127
|
this.keys = createPanelKeys(options.keybindings);
|
|
125
128
|
this.prefix = config.settings?.toolPrefix ?? "server";
|
|
126
129
|
for (const [serverName, definition] of Object.entries(config.mcpServers)) {
|
|
127
|
-
if (this.authOnly && !callbacks.canAuthenticate(serverName))
|
|
130
|
+
if (this.authOnly && !callbacks.canAuthenticate(serverName)) {
|
|
128
131
|
continue;
|
|
132
|
+
}
|
|
129
133
|
const prov = provenance.get(serverName);
|
|
130
134
|
const serverCache = cache?.servers?.[serverName];
|
|
131
135
|
const globalDirect = config.settings?.directTools;
|
|
@@ -149,18 +153,20 @@ class McpPanel {
|
|
|
149
153
|
connectionStatus: status,
|
|
150
154
|
failureMessage,
|
|
151
155
|
tools: [],
|
|
152
|
-
hasCachedData:
|
|
156
|
+
hasCachedData: Boolean(serverCache),
|
|
153
157
|
};
|
|
154
158
|
this.servers.push(server);
|
|
155
|
-
if (serverCache && !this.authOnly)
|
|
159
|
+
if (serverCache && !this.authOnly) {
|
|
156
160
|
this.rebuildServerTools(server, serverCache);
|
|
161
|
+
}
|
|
157
162
|
}
|
|
158
163
|
this.rebuildVisibleItems();
|
|
159
164
|
this.resetInactivityTimeout();
|
|
160
165
|
}
|
|
161
166
|
resetInactivityTimeout() {
|
|
162
|
-
if (this.inactivityTimeout)
|
|
167
|
+
if (this.inactivityTimeout) {
|
|
163
168
|
clearTimeout(this.inactivityTimeout);
|
|
169
|
+
}
|
|
164
170
|
this.inactivityTimeout = setTimeout(() => {
|
|
165
171
|
this.cleanup();
|
|
166
172
|
this.done({ cancelled: true, changes: new Map() });
|
|
@@ -178,8 +184,9 @@ class McpPanel {
|
|
|
178
184
|
this.visibleItems = [];
|
|
179
185
|
for (let si = 0; si < this.servers.length; si++) {
|
|
180
186
|
const server = this.servers[si];
|
|
181
|
-
if (!server)
|
|
187
|
+
if (!server) {
|
|
182
188
|
continue;
|
|
189
|
+
}
|
|
183
190
|
if (query && this.authOnly) {
|
|
184
191
|
const score = mode === "name" ? fuzzyScore(query, server.name) : 0;
|
|
185
192
|
if (score > 0) {
|
|
@@ -191,14 +198,16 @@ class McpPanel {
|
|
|
191
198
|
if (server.expanded || query) {
|
|
192
199
|
for (let ti = 0; ti < server.tools.length; ti++) {
|
|
193
200
|
const tool = server.tools[ti];
|
|
194
|
-
if (!tool)
|
|
201
|
+
if (!tool) {
|
|
195
202
|
continue;
|
|
203
|
+
}
|
|
196
204
|
if (query) {
|
|
197
205
|
const score = mode === "name"
|
|
198
206
|
? Math.max(fuzzyScore(query, tool.name), fuzzyScore(query, server.name) * 0.6)
|
|
199
207
|
: fuzzyScore(query, tool.description);
|
|
200
|
-
if (score === 0)
|
|
208
|
+
if (score === 0) {
|
|
201
209
|
continue;
|
|
210
|
+
}
|
|
202
211
|
}
|
|
203
212
|
this.visibleItems.push({ type: "tool", serverIndex: si, toolIndex: ti });
|
|
204
213
|
}
|
|
@@ -220,8 +229,9 @@ class McpPanel {
|
|
|
220
229
|
const changes = new Map();
|
|
221
230
|
for (const server of this.servers) {
|
|
222
231
|
const changed = server.tools.some((t) => t.isDirect !== t.wasDirect);
|
|
223
|
-
if (!changed)
|
|
232
|
+
if (!changed) {
|
|
224
233
|
continue;
|
|
234
|
+
}
|
|
225
235
|
const directTools = server.tools.filter((t) => t.isDirect);
|
|
226
236
|
if (directTools.length === server.tools.length && server.tools.length > 0) {
|
|
227
237
|
changes.set(server.name, true);
|
|
@@ -238,8 +248,9 @@ class McpPanel {
|
|
|
238
248
|
handleInput(data) {
|
|
239
249
|
this.resetInactivityTimeout();
|
|
240
250
|
this.importNotice = null;
|
|
241
|
-
if (!this.authInFlight)
|
|
251
|
+
if (!this.authInFlight) {
|
|
242
252
|
this.authNotice = null;
|
|
253
|
+
}
|
|
243
254
|
if (this.confirmingDiscard) {
|
|
244
255
|
this.handleDiscardInput(data);
|
|
245
256
|
return;
|
|
@@ -283,8 +294,9 @@ class McpPanel {
|
|
|
283
294
|
if (matchesKey(data, "space")) {
|
|
284
295
|
// Toggle even while in desc search
|
|
285
296
|
const item = this.visibleItems[this.cursorIndex];
|
|
286
|
-
if (item)
|
|
297
|
+
if (item) {
|
|
287
298
|
this.toggleItem(item);
|
|
299
|
+
}
|
|
288
300
|
return;
|
|
289
301
|
}
|
|
290
302
|
if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
@@ -321,20 +333,24 @@ class McpPanel {
|
|
|
321
333
|
}
|
|
322
334
|
if (matchesKey(data, "space")) {
|
|
323
335
|
const item = this.visibleItems[this.cursorIndex];
|
|
324
|
-
if (item && !this.authOnly)
|
|
336
|
+
if (item && !this.authOnly) {
|
|
325
337
|
this.toggleItem(item);
|
|
338
|
+
}
|
|
326
339
|
return;
|
|
327
340
|
}
|
|
328
341
|
if (this.keys.selectConfirm(data)) {
|
|
329
342
|
const item = this.visibleItems[this.cursorIndex];
|
|
330
|
-
if (!item)
|
|
343
|
+
if (!item) {
|
|
331
344
|
return;
|
|
345
|
+
}
|
|
332
346
|
const server = this.servers[item.serverIndex];
|
|
333
|
-
if (!server)
|
|
347
|
+
if (!server) {
|
|
334
348
|
return;
|
|
349
|
+
}
|
|
335
350
|
if (item.type === "server") {
|
|
336
|
-
if (server.connectionStatus === "disabled")
|
|
351
|
+
if (server.connectionStatus === "disabled") {
|
|
337
352
|
return;
|
|
353
|
+
}
|
|
338
354
|
if (this.authOnly || server.connectionStatus === "needs-auth") {
|
|
339
355
|
this.authenticateServer(server);
|
|
340
356
|
return;
|
|
@@ -345,8 +361,9 @@ class McpPanel {
|
|
|
345
361
|
}
|
|
346
362
|
else if (item.toolIndex !== undefined) {
|
|
347
363
|
const tool = server.tools[item.toolIndex];
|
|
348
|
-
if (!tool)
|
|
364
|
+
if (!tool) {
|
|
349
365
|
return;
|
|
366
|
+
}
|
|
350
367
|
tool.isDirect = !tool.isDirect;
|
|
351
368
|
if (tool.isDirect && server.source === "import") {
|
|
352
369
|
this.importNotice = "Only tool selections save to Pi config.";
|
|
@@ -357,32 +374,40 @@ class McpPanel {
|
|
|
357
374
|
}
|
|
358
375
|
if (matchesKey(data, "ctrl+a")) {
|
|
359
376
|
const item = this.visibleItems[this.cursorIndex];
|
|
360
|
-
if (item)
|
|
377
|
+
if (item) {
|
|
361
378
|
this.authenticateSelectedServer(item);
|
|
379
|
+
}
|
|
362
380
|
return;
|
|
363
381
|
}
|
|
364
382
|
if (matchesKey(data, "ctrl+r")) {
|
|
365
383
|
const item = this.visibleItems[this.cursorIndex];
|
|
366
|
-
if (!item)
|
|
384
|
+
if (!item) {
|
|
367
385
|
return;
|
|
386
|
+
}
|
|
368
387
|
const server = this.servers[item.serverIndex];
|
|
369
|
-
if (server)
|
|
388
|
+
if (server) {
|
|
370
389
|
this.reconnectServer(server);
|
|
390
|
+
}
|
|
371
391
|
return;
|
|
372
392
|
}
|
|
373
393
|
if (matchesKey(data, "ctrl+y")) {
|
|
374
394
|
const item = this.visibleItems[this.cursorIndex];
|
|
375
|
-
if (!item)
|
|
395
|
+
if (!item) {
|
|
376
396
|
return;
|
|
397
|
+
}
|
|
377
398
|
const server = this.servers[item.serverIndex];
|
|
378
|
-
if (!server || server.connectionStatus !== "failed" || !server.failureMessage)
|
|
399
|
+
if (!server || server.connectionStatus !== "failed" || !server.failureMessage) {
|
|
379
400
|
return;
|
|
401
|
+
}
|
|
380
402
|
const serverName = sanitizeDisplayText(server.name);
|
|
381
403
|
const failureMessage = sanitizeDisplayText(server.failureMessage);
|
|
382
|
-
import("@earendil-works/pi-coding-agent")
|
|
404
|
+
import("@earendil-works/pi-coding-agent")
|
|
405
|
+
.then(({ copyToClipboard }) => copyToClipboard(failureMessage))
|
|
406
|
+
.then(() => {
|
|
383
407
|
this.authNotice = `Copied error for ${serverName} to clipboard`;
|
|
384
408
|
this.tui.requestRender();
|
|
385
|
-
})
|
|
409
|
+
})
|
|
410
|
+
.catch((error) => {
|
|
386
411
|
const message = sanitizeDisplayText(error instanceof Error ? error.message : String(error));
|
|
387
412
|
this.authNotice = `Failed to copy error for ${serverName}: ${message}`;
|
|
388
413
|
this.tui.requestRender();
|
|
@@ -390,8 +415,9 @@ class McpPanel {
|
|
|
390
415
|
return;
|
|
391
416
|
}
|
|
392
417
|
if (data === "?") {
|
|
393
|
-
if (this.authOnly)
|
|
418
|
+
if (this.authOnly) {
|
|
394
419
|
return;
|
|
420
|
+
}
|
|
395
421
|
this.descSearchActive = true;
|
|
396
422
|
this.descQuery = "";
|
|
397
423
|
this.rebuildVisibleItems();
|
|
@@ -417,14 +443,17 @@ class McpPanel {
|
|
|
417
443
|
}
|
|
418
444
|
authenticateSelectedServer(item) {
|
|
419
445
|
const server = this.servers[item.serverIndex];
|
|
420
|
-
if (server)
|
|
446
|
+
if (server) {
|
|
421
447
|
this.authenticateServer(server);
|
|
448
|
+
}
|
|
422
449
|
}
|
|
423
450
|
authenticateServer(server) {
|
|
424
|
-
if (this.authInFlight)
|
|
451
|
+
if (this.authInFlight) {
|
|
425
452
|
return;
|
|
426
|
-
|
|
453
|
+
}
|
|
454
|
+
if (server.connectionStatus === "connecting" || server.connectionStatus === "disabled") {
|
|
427
455
|
return;
|
|
456
|
+
}
|
|
428
457
|
const serverName = sanitizeDisplayText(server.name);
|
|
429
458
|
if (!this.callbacks.canAuthenticate(server.name)) {
|
|
430
459
|
this.authNotice = `${serverName} does not use OAuth authentication.`;
|
|
@@ -433,7 +462,9 @@ class McpPanel {
|
|
|
433
462
|
this.authInFlight = server.name;
|
|
434
463
|
this.authNotice = `Authenticating ${serverName}...`;
|
|
435
464
|
this.tui.requestRender();
|
|
436
|
-
this.callbacks
|
|
465
|
+
this.callbacks
|
|
466
|
+
.authenticate(server.name)
|
|
467
|
+
.then((result) => {
|
|
437
468
|
server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
|
|
438
469
|
if (result.ok) {
|
|
439
470
|
this.authNotice = `OAuth finished for ${serverName}. Reconnecting...`;
|
|
@@ -446,7 +477,8 @@ class McpPanel {
|
|
|
446
477
|
this.authNotice = `OAuth failed for ${serverName}${message ? `: ${message}` : ". Check the notification for details."}`;
|
|
447
478
|
this.authInFlight = null;
|
|
448
479
|
this.tui.requestRender();
|
|
449
|
-
})
|
|
480
|
+
})
|
|
481
|
+
.catch((error) => {
|
|
450
482
|
const message = sanitizeDisplayText(error instanceof Error ? error.message : String(error));
|
|
451
483
|
server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
|
|
452
484
|
this.authNotice = `OAuth failed for ${serverName}: ${message}`;
|
|
@@ -455,12 +487,15 @@ class McpPanel {
|
|
|
455
487
|
});
|
|
456
488
|
}
|
|
457
489
|
reconnectServer(server, options = {}) {
|
|
458
|
-
if (server.connectionStatus === "connecting" || server.connectionStatus === "disabled")
|
|
490
|
+
if (server.connectionStatus === "connecting" || server.connectionStatus === "disabled") {
|
|
459
491
|
return;
|
|
492
|
+
}
|
|
460
493
|
const serverName = sanitizeDisplayText(server.name);
|
|
461
494
|
server.connectionStatus = "connecting";
|
|
462
495
|
this.tui.requestRender();
|
|
463
|
-
this.callbacks
|
|
496
|
+
this.callbacks
|
|
497
|
+
.reconnect(server.name, options.afterAuth)
|
|
498
|
+
.then((connected) => {
|
|
464
499
|
server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
|
|
465
500
|
server.failureMessage = this.callbacks.getFailureMessage?.(server.name) ?? null;
|
|
466
501
|
if (server.connectionStatus === "connected") {
|
|
@@ -470,12 +505,14 @@ class McpPanel {
|
|
|
470
505
|
}
|
|
471
506
|
}
|
|
472
507
|
if (options.afterAuth) {
|
|
473
|
-
this.authNotice =
|
|
474
|
-
|
|
475
|
-
|
|
508
|
+
this.authNotice =
|
|
509
|
+
connected && server.connectionStatus === "connected"
|
|
510
|
+
? `OAuth finished for ${serverName}. Reconnected.`
|
|
511
|
+
: `OAuth finished for ${serverName}, but reconnect did not complete. Press ctrl+r to retry.`;
|
|
476
512
|
}
|
|
477
513
|
this.tui.requestRender();
|
|
478
|
-
})
|
|
514
|
+
})
|
|
515
|
+
.catch((error) => {
|
|
479
516
|
server.connectionStatus = "failed";
|
|
480
517
|
const message = sanitizeDisplayText(error instanceof Error ? error.message : String(error));
|
|
481
518
|
this.authNotice = `Reconnect failed for ${serverName}: ${message}`;
|
|
@@ -483,23 +520,27 @@ class McpPanel {
|
|
|
483
520
|
});
|
|
484
521
|
}
|
|
485
522
|
toggleItem(item) {
|
|
486
|
-
if (this.authOnly)
|
|
523
|
+
if (this.authOnly) {
|
|
487
524
|
return;
|
|
525
|
+
}
|
|
488
526
|
const server = this.servers[item.serverIndex];
|
|
489
|
-
if (!server)
|
|
527
|
+
if (!server) {
|
|
490
528
|
return;
|
|
529
|
+
}
|
|
491
530
|
if (item.type === "server") {
|
|
492
531
|
const newState = !server.tools.every((t) => t.isDirect);
|
|
493
532
|
if (server.source === "import" && newState) {
|
|
494
533
|
this.importNotice = "Only tool selections save to Pi config.";
|
|
495
534
|
}
|
|
496
|
-
for (const t of server.tools)
|
|
535
|
+
for (const t of server.tools) {
|
|
497
536
|
t.isDirect = newState;
|
|
537
|
+
}
|
|
498
538
|
}
|
|
499
539
|
else if (item.toolIndex !== undefined) {
|
|
500
540
|
const tool = server.tools[item.toolIndex];
|
|
501
|
-
if (!tool)
|
|
541
|
+
if (!tool) {
|
|
502
542
|
return;
|
|
543
|
+
}
|
|
503
544
|
tool.isDirect = !tool.isDirect;
|
|
504
545
|
if (tool.isDirect && server.source === "import") {
|
|
505
546
|
this.importNotice = "Only tool selections save to Pi config.";
|
|
@@ -537,18 +578,21 @@ class McpPanel {
|
|
|
537
578
|
}
|
|
538
579
|
}
|
|
539
580
|
moveCursor(delta) {
|
|
540
|
-
if (this.visibleItems.length === 0)
|
|
581
|
+
if (this.visibleItems.length === 0) {
|
|
541
582
|
return;
|
|
583
|
+
}
|
|
542
584
|
this.cursorIndex = Math.max(0, Math.min(this.visibleItems.length - 1, this.cursorIndex + delta));
|
|
543
585
|
}
|
|
544
586
|
rebuildServerTools(server, entry) {
|
|
545
|
-
const existingState = new Map(server.tools.map(tool => [tool.name, tool]));
|
|
587
|
+
const existingState = new Map(server.tools.map((tool) => [tool.name, tool]));
|
|
546
588
|
const metadata = reconstructToolMetadata(server.name, entry, this.prefix, server.definition);
|
|
547
|
-
server.resourceCount = metadata.filter(tool => tool.resourceUri !== undefined).length;
|
|
548
|
-
server.tools = metadata
|
|
589
|
+
server.resourceCount = metadata.filter((tool) => tool.resourceUri !== undefined).length;
|
|
590
|
+
server.tools = metadata
|
|
591
|
+
.filter((tool) => tool.resourceUri === undefined)
|
|
592
|
+
.map((tool) => {
|
|
549
593
|
const previous = existingState.get(tool.originalName);
|
|
550
|
-
const pinned = server.toolFilter === true
|
|
551
|
-
|
|
594
|
+
const pinned = server.toolFilter === true ||
|
|
595
|
+
(Array.isArray(server.toolFilter) && server.toolFilter.includes(tool.originalName));
|
|
552
596
|
return {
|
|
553
597
|
name: tool.originalName,
|
|
554
598
|
description: tool.description,
|
|
@@ -568,14 +612,18 @@ class McpPanel {
|
|
|
568
612
|
const bold = (s) => `\x1b[1m${s}\x1b[22m`;
|
|
569
613
|
const italic = (s) => `\x1b[3m${s}\x1b[23m`;
|
|
570
614
|
const inverse = (s) => `\x1b[7m${s}\x1b[27m`;
|
|
571
|
-
const row = (content) => fg(t.border, "│") +
|
|
615
|
+
const row = (content) => fg(t.border, "│") +
|
|
616
|
+
truncateToWidth(" " + sanitizeRowContent(content), innerW, "…", true) +
|
|
617
|
+
fg(t.border, "│");
|
|
572
618
|
const emptyRow = () => fg(t.border, "│") + " ".repeat(innerW) + fg(t.border, "│");
|
|
573
619
|
const divider = () => fg(t.border, "├" + "─".repeat(innerW) + "┤");
|
|
574
620
|
const titleText = this.authOnly ? " MCP OAuth " : " MCP Servers ";
|
|
575
621
|
const borderLen = innerW - visibleWidth(titleText);
|
|
576
622
|
const leftB = Math.floor(borderLen / 2);
|
|
577
623
|
const rightB = borderLen - leftB;
|
|
578
|
-
lines.push(fg(t.border, "╭" + "─".repeat(leftB)) +
|
|
624
|
+
lines.push(fg(t.border, "╭" + "─".repeat(leftB)) +
|
|
625
|
+
fg(t.title, titleText) +
|
|
626
|
+
fg(t.border, "─".repeat(rightB) + "╮"));
|
|
579
627
|
lines.push(emptyRow());
|
|
580
628
|
const cursor = fg(t.selected, "│");
|
|
581
629
|
const searchIcon = fg(t.border, "◎");
|
|
@@ -598,7 +646,9 @@ class McpPanel {
|
|
|
598
646
|
lines.push(divider());
|
|
599
647
|
if (this.servers.length === 0) {
|
|
600
648
|
lines.push(emptyRow());
|
|
601
|
-
lines.push(row(fg(t.hint, italic(this.authOnly
|
|
649
|
+
lines.push(row(fg(t.hint, italic(this.authOnly
|
|
650
|
+
? "No OAuth-capable MCP servers configured."
|
|
651
|
+
: "No MCP servers configured."))));
|
|
602
652
|
lines.push(emptyRow());
|
|
603
653
|
}
|
|
604
654
|
else {
|
|
@@ -609,12 +659,14 @@ class McpPanel {
|
|
|
609
659
|
lines.push(emptyRow());
|
|
610
660
|
for (let i = startIdx; i < endIdx; i++) {
|
|
611
661
|
const item = this.visibleItems[i];
|
|
612
|
-
if (!item)
|
|
662
|
+
if (!item) {
|
|
613
663
|
continue;
|
|
664
|
+
}
|
|
614
665
|
const isCursor = i === this.cursorIndex;
|
|
615
666
|
const server = this.servers[item.serverIndex];
|
|
616
|
-
if (!server)
|
|
667
|
+
if (!server) {
|
|
617
668
|
continue;
|
|
669
|
+
}
|
|
618
670
|
if (item.type === "server") {
|
|
619
671
|
lines.push(row(this.renderServerRow(server, isCursor)));
|
|
620
672
|
if (isCursor && !this.authOnly && server.resourceCount > 0) {
|
|
@@ -628,8 +680,9 @@ class McpPanel {
|
|
|
628
680
|
}
|
|
629
681
|
else if (item.toolIndex !== undefined) {
|
|
630
682
|
const tool = server.tools[item.toolIndex];
|
|
631
|
-
if (tool)
|
|
683
|
+
if (tool) {
|
|
632
684
|
lines.push(row(this.renderToolRow(tool, isCursor, innerW)));
|
|
685
|
+
}
|
|
633
686
|
}
|
|
634
687
|
}
|
|
635
688
|
lines.push(emptyRow());
|
|
@@ -665,7 +718,9 @@ class McpPanel {
|
|
|
665
718
|
else {
|
|
666
719
|
const directCount = this.servers.reduce((sum, s) => sum + s.tools.filter((t) => t.isDirect).length, 0);
|
|
667
720
|
const totalTokens = this.servers.reduce((sum, s) => sum + s.tools.filter((t) => t.isDirect).reduce((ts, t) => ts + t.estimatedTokens, 0), 0);
|
|
668
|
-
const stats = directCount > 0
|
|
721
|
+
const stats = directCount > 0
|
|
722
|
+
? `${directCount} pinned ~${totalTokens.toLocaleString()} tokens`
|
|
723
|
+
: "no pinned tools";
|
|
669
724
|
lines.push(row(fg(t.description, stats + (this.dirty ? fg(t.needsAuth, " (unsaved)") : ""))));
|
|
670
725
|
lines.push(row(fg(t.hint, "Pins load at startup; discover more with mcp_search.")));
|
|
671
726
|
}
|
|
@@ -709,8 +764,9 @@ class McpPanel {
|
|
|
709
764
|
curW += needed;
|
|
710
765
|
}
|
|
711
766
|
}
|
|
712
|
-
if (curLine)
|
|
767
|
+
if (curLine) {
|
|
713
768
|
lines.push(row(fg(t.hint, curLine)));
|
|
769
|
+
}
|
|
714
770
|
lines.push(fg(t.border, "╰" + "─".repeat(innerW) + "╯"));
|
|
715
771
|
return lines;
|
|
716
772
|
}
|
|
@@ -718,7 +774,9 @@ class McpPanel {
|
|
|
718
774
|
const t = this.t;
|
|
719
775
|
const bold = (s) => `\x1b[1m${s}\x1b[22m`;
|
|
720
776
|
const expandIcon = server.expanded ? "▾" : "▸";
|
|
721
|
-
const prefix = isCursor
|
|
777
|
+
const prefix = isCursor
|
|
778
|
+
? fg(t.selected, expandIcon)
|
|
779
|
+
: fg(t.border, server.expanded ? expandIcon : "·");
|
|
722
780
|
const serverName = sanitizeDisplayText(server.name);
|
|
723
781
|
const importKind = sanitizeDisplayText(server.importKind ?? "import");
|
|
724
782
|
const nameStr = isCursor ? bold(fg(t.selected, serverName)) : serverName;
|
|
@@ -736,15 +794,18 @@ class McpPanel {
|
|
|
736
794
|
else if (directCount > 0) {
|
|
737
795
|
toggleIcon = fg(t.needsAuth, "◐");
|
|
738
796
|
}
|
|
739
|
-
const toolInfo = this.authOnly
|
|
797
|
+
const toolInfo = this.authOnly
|
|
798
|
+
? ""
|
|
799
|
+
: fg(t.description, `${totalCount} tool${totalCount === 1 ? "" : "s"}, ${directCount} pinned${server.resourceCount > 0 ? `, ${server.resourceCount} resource${server.resourceCount === 1 ? "" : "s"}` : ""}`);
|
|
740
800
|
return `${prefix} ${toggleIcon} ${nameStr}${importLabel}${statusLabel} ${toolInfo}`;
|
|
741
801
|
}
|
|
742
802
|
selectedServerHasFailureMessage() {
|
|
743
803
|
const item = this.visibleItems[this.cursorIndex];
|
|
744
|
-
if (!item)
|
|
804
|
+
if (!item) {
|
|
745
805
|
return false;
|
|
806
|
+
}
|
|
746
807
|
const server = this.servers[item.serverIndex];
|
|
747
|
-
return server?.connectionStatus === "failed" &&
|
|
808
|
+
return server?.connectionStatus === "failed" && Boolean(server.failureMessage);
|
|
748
809
|
}
|
|
749
810
|
wrapText(text, width) {
|
|
750
811
|
const max = Math.max(8, width);
|
|
@@ -760,8 +821,9 @@ class McpPanel {
|
|
|
760
821
|
take += rest.charAt(index);
|
|
761
822
|
index++;
|
|
762
823
|
}
|
|
763
|
-
if (!take)
|
|
824
|
+
if (!take) {
|
|
764
825
|
take = rest.charAt(0);
|
|
826
|
+
}
|
|
765
827
|
lines.push(take);
|
|
766
828
|
rest = rest.slice(take.length);
|
|
767
829
|
}
|
|
@@ -773,31 +835,40 @@ class McpPanel {
|
|
|
773
835
|
current = candidate;
|
|
774
836
|
}
|
|
775
837
|
else {
|
|
776
|
-
if (current)
|
|
838
|
+
if (current) {
|
|
777
839
|
lines.push(current);
|
|
840
|
+
}
|
|
778
841
|
current = splitLongWord(word);
|
|
779
842
|
}
|
|
780
843
|
}
|
|
781
|
-
if (current)
|
|
844
|
+
if (current) {
|
|
782
845
|
lines.push(current);
|
|
846
|
+
}
|
|
783
847
|
return lines.length > 0 ? lines : [text];
|
|
784
848
|
}
|
|
785
849
|
renderConnectionStatus(server) {
|
|
786
850
|
const t = this.t;
|
|
787
|
-
if (this.authInFlight === server.name)
|
|
851
|
+
if (this.authInFlight === server.name) {
|
|
788
852
|
return ` ${fg(t.needsAuth, "authenticating")}`;
|
|
789
|
-
|
|
853
|
+
}
|
|
854
|
+
if (server.connectionStatus === "disabled") {
|
|
790
855
|
return ` ${fg(t.description, "disabled")}`;
|
|
791
|
-
|
|
856
|
+
}
|
|
857
|
+
if (server.connectionStatus === "needs-auth") {
|
|
792
858
|
return ` ${fg(t.needsAuth, "needs auth")}`;
|
|
793
|
-
|
|
859
|
+
}
|
|
860
|
+
if (server.connectionStatus === "connecting") {
|
|
794
861
|
return ` ${fg(t.needsAuth, "connecting")}`;
|
|
795
|
-
|
|
862
|
+
}
|
|
863
|
+
if (server.connectionStatus === "failed") {
|
|
796
864
|
return ` ${fg(t.cancel, "failed")}`;
|
|
797
|
-
|
|
865
|
+
}
|
|
866
|
+
if (this.authOnly && server.connectionStatus === "connected") {
|
|
798
867
|
return ` ${fg(t.direct, "connected")}`;
|
|
799
|
-
|
|
868
|
+
}
|
|
869
|
+
if (this.authOnly) {
|
|
800
870
|
return ` ${fg(t.description, "idle")}`;
|
|
871
|
+
}
|
|
801
872
|
return "";
|
|
802
873
|
}
|
|
803
874
|
renderToolRow(tool, isCursor, innerW) {
|