@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/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: !!serverCache,
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").then(({ copyToClipboard }) => copyToClipboard(failureMessage)).then(() => {
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
- }).catch((error) => {
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
- if (server.connectionStatus === "connecting" || server.connectionStatus === "disabled")
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.authenticate(server.name).then((result) => {
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
- }).catch((error) => {
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.reconnect(server.name, options.afterAuth).then((connected) => {
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 = connected && server.connectionStatus === "connected"
474
- ? `OAuth finished for ${serverName}. Reconnected.`
475
- : `OAuth finished for ${serverName}, but reconnect did not complete. Press ctrl+r to retry.`;
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
- }).catch((error) => {
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.filter(tool => tool.resourceUri === undefined).map(tool => {
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
- || (Array.isArray(server.toolFilter) && server.toolFilter.includes(tool.originalName));
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, "│") + truncateToWidth(" " + sanitizeRowContent(content), innerW, "…", true) + 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)) + fg(t.title, titleText) + fg(t.border, "─".repeat(rightB) + "╮"));
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 ? "No OAuth-capable MCP servers configured." : "No MCP servers configured."))));
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 ? `${directCount} pinned ~${totalTokens.toLocaleString()} tokens` : "no pinned tools";
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 ? fg(t.selected, expandIcon) : fg(t.border, server.expanded ? expandIcon : "·");
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 ? "" : fg(t.description, `${totalCount} tool${totalCount === 1 ? "" : "s"}, ${directCount} pinned${server.resourceCount > 0 ? `, ${server.resourceCount} resource${server.resourceCount === 1 ? "" : "s"}` : ""}`);
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" && !!server.failureMessage;
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
- if (server.connectionStatus === "disabled")
853
+ }
854
+ if (server.connectionStatus === "disabled") {
790
855
  return ` ${fg(t.description, "disabled")}`;
791
- if (server.connectionStatus === "needs-auth")
856
+ }
857
+ if (server.connectionStatus === "needs-auth") {
792
858
  return ` ${fg(t.needsAuth, "needs auth")}`;
793
- if (server.connectionStatus === "connecting")
859
+ }
860
+ if (server.connectionStatus === "connecting") {
794
861
  return ` ${fg(t.needsAuth, "connecting")}`;
795
- if (server.connectionStatus === "failed")
862
+ }
863
+ if (server.connectionStatus === "failed") {
796
864
  return ` ${fg(t.cancel, "failed")}`;
797
- if (this.authOnly && server.connectionStatus === "connected")
865
+ }
866
+ if (this.authOnly && server.connectionStatus === "connected") {
798
867
  return ` ${fg(t.direct, "connected")}`;
799
- if (this.authOnly)
868
+ }
869
+ if (this.authOnly) {
800
870
  return ` ${fg(t.description, "idle")}`;
871
+ }
801
872
  return "";
802
873
  }
803
874
  renderToolRow(tool, isCursor, innerW) {