@humain/terminal 0.0.14 → 0.0.15
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 +7 -0
- package/dist/bundle/chunks/{chunk-6TW5AYGH.js → chunk-QMTWJZBT.js} +251 -39
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +30 -9
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +27 -0
- package/dist/humain/mcp-adapter/vendor/README.md +13 -13
- package/dist/humain/mcp-adapter/vendor/commands.ts +58 -21
- package/dist/humain/mcp-adapter/vendor/config.ts +18 -4
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +26 -6
- package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +4 -0
- package/dist/humain/mcp-adapter/vendor/dist/config.js +13 -4
- package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +11 -3
- package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/error-signal.ts +15 -4
- package/dist/humain/mcp-adapter/vendor/errors.ts +141 -0
- package/dist/humain/mcp-adapter/vendor/host-html-template.ts +58 -5
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1017 -160
- package/dist/humain/mcp-adapter/vendor/index.ts +2 -3
- package/dist/humain/mcp-adapter/vendor/init.ts +5 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +30 -9
- package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +66 -28
- package/dist/humain/mcp-adapter/vendor/mcp-status.ts +2 -0
- package/dist/humain/mcp-adapter/vendor/package.json +3 -2
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +66 -13
- package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +217 -0
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +337 -6
- package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +1 -0
- package/dist/humain/mcp-adapter/vendor/types.ts +18 -3
- package/dist/humain/mcp-adapter/vendor/ui-resource-handler.ts +18 -2
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +179 -54
- package/dist/humain/mcp-adapter/vendor/ui-session.ts +28 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
- package/src/humain/mcp-adapter/upstream.json +4 -4
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +27 -0
|
@@ -590,7 +590,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
590
590
|
initPromise = null;
|
|
591
591
|
if (earlyConfig.settings?.freezeDirectTools === true) {
|
|
592
592
|
directToolsFrozen = true;
|
|
593
|
-
logger.info("MCP: direct tools frozen after initial sync —
|
|
593
|
+
logger.info("MCP: direct tools frozen after initial sync — metadata can refresh without rebuilding the active tool surface");
|
|
594
594
|
}
|
|
595
595
|
}).catch(async err => {
|
|
596
596
|
if (!owner.isActive() || generation !== lifecycleGeneration) {
|
|
@@ -817,7 +817,6 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
817
817
|
case "reconnect":
|
|
818
818
|
commandOwner?.throwIfInactive();
|
|
819
819
|
await reconnectServers(state, commandCtx, targetServer);
|
|
820
|
-
if (directToolsFrozen) syncToolSurface(commandCtx);
|
|
821
820
|
break;
|
|
822
821
|
case "tools":
|
|
823
822
|
await showTools(state, commandCtx);
|
|
@@ -1168,7 +1167,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1168
1167
|
}
|
|
1169
1168
|
if (params.connect) {
|
|
1170
1169
|
const result = await executeConnect(state, params.connect, signal);
|
|
1171
|
-
syncToolSurface(_ctx as ExtensionContext);
|
|
1170
|
+
if (!directToolsFrozen) syncToolSurface(_ctx as ExtensionContext);
|
|
1172
1171
|
return result;
|
|
1173
1172
|
}
|
|
1174
1173
|
if (params.describe) {
|
|
@@ -215,6 +215,10 @@ export async function initializeMcp(
|
|
|
215
215
|
notifyToolMetadataUpdated(state, serverName, reason);
|
|
216
216
|
updateStatusBar(state);
|
|
217
217
|
});
|
|
218
|
+
manager.setListenStateChangedListener?.(() => {
|
|
219
|
+
if (!owner.isActive()) return;
|
|
220
|
+
updateStatusBar(state);
|
|
221
|
+
});
|
|
218
222
|
owner.addCleanup(() => lifecycle.gracefulShutdown());
|
|
219
223
|
owner.addCleanup(() => {
|
|
220
224
|
if (state.uiServer) {
|
|
@@ -641,6 +645,7 @@ export async function lazyConnect(state: McpExtensionState, serverName: string,
|
|
|
641
645
|
return false;
|
|
642
646
|
}
|
|
643
647
|
if (connection?.status === "connected") {
|
|
648
|
+
await state.manager.ensureListen?.(serverName, connection);
|
|
644
649
|
updateServerMetadata(state, serverName);
|
|
645
650
|
markKeepAliveAfterConnect(state, serverName);
|
|
646
651
|
return true;
|
|
@@ -134,6 +134,8 @@ interface ServerState {
|
|
|
134
134
|
includeTools?: string[];
|
|
135
135
|
excludeTools?: string[];
|
|
136
136
|
exposeResources: boolean;
|
|
137
|
+
disabled: boolean;
|
|
138
|
+
wasDisabled: boolean;
|
|
137
139
|
connectionStatus: ConnectionStatus;
|
|
138
140
|
failureMessage?: string | null;
|
|
139
141
|
tools: ToolState[];
|
|
@@ -243,6 +245,7 @@ class McpPanel {
|
|
|
243
245
|
|
|
244
246
|
const status = callbacks.getConnectionStatus(serverName);
|
|
245
247
|
const failureMessage = callbacks.getFailureMessage?.(serverName) ?? null;
|
|
248
|
+
const serverDisabled = isServerDisabled(definition);
|
|
246
249
|
let directCount = 0;
|
|
247
250
|
let directTokens = 0;
|
|
248
251
|
for (const tool of tools) {
|
|
@@ -258,6 +261,8 @@ class McpPanel {
|
|
|
258
261
|
...(definition.includeTools !== undefined ? { includeTools: definition.includeTools } : {}),
|
|
259
262
|
...(definition.excludeTools !== undefined ? { excludeTools: definition.excludeTools } : {}),
|
|
260
263
|
exposeResources: definition.exposeResources !== false,
|
|
264
|
+
disabled: serverDisabled,
|
|
265
|
+
wasDisabled: serverDisabled,
|
|
261
266
|
connectionStatus: status,
|
|
262
267
|
failureMessage,
|
|
263
268
|
tools,
|
|
@@ -275,7 +280,7 @@ class McpPanel {
|
|
|
275
280
|
if (this.inactivityTimeout) clearTimeout(this.inactivityTimeout);
|
|
276
281
|
this.inactivityTimeout = setTimeout(() => {
|
|
277
282
|
this.cleanup();
|
|
278
|
-
this.done({ cancelled: true, changes: new Map() });
|
|
283
|
+
this.done({ cancelled: true, changes: new Map(), disabledChanges: new Map() });
|
|
279
284
|
}, McpPanel.INACTIVITY_MS);
|
|
280
285
|
}
|
|
281
286
|
|
|
@@ -334,12 +339,16 @@ class McpPanel {
|
|
|
334
339
|
}
|
|
335
340
|
|
|
336
341
|
private updateDirty(): void {
|
|
337
|
-
this.dirty = this.servers.some((s) => s.tools.some((t) => t.isDirect !== t.wasDirect));
|
|
342
|
+
this.dirty = this.servers.some((s) => s.disabled !== s.wasDisabled || s.tools.some((t) => t.isDirect !== t.wasDirect));
|
|
338
343
|
}
|
|
339
344
|
|
|
340
345
|
private buildResult(): McpPanelResult {
|
|
341
346
|
const changes = new Map<string, true | string[] | false>();
|
|
347
|
+
const disabledChanges = new Map<string, boolean>();
|
|
342
348
|
for (const server of this.servers) {
|
|
349
|
+
if (server.disabled !== server.wasDisabled) {
|
|
350
|
+
disabledChanges.set(server.name, server.disabled);
|
|
351
|
+
}
|
|
343
352
|
const changed = server.tools.some((t) => t.isDirect !== t.wasDirect);
|
|
344
353
|
if (!changed) continue;
|
|
345
354
|
const directTools = server.tools.filter((t) => t.isDirect);
|
|
@@ -351,7 +360,7 @@ class McpPanel {
|
|
|
351
360
|
changes.set(server.name, directTools.map((t) => t.name));
|
|
352
361
|
}
|
|
353
362
|
}
|
|
354
|
-
return { changes, cancelled: false };
|
|
363
|
+
return { changes, disabledChanges, cancelled: false };
|
|
355
364
|
}
|
|
356
365
|
|
|
357
366
|
handleInput(data: string): void {
|
|
@@ -367,7 +376,7 @@ class McpPanel {
|
|
|
367
376
|
// Global shortcuts — always work, even during desc search
|
|
368
377
|
if (matchesKey(data, "ctrl+c")) {
|
|
369
378
|
this.cleanup();
|
|
370
|
-
this.done({ cancelled: true, changes: new Map() });
|
|
379
|
+
this.done({ cancelled: true, changes: new Map(), disabledChanges: new Map() });
|
|
371
380
|
return;
|
|
372
381
|
}
|
|
373
382
|
|
|
@@ -424,7 +433,7 @@ class McpPanel {
|
|
|
424
433
|
return;
|
|
425
434
|
}
|
|
426
435
|
this.cleanup();
|
|
427
|
-
this.done({ cancelled: true, changes: new Map() });
|
|
436
|
+
this.done({ cancelled: true, changes: new Map(), disabledChanges: new Map() });
|
|
428
437
|
return;
|
|
429
438
|
}
|
|
430
439
|
|
|
@@ -477,6 +486,17 @@ class McpPanel {
|
|
|
477
486
|
return;
|
|
478
487
|
}
|
|
479
488
|
|
|
489
|
+
if (matchesKey(data, "ctrl+d")) {
|
|
490
|
+
const item = this.visibleItems[this.cursorIndex];
|
|
491
|
+
if (!item || item.type !== "server" || this.authOnly) return;
|
|
492
|
+
const server = this.servers[item.serverIndex];
|
|
493
|
+
if (!server) return;
|
|
494
|
+
server.disabled = !server.disabled;
|
|
495
|
+
this.updateDirty();
|
|
496
|
+
this.tui.requestRender();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
480
500
|
if (matchesKey(data, "ctrl+y")) {
|
|
481
501
|
const item = this.visibleItems[this.cursorIndex];
|
|
482
502
|
if (!item) return;
|
|
@@ -632,7 +652,7 @@ class McpPanel {
|
|
|
632
652
|
private handleDiscardInput(data: string): void {
|
|
633
653
|
if (matchesKey(data, "ctrl+c")) {
|
|
634
654
|
this.cleanup();
|
|
635
|
-
this.done({ cancelled: true, changes: new Map() });
|
|
655
|
+
this.done({ cancelled: true, changes: new Map(), disabledChanges: new Map() });
|
|
636
656
|
return;
|
|
637
657
|
}
|
|
638
658
|
if (matchesKey(data, "escape") || data === "n" || data === "N") {
|
|
@@ -642,7 +662,7 @@ class McpPanel {
|
|
|
642
662
|
if (this.keys.selectConfirm(data)) {
|
|
643
663
|
this.cleanup();
|
|
644
664
|
if (this.discardSelected === 0) {
|
|
645
|
-
this.done({ cancelled: true, changes: new Map() });
|
|
665
|
+
this.done({ cancelled: true, changes: new Map(), disabledChanges: new Map() });
|
|
646
666
|
} else {
|
|
647
667
|
this.done(this.buildResult());
|
|
648
668
|
}
|
|
@@ -650,7 +670,7 @@ class McpPanel {
|
|
|
650
670
|
}
|
|
651
671
|
if (data === "y" || data === "Y") {
|
|
652
672
|
this.cleanup();
|
|
653
|
-
this.done({ cancelled: true, changes: new Map() });
|
|
673
|
+
this.done({ cancelled: true, changes: new Map(), disabledChanges: new Map() });
|
|
654
674
|
return;
|
|
655
675
|
}
|
|
656
676
|
if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "tab")) {
|
|
@@ -883,6 +903,7 @@ class McpPanel {
|
|
|
883
903
|
italic("⏎") + " expand/auth",
|
|
884
904
|
italic("ctrl+a") + " auth",
|
|
885
905
|
italic("ctrl+r") + " reconnect",
|
|
906
|
+
italic("ctrl+d") + " disable/enable",
|
|
886
907
|
...(this.selectedServerHasFailureMessage() ? [italic("ctrl+y") + " copy error"] : []),
|
|
887
908
|
italic("?") + " desc search",
|
|
888
909
|
...(saveLabel ? [italic(saveLabel) + " save"] : []),
|
|
@@ -995,7 +1016,7 @@ class McpPanel {
|
|
|
995
1016
|
private renderConnectionStatus(server: ServerState): string {
|
|
996
1017
|
const t = this.t;
|
|
997
1018
|
if (this.authInFlight === server.name) return ` ${fg(t.needsAuth, "authenticating")}`;
|
|
998
|
-
if (server.
|
|
1019
|
+
if (server.disabled) return ` ${fg(t.description, "disabled")}`;
|
|
999
1020
|
if (server.connectionStatus === "needs-auth") return ` ${fg(t.needsAuth, "needs auth")}`;
|
|
1000
1021
|
if (server.connectionStatus === "connecting") return ` ${fg(t.needsAuth, "connecting")}`;
|
|
1001
1022
|
if (server.connectionStatus === "failed") return ` ${fg(t.cancel, "failed")}`;
|
|
@@ -2,7 +2,7 @@ import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tu
|
|
|
2
2
|
import { createPanelKeys, type PanelKeybindings, type PanelKeys } from "./panel-keys.ts";
|
|
3
3
|
import type { ImportKind } from "./types.ts";
|
|
4
4
|
import { getConfigDirName } from "./agent-dir.ts";
|
|
5
|
-
import { KNOWN_SERVER_PRESETS, type ConfigWritePreview, type KnownServerPreset, type McpDiscoverySummary } from "./config.ts";
|
|
5
|
+
import { KNOWN_SERVER_PRESETS, type ConfigWritePreview, type KnownServerPreset, type McpDiscoverySummary, type SharedConfigTarget } from "./config.ts";
|
|
6
6
|
import type { McpOnboardingState } from "./onboarding-state.ts";
|
|
7
7
|
|
|
8
8
|
interface SetupTheme {
|
|
@@ -54,13 +54,13 @@ function wrapText(text: string, width: number): string[] {
|
|
|
54
54
|
|
|
55
55
|
export interface SetupPanelCallbacks {
|
|
56
56
|
previewImports: (imports: ImportKind[]) => ConfigWritePreview;
|
|
57
|
-
|
|
58
|
-
previewRepoPrompt: () => ConfigWritePreview | null;
|
|
59
|
-
previewKnownServer: (preset: KnownServerPreset) => ConfigWritePreview;
|
|
57
|
+
previewStarterConfig: (target: SharedConfigTarget) => ConfigWritePreview;
|
|
58
|
+
previewRepoPrompt: (target: SharedConfigTarget) => ConfigWritePreview | null;
|
|
59
|
+
previewKnownServer: (preset: KnownServerPreset, target: SharedConfigTarget) => ConfigWritePreview;
|
|
60
60
|
adoptImports: (imports: ImportKind[]) => Promise<{ added: ImportKind[]; path: string }>;
|
|
61
|
-
|
|
62
|
-
addRepoPrompt: () => Promise<{ path: string; serverName: string }>;
|
|
63
|
-
addKnownServer: (preset: KnownServerPreset) => Promise<{ path: string; serverName: string }>;
|
|
61
|
+
scaffoldConfig: (target: SharedConfigTarget) => Promise<{ path: string }>;
|
|
62
|
+
addRepoPrompt: (target: SharedConfigTarget) => Promise<{ path: string; serverName: string }>;
|
|
63
|
+
addKnownServer: (preset: KnownServerPreset, target: SharedConfigTarget) => Promise<{ path: string; serverName: string }>;
|
|
64
64
|
openPath: (path: string) => Promise<void>;
|
|
65
65
|
markSetupCompleted: () => void;
|
|
66
66
|
}
|
|
@@ -75,13 +75,14 @@ type Screen = "empty" | "setup" | "imports" | "paths";
|
|
|
75
75
|
|
|
76
76
|
type ActionId =
|
|
77
77
|
| "run-setup"
|
|
78
|
+
| "select-shared-target"
|
|
78
79
|
| "adopt-imports"
|
|
79
80
|
| "view-example"
|
|
80
81
|
| "show-precedence"
|
|
81
82
|
| "open-paths"
|
|
82
83
|
| "add-repoprompt"
|
|
83
84
|
| "add-known-server"
|
|
84
|
-
| "scaffold-
|
|
85
|
+
| "scaffold-shared-config"
|
|
85
86
|
| "close";
|
|
86
87
|
|
|
87
88
|
interface Action {
|
|
@@ -89,6 +90,7 @@ interface Action {
|
|
|
89
90
|
label: string;
|
|
90
91
|
description: string;
|
|
91
92
|
preset?: KnownServerPreset;
|
|
93
|
+
target?: SharedConfigTarget;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
export class McpSetupPanel {
|
|
@@ -96,6 +98,7 @@ export class McpSetupPanel {
|
|
|
96
98
|
private actionCursor = 0;
|
|
97
99
|
private importCursor = 0;
|
|
98
100
|
private pathCursor = 0;
|
|
101
|
+
private sharedConfigTarget: SharedConfigTarget = "project";
|
|
99
102
|
private selectedImports = new Set<ImportKind>();
|
|
100
103
|
private busy = false;
|
|
101
104
|
private notice: { text: string; tone: "success" | "warning" | "muted" } | null = null;
|
|
@@ -144,9 +147,13 @@ export class McpSetupPanel {
|
|
|
144
147
|
if (this.discovery.imports.length > 0) {
|
|
145
148
|
actions.push({ id: "adopt-imports", label: "Adopt detected compatibility imports", description: `Choose which host-specific MCP configs Pi should import into its own override file. ${this.discovery.imports.length} source${this.discovery.imports.length === 1 ? "" : "s"} found.` });
|
|
146
149
|
}
|
|
147
|
-
actions.push(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
actions.push(
|
|
151
|
+
{ id: "select-shared-target", label: `${this.sharedConfigTarget === "project" ? "●" : "○"} Add to this project (.mcp.json)`, description: "Write new shared MCP servers to the project/team config.", target: "project" },
|
|
152
|
+
{ id: "select-shared-target", label: `${this.sharedConfigTarget === "global" ? "●" : "○"} Add globally (~/.config/mcp/mcp.json)`, description: "Write new shared MCP servers to your all-projects config.", target: "global" },
|
|
153
|
+
);
|
|
154
|
+
actions.push({ id: "view-example", label: "View example shared config", description: "Preview a working shared MCP config you can paste or adapt." });
|
|
155
|
+
if (!this.selectedSharedConfigExists()) {
|
|
156
|
+
actions.push({ id: "scaffold-shared-config", label: `Scaffold ${this.sharedTargetLabel()}`, description: "Write a minimal config at the selected normal MCP setup path, then reload Pi." });
|
|
150
157
|
}
|
|
151
158
|
actions.push({ id: "show-precedence", label: "Explain config precedence", description: "Show the read order and where Pi writes compatibility settings." });
|
|
152
159
|
if (this.getDetectedPaths().length > 0) {
|
|
@@ -156,7 +163,7 @@ export class McpSetupPanel {
|
|
|
156
163
|
actions.push({ id: "add-known-server", label: preset.name, description: preset.summary, preset });
|
|
157
164
|
}
|
|
158
165
|
if (!this.discovery.repoPrompt.configured && this.discovery.repoPrompt.executablePath && this.discovery.repoPrompt.targetPath && this.discovery.repoPrompt.entry && this.discovery.repoPrompt.serverName) {
|
|
159
|
-
actions.push({ id: "add-repoprompt", label: "Add RepoPrompt to shared
|
|
166
|
+
actions.push({ id: "add-repoprompt", label: "Add RepoPrompt to selected shared config", description: "Write a standard MCP entry for RepoPrompt to the selected normal setup path, then reload MCP in-session." });
|
|
160
167
|
}
|
|
161
168
|
actions.push({ id: "close", label: "Close", description: "Exit the onboarding flow." });
|
|
162
169
|
return actions;
|
|
@@ -170,6 +177,15 @@ export class McpSetupPanel {
|
|
|
170
177
|
return [...new Set(paths)];
|
|
171
178
|
}
|
|
172
179
|
|
|
180
|
+
private sharedTargetLabel(): string {
|
|
181
|
+
return this.sharedConfigTarget === "project" ? "project .mcp.json" : "global ~/.config/mcp/mcp.json";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private selectedSharedConfigExists(): boolean {
|
|
185
|
+
const sourceId = this.sharedConfigTarget === "project" ? "shared-project" : "shared-global";
|
|
186
|
+
return this.discovery.sources.some((source) => source.id === sourceId && source.exists);
|
|
187
|
+
}
|
|
188
|
+
|
|
173
189
|
private getSelectedAction(): Action | undefined {
|
|
174
190
|
const actions = this.getActions();
|
|
175
191
|
return actions[this.actionCursor];
|
|
@@ -293,9 +309,15 @@ export class McpSetupPanel {
|
|
|
293
309
|
this.tui.requestRender();
|
|
294
310
|
return;
|
|
295
311
|
}
|
|
296
|
-
if (action.id === "
|
|
312
|
+
if (action.id === "select-shared-target" && action.target) {
|
|
313
|
+
this.sharedConfigTarget = action.target;
|
|
314
|
+
this.notice = { text: `New shared servers will be written to ${this.sharedTargetLabel()}.`, tone: "muted" };
|
|
315
|
+
this.tui.requestRender();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (action.id === "scaffold-shared-config") {
|
|
297
319
|
await this.runBusy(async () => {
|
|
298
|
-
const result = await this.callbacks.
|
|
320
|
+
const result = await this.callbacks.scaffoldConfig(this.sharedConfigTarget);
|
|
299
321
|
this.callbacks.markSetupCompleted();
|
|
300
322
|
this.notice = { text: `Wrote starter config to ${result.path}. Pi will reload after this panel closes.`, tone: "success" };
|
|
301
323
|
});
|
|
@@ -303,7 +325,7 @@ export class McpSetupPanel {
|
|
|
303
325
|
}
|
|
304
326
|
if (action.id === "add-repoprompt") {
|
|
305
327
|
await this.runBusy(async () => {
|
|
306
|
-
const result = await this.callbacks.addRepoPrompt();
|
|
328
|
+
const result = await this.callbacks.addRepoPrompt(this.sharedConfigTarget);
|
|
307
329
|
this.callbacks.markSetupCompleted();
|
|
308
330
|
this.notice = { text: `Added ${result.serverName} to ${result.path}. Pi will reload after this panel closes.`, tone: "success" };
|
|
309
331
|
});
|
|
@@ -312,7 +334,7 @@ export class McpSetupPanel {
|
|
|
312
334
|
if (action.id === "add-known-server" && action.preset) {
|
|
313
335
|
const preset = action.preset;
|
|
314
336
|
await this.runBusy(async () => {
|
|
315
|
-
const result = await this.callbacks.addKnownServer(preset);
|
|
337
|
+
const result = await this.callbacks.addKnownServer(preset, this.sharedConfigTarget);
|
|
316
338
|
this.callbacks.markSetupCompleted();
|
|
317
339
|
this.notice = { text: `Added ${result.serverName} to ${result.path}. Pi will reload after this panel closes.`, tone: "success" };
|
|
318
340
|
});
|
|
@@ -416,8 +438,11 @@ export class McpSetupPanel {
|
|
|
416
438
|
for (let index = start; index < end; index++) {
|
|
417
439
|
const action = actions[index];
|
|
418
440
|
if (!action) continue;
|
|
441
|
+
if (action.id === "select-shared-target" && (index === start || actions[index - 1]?.id !== "select-shared-target")) {
|
|
442
|
+
lines.push(this.padLine(fg(this.t.title, "Choose where new shared servers go"), innerW));
|
|
443
|
+
}
|
|
419
444
|
if (action.id === "add-known-server" && (index === start || actions[index - 1]?.id !== "add-known-server")) {
|
|
420
|
-
lines.push(this.padLine(fg(this.t.title,
|
|
445
|
+
lines.push(this.padLine(fg(this.t.title, `Add a known server to ${this.sharedTargetLabel()}`), innerW));
|
|
421
446
|
}
|
|
422
447
|
const selected = index === this.actionCursor;
|
|
423
448
|
const cursor = selected ? fg(this.t.selected, "›") : " ";
|
|
@@ -495,12 +520,12 @@ export class McpSetupPanel {
|
|
|
495
520
|
? ` ${this.discovery.conflicts.length} same-name conflict${this.discovery.conflicts.length === 1 ? "" : "s"} reported.`
|
|
496
521
|
: "";
|
|
497
522
|
if (!this.discovery.hasAnyConfig) {
|
|
498
|
-
return `
|
|
523
|
+
return `Add shared servers to .mcp.json for this project/team or ~/.config/mcp/mcp.json for all projects. Adopt host imports or quick-add RepoPrompt from this screen.${hostNote}${conflictNote}`;
|
|
499
524
|
}
|
|
500
525
|
if (this.discovery.totalServerCount === 0 && this.discovery.imports.length > 0) {
|
|
501
526
|
return `Detected ${this.discovery.imports.length} compatibility import source${this.discovery.imports.length === 1 ? "" : "s"}. Adopt them into Pi or inspect the underlying files.${hostNote}${conflictNote}`;
|
|
502
527
|
}
|
|
503
|
-
return `
|
|
528
|
+
return `Use .mcp.json for project/team servers or ~/.config/mcp/mcp.json for all projects. Pi-owned files are for compatibility imports and adapter-specific overrides, not another normal setup path.${hostNote}${conflictNote}`;
|
|
504
529
|
}
|
|
505
530
|
|
|
506
531
|
private visibleActionRange(total: number): { start: number; end: number } {
|
|
@@ -534,6 +559,12 @@ export class McpSetupPanel {
|
|
|
534
559
|
],
|
|
535
560
|
previewW,
|
|
536
561
|
);
|
|
562
|
+
case "select-shared-target":
|
|
563
|
+
return this.formatPreview([
|
|
564
|
+
action.target === "project" ? "Project target: .mcp.json" : "Global target: ~/.config/mcp/mcp.json",
|
|
565
|
+
"Known server presets and starter configs will be written to the selected normal MCP setup path.",
|
|
566
|
+
"Pi-owned mcp.json files remain compatibility and adapter-only override state.",
|
|
567
|
+
], previewW);
|
|
537
568
|
case "view-example":
|
|
538
569
|
return this.formatPreview([
|
|
539
570
|
"Example shared `.mcp.json`:",
|
|
@@ -546,10 +577,17 @@ export class McpSetupPanel {
|
|
|
546
577
|
" }",
|
|
547
578
|
"}",
|
|
548
579
|
"",
|
|
549
|
-
"Use Scaffold
|
|
580
|
+
"Use Scaffold selected config when you want a safe empty shell instead of a live example server.",
|
|
550
581
|
], previewW);
|
|
551
582
|
case "show-precedence":
|
|
552
583
|
return this.formatPreview([
|
|
584
|
+
"Recommended shared config:",
|
|
585
|
+
" project/team: .mcp.json",
|
|
586
|
+
" all projects: ~/.config/mcp/mcp.json",
|
|
587
|
+
"",
|
|
588
|
+
"Advanced compatibility and Pi-owned layers:",
|
|
589
|
+
" host imports, .agents files, package MCP manifests, and Pi overrides",
|
|
590
|
+
"",
|
|
553
591
|
"Read order (later entries win):",
|
|
554
592
|
"0. detected host configs (opt-in lowest-precedence fallback)",
|
|
555
593
|
"1. ~/.config/mcp/mcp.json",
|
|
@@ -570,7 +608,7 @@ export class McpSetupPanel {
|
|
|
570
608
|
: ["No config paths were detected."], previewW);
|
|
571
609
|
case "add-repoprompt": {
|
|
572
610
|
const repoPrompt = this.discovery.repoPrompt;
|
|
573
|
-
const preview = this.callbacks.previewRepoPrompt();
|
|
611
|
+
const preview = this.callbacks.previewRepoPrompt(this.sharedConfigTarget);
|
|
574
612
|
if (!preview) {
|
|
575
613
|
return this.formatPreview(["RepoPrompt is not available to add from this setup screen."], previewW);
|
|
576
614
|
}
|
|
@@ -579,7 +617,7 @@ export class McpSetupPanel {
|
|
|
579
617
|
preview,
|
|
580
618
|
[
|
|
581
619
|
`Executable: ${repoPrompt.executablePath ?? "not found"}`,
|
|
582
|
-
`Target: ${
|
|
620
|
+
`Target: ${this.sharedTargetLabel()}`,
|
|
583
621
|
`Server name: ${repoPrompt.serverName ?? "repoprompt"}`,
|
|
584
622
|
],
|
|
585
623
|
previewW,
|
|
@@ -590,17 +628,17 @@ export class McpSetupPanel {
|
|
|
590
628
|
if (!preset) return this.formatPreview(["Known server preset is unavailable."], previewW);
|
|
591
629
|
return this.formatWritePreview(
|
|
592
630
|
`${preset.name} write preview`,
|
|
593
|
-
this.callbacks.previewKnownServer(preset),
|
|
594
|
-
[preset.summary],
|
|
631
|
+
this.callbacks.previewKnownServer(preset, this.sharedConfigTarget),
|
|
632
|
+
[preset.summary, `Target: ${this.sharedTargetLabel()}`],
|
|
595
633
|
previewW,
|
|
596
634
|
);
|
|
597
635
|
}
|
|
598
|
-
case "scaffold-
|
|
636
|
+
case "scaffold-shared-config":
|
|
599
637
|
return this.formatWritePreview(
|
|
600
|
-
|
|
601
|
-
this.callbacks.
|
|
638
|
+
`${this.sharedTargetLabel()} starter write preview`,
|
|
639
|
+
this.callbacks.previewStarterConfig(this.sharedConfigTarget),
|
|
602
640
|
[
|
|
603
|
-
"This writes a minimal
|
|
641
|
+
"This writes a minimal config at the selected normal MCP setup path.",
|
|
604
642
|
"It intentionally avoids adding a fake placeholder server that would fail on first reload.",
|
|
605
643
|
],
|
|
606
644
|
previewW,
|
|
@@ -48,7 +48,9 @@ export function createMcpStatusSnapshot(state: McpExtensionState): McpStatusSnap
|
|
|
48
48
|
servers.push({
|
|
49
49
|
name,
|
|
50
50
|
status,
|
|
51
|
+
listenState: connection?.status === "connected" ? connection.listenState : "disconnected",
|
|
51
52
|
toolCount,
|
|
53
|
+
...(connection?.status === "connected" && connection.listenCatalogStale ? { catalogStale: true } : {}),
|
|
52
54
|
...(resourceCount !== undefined ? { resourceCount } : {}),
|
|
53
55
|
...(status === "failed" && failedAgoSeconds !== null ? { failedAgoSeconds } : {}),
|
|
54
56
|
disabled,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-adapter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.32.1",
|
|
4
4
|
"description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -125,6 +125,7 @@
|
|
|
125
125
|
"mcp-status.ts",
|
|
126
126
|
"metadata-cache.ts",
|
|
127
127
|
"host-html-template.ts",
|
|
128
|
+
"sandbox-proxy-template.ts",
|
|
128
129
|
"ui-resource-handler.ts",
|
|
129
130
|
"consent-manager.ts",
|
|
130
131
|
"ui-server.ts",
|
|
@@ -166,7 +167,7 @@
|
|
|
166
167
|
"zod": "^3.25.0 || ^4.0.0"
|
|
167
168
|
},
|
|
168
169
|
"peerDependencies": {
|
|
169
|
-
"@earendil-works/pi-ai": "^0.84.
|
|
170
|
+
"@earendil-works/pi-ai": "^0.84.1",
|
|
170
171
|
"@earendil-works/pi-tui": "*",
|
|
171
172
|
"typebox": "*",
|
|
172
173
|
"zod": "^3.25.0 || ^4.0.0"
|
|
@@ -19,6 +19,7 @@ import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session
|
|
|
19
19
|
import { paginate, rankSuggestions, rankToolMatches, resolveSearchKeywords } from "./search-ranking.ts";
|
|
20
20
|
import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.ts";
|
|
21
21
|
import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
|
|
22
|
+
import { getInputRequiredNeedsUiDetails } from "./errors.ts";
|
|
22
23
|
|
|
23
24
|
type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
|
|
24
25
|
type ClientCallToolResult = Awaited<ReturnType<Client["callTool"]>>;
|
|
@@ -339,7 +340,7 @@ export function executeUiMessages(state: McpExtensionState): ProxyToolResult {
|
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
342
|
-
const servers: Array<{ name: string; status: string; toolCount: number; failedAgo: number | null; disabled?: boolean }> = [];
|
|
343
|
+
const servers: Array<{ name: string; status: string; listenState: string; catalogStale?: boolean; toolCount: number; failedAgo: number | null; disabled?: boolean }> = [];
|
|
343
344
|
|
|
344
345
|
for (const name of Object.keys(state.config.mcpServers)) {
|
|
345
346
|
const definition = state.config.mcpServers[name];
|
|
@@ -360,7 +361,16 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
360
361
|
|
|
361
362
|
const toolCount = status === "failed" ? 0 : metadata?.length ?? 0;
|
|
362
363
|
|
|
363
|
-
|
|
364
|
+
const listenState = connection?.status === "connected" ? connection.listenState : "disconnected";
|
|
365
|
+
servers.push({
|
|
366
|
+
name,
|
|
367
|
+
status,
|
|
368
|
+
listenState,
|
|
369
|
+
...(connection?.status === "connected" && connection.listenCatalogStale ? { catalogStale: true } : {}),
|
|
370
|
+
toolCount,
|
|
371
|
+
failedAgo,
|
|
372
|
+
...(disabled ? { disabled: true } : {}),
|
|
373
|
+
});
|
|
364
374
|
}
|
|
365
375
|
|
|
366
376
|
const disabledCount = servers.filter(s => s.disabled).length;
|
|
@@ -377,7 +387,18 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
377
387
|
continue;
|
|
378
388
|
}
|
|
379
389
|
if (server.status === "connected") {
|
|
380
|
-
|
|
390
|
+
const listen = server.listenState === "active"
|
|
391
|
+
? server.catalogStale
|
|
392
|
+
? ", listen active, catalog may be stale"
|
|
393
|
+
: ", listen active"
|
|
394
|
+
: server.listenState === "dropped"
|
|
395
|
+
? ", catalog may be stale; will reconcile on next keep-alive or tool use"
|
|
396
|
+
: server.listenState === "re-establishing"
|
|
397
|
+
? ", re-establishing listen"
|
|
398
|
+
: server.listenState === "legacy"
|
|
399
|
+
? ", legacy notification path"
|
|
400
|
+
: ", not listening for catalog updates";
|
|
401
|
+
text += `✓ ${server.name} (${server.toolCount} tools${listen})\n`;
|
|
381
402
|
continue;
|
|
382
403
|
}
|
|
383
404
|
if (server.status === "needs-auth") {
|
|
@@ -385,14 +406,19 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
385
406
|
continue;
|
|
386
407
|
}
|
|
387
408
|
if (server.status === "cached") {
|
|
388
|
-
text += `○ ${server.name} (${server.toolCount} tools, cached)\n`;
|
|
409
|
+
text += `○ ${server.name} (${server.toolCount} tools, cached; not listening)\n`;
|
|
389
410
|
continue;
|
|
390
411
|
}
|
|
391
412
|
if (server.status === "failed") {
|
|
392
413
|
text += `✗ ${server.name} (failed ${server.failedAgo ?? 0}s ago)\n`;
|
|
393
414
|
continue;
|
|
394
415
|
}
|
|
395
|
-
text += `○ ${server.name} (not
|
|
416
|
+
text += `○ ${server.name} (not listening; disconnected)\n`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const directToolsFrozen = state.config.settings?.freezeDirectTools === true;
|
|
420
|
+
if (directToolsFrozen) {
|
|
421
|
+
text += "\nDirect tools frozen; active registrations may differ from current metadata.\n";
|
|
396
422
|
}
|
|
397
423
|
|
|
398
424
|
if (servers.length > 0) {
|
|
@@ -401,7 +427,7 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
401
427
|
|
|
402
428
|
return {
|
|
403
429
|
content: [{ type: "text" as const, text: text.trim() }],
|
|
404
|
-
details: { mode: "status", servers, totalTools, connectedCount, disabledCount },
|
|
430
|
+
details: { mode: "status", servers, totalTools, connectedCount, disabledCount, directToolsFrozen },
|
|
405
431
|
};
|
|
406
432
|
}
|
|
407
433
|
|
|
@@ -751,7 +777,17 @@ export function executeList(state: McpExtensionState, server: string): ProxyTool
|
|
|
751
777
|
};
|
|
752
778
|
}
|
|
753
779
|
|
|
754
|
-
|
|
780
|
+
// "not connected" alone reads as an outage while the tools listed below are
|
|
781
|
+
// real and the connection is simply lazy (tools served from cache).
|
|
782
|
+
// Distinguish auth from plain laziness so the caller knows which remedy applies.
|
|
783
|
+
let cachedNote = "";
|
|
784
|
+
if (connection?.status !== "connected") {
|
|
785
|
+
if (connection?.status === "needs-auth") {
|
|
786
|
+
cachedNote = ` (needs auth — run mcp({ action: "auth-start", server: "${server}" }))`;
|
|
787
|
+
} else {
|
|
788
|
+
cachedNote = ` (lazy: tools from cache, not connected yet — mcp({ connect: "${server}" }) to connect)`;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
755
791
|
let text = `${server} (${toolNames.length} tools${cachedNote}):\n\n`;
|
|
756
792
|
|
|
757
793
|
const descMap = new Map<string, string>();
|
|
@@ -1300,7 +1336,13 @@ export async function executeCall(
|
|
|
1300
1336
|
onNeedsAuth: recoverAuthConnection,
|
|
1301
1337
|
},
|
|
1302
1338
|
serverName,
|
|
1303
|
-
(conn) =>
|
|
1339
|
+
async (conn) => {
|
|
1340
|
+
const refreshRead = await state.manager.prepareResourceUse?.(serverName, toolMeta.resourceUri!, conn);
|
|
1341
|
+
return conn.client.readResource(
|
|
1342
|
+
{ uri: toolMeta.resourceUri! },
|
|
1343
|
+
refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions,
|
|
1344
|
+
);
|
|
1345
|
+
},
|
|
1304
1346
|
);
|
|
1305
1347
|
const content = transformMcpResourceContents(result.contents ?? [], state.owner?.signal);
|
|
1306
1348
|
const guarded = await guardMcpOutput(content.length > 0 ? content : [{ type: "text" as const, text: "(empty resource)" }], outputGuardOptions);
|
|
@@ -1330,11 +1372,14 @@ export async function executeCall(
|
|
|
1330
1372
|
onNeedsAuth: recoverAuthConnection,
|
|
1331
1373
|
},
|
|
1332
1374
|
serverName,
|
|
1333
|
-
(conn) =>
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1375
|
+
async (conn) => {
|
|
1376
|
+
await state.manager.ensureListen?.(serverName, conn);
|
|
1377
|
+
return abortable(conn.client.callTool({
|
|
1378
|
+
name: toolMeta.originalName,
|
|
1379
|
+
arguments: normalizedArgs,
|
|
1380
|
+
_meta: uiSession?.requestMeta,
|
|
1381
|
+
}, requestOptions), ownedSignal);
|
|
1382
|
+
},
|
|
1338
1383
|
);
|
|
1339
1384
|
|
|
1340
1385
|
if (toolMeta.uiResourceUri) {
|
|
@@ -1408,6 +1453,14 @@ export async function executeCall(
|
|
|
1408
1453
|
details: { mode: "call", error: "url_elicitation_required", ...callIdentity, action },
|
|
1409
1454
|
};
|
|
1410
1455
|
}
|
|
1456
|
+
const inputRequired = getInputRequiredNeedsUiDetails(error, callIdentity);
|
|
1457
|
+
if (inputRequired) {
|
|
1458
|
+
uiSession?.sendToolCancelled(inputRequired.message);
|
|
1459
|
+
return {
|
|
1460
|
+
content: [{ type: "text" as const, text: inputRequired.message }],
|
|
1461
|
+
details: { mode: "call", ...inputRequired },
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1411
1464
|
const message = error instanceof Error ? error.message : String(error);
|
|
1412
1465
|
uiSession?.sendToolCancelled(message);
|
|
1413
1466
|
|