@humain/terminal 0.0.13 → 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 +9 -0
- package/dist/bundle/chunks/{chunk-V5XRWLQF.js → chunk-QMTWJZBT.js} +252 -40
- 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/agent-execution-runtime.d.ts.map +1 -1
- package/dist/humain/agent-execution-runtime.js +3 -1
- package/dist/humain/agent-execution-runtime.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
|
@@ -1106,12 +1106,18 @@ import stripJsonComments2 from "strip-json-comments";
|
|
|
1106
1106
|
function getPiGlobalConfigPath(overridePath) {
|
|
1107
1107
|
return overridePath ? resolve4(overridePath) : getAgentPath("mcp.json");
|
|
1108
1108
|
}
|
|
1109
|
+
function getGenericGlobalConfigPath() {
|
|
1110
|
+
return GENERIC_GLOBAL_CONFIG_PATH;
|
|
1111
|
+
}
|
|
1109
1112
|
function getProjectConfigPath(cwd = process.cwd()) {
|
|
1110
1113
|
return resolve4(cwd, PROJECT_CONFIG_NAME);
|
|
1111
1114
|
}
|
|
1112
1115
|
function getProjectPiConfigPath(cwd = process.cwd()) {
|
|
1113
1116
|
return resolve4(cwd, getConfigDirName(), PROJECT_PI_CONFIG_NAME);
|
|
1114
1117
|
}
|
|
1118
|
+
function getSharedConfigPath(target, cwd = process.cwd()) {
|
|
1119
|
+
return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
|
|
1120
|
+
}
|
|
1115
1121
|
function getConfigSourceSummaries(sourceSpecs) {
|
|
1116
1122
|
return sourceSpecs.map((source) => {
|
|
1117
1123
|
const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
|
|
@@ -1897,13 +1903,13 @@ function buildStarterProjectConfig() {
|
|
|
1897
1903
|
mcpServers: {}
|
|
1898
1904
|
};
|
|
1899
1905
|
}
|
|
1900
|
-
function
|
|
1901
|
-
const targetPath =
|
|
1906
|
+
function previewStarterSharedConfig(target, cwd = process.cwd()) {
|
|
1907
|
+
const targetPath = getSharedConfigPath(target, cwd);
|
|
1902
1908
|
const nextRaw = { mcpServers: buildStarterProjectConfig().mcpServers };
|
|
1903
1909
|
return buildConfigWritePreview(targetPath, nextRaw);
|
|
1904
1910
|
}
|
|
1905
|
-
function
|
|
1906
|
-
const targetPath =
|
|
1911
|
+
function writeStarterSharedConfig(target, cwd = process.cwd()) {
|
|
1912
|
+
const targetPath = getSharedConfigPath(target, cwd);
|
|
1907
1913
|
const raw = { mcpServers: buildStarterProjectConfig().mcpServers };
|
|
1908
1914
|
writeRawConfigObject(targetPath, raw);
|
|
1909
1915
|
return targetPath;
|
|
@@ -2529,6 +2535,7 @@ var init_mcp_setup_panel = __esm({
|
|
|
2529
2535
|
this.actionCursor = 0;
|
|
2530
2536
|
this.importCursor = 0;
|
|
2531
2537
|
this.pathCursor = 0;
|
|
2538
|
+
this.sharedConfigTarget = "project";
|
|
2532
2539
|
this.selectedImports = /* @__PURE__ */ new Set();
|
|
2533
2540
|
this.busy = false;
|
|
2534
2541
|
this.notice = null;
|
|
@@ -2566,9 +2573,13 @@ var init_mcp_setup_panel = __esm({
|
|
|
2566
2573
|
if (this.discovery.imports.length > 0) {
|
|
2567
2574
|
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.` });
|
|
2568
2575
|
}
|
|
2569
|
-
actions.push(
|
|
2570
|
-
|
|
2571
|
-
|
|
2576
|
+
actions.push(
|
|
2577
|
+
{ id: "select-shared-target", label: `${this.sharedConfigTarget === "project" ? "\u25CF" : "\u25CB"} Add to this project (.mcp.json)`, description: "Write new shared MCP servers to the project/team config.", target: "project" },
|
|
2578
|
+
{ id: "select-shared-target", label: `${this.sharedConfigTarget === "global" ? "\u25CF" : "\u25CB"} Add globally (~/.config/mcp/mcp.json)`, description: "Write new shared MCP servers to your all-projects config.", target: "global" }
|
|
2579
|
+
);
|
|
2580
|
+
actions.push({ id: "view-example", label: "View example shared config", description: "Preview a working shared MCP config you can paste or adapt." });
|
|
2581
|
+
if (!this.selectedSharedConfigExists()) {
|
|
2582
|
+
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." });
|
|
2572
2583
|
}
|
|
2573
2584
|
actions.push({ id: "show-precedence", label: "Explain config precedence", description: "Show the read order and where Pi writes compatibility settings." });
|
|
2574
2585
|
if (this.getDetectedPaths().length > 0) {
|
|
@@ -2578,7 +2589,7 @@ var init_mcp_setup_panel = __esm({
|
|
|
2578
2589
|
actions.push({ id: "add-known-server", label: preset.name, description: preset.summary, preset });
|
|
2579
2590
|
}
|
|
2580
2591
|
if (!this.discovery.repoPrompt.configured && this.discovery.repoPrompt.executablePath && this.discovery.repoPrompt.targetPath && this.discovery.repoPrompt.entry && this.discovery.repoPrompt.serverName) {
|
|
2581
|
-
actions.push({ id: "add-repoprompt", label: "Add RepoPrompt to shared
|
|
2592
|
+
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." });
|
|
2582
2593
|
}
|
|
2583
2594
|
actions.push({ id: "close", label: "Close", description: "Exit the onboarding flow." });
|
|
2584
2595
|
return actions;
|
|
@@ -2590,6 +2601,13 @@ var init_mcp_setup_panel = __esm({
|
|
|
2590
2601
|
];
|
|
2591
2602
|
return [...new Set(paths)];
|
|
2592
2603
|
}
|
|
2604
|
+
sharedTargetLabel() {
|
|
2605
|
+
return this.sharedConfigTarget === "project" ? "project .mcp.json" : "global ~/.config/mcp/mcp.json";
|
|
2606
|
+
}
|
|
2607
|
+
selectedSharedConfigExists() {
|
|
2608
|
+
const sourceId = this.sharedConfigTarget === "project" ? "shared-project" : "shared-global";
|
|
2609
|
+
return this.discovery.sources.some((source) => source.id === sourceId && source.exists);
|
|
2610
|
+
}
|
|
2593
2611
|
getSelectedAction() {
|
|
2594
2612
|
const actions = this.getActions();
|
|
2595
2613
|
return actions[this.actionCursor];
|
|
@@ -2704,9 +2722,15 @@ var init_mcp_setup_panel = __esm({
|
|
|
2704
2722
|
this.tui.requestRender();
|
|
2705
2723
|
return;
|
|
2706
2724
|
}
|
|
2707
|
-
if (action.id === "
|
|
2725
|
+
if (action.id === "select-shared-target" && action.target) {
|
|
2726
|
+
this.sharedConfigTarget = action.target;
|
|
2727
|
+
this.notice = { text: `New shared servers will be written to ${this.sharedTargetLabel()}.`, tone: "muted" };
|
|
2728
|
+
this.tui.requestRender();
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
if (action.id === "scaffold-shared-config") {
|
|
2708
2732
|
await this.runBusy(async () => {
|
|
2709
|
-
const result = await this.callbacks.
|
|
2733
|
+
const result = await this.callbacks.scaffoldConfig(this.sharedConfigTarget);
|
|
2710
2734
|
this.callbacks.markSetupCompleted();
|
|
2711
2735
|
this.notice = { text: `Wrote starter config to ${result.path}. Pi will reload after this panel closes.`, tone: "success" };
|
|
2712
2736
|
});
|
|
@@ -2714,7 +2738,7 @@ var init_mcp_setup_panel = __esm({
|
|
|
2714
2738
|
}
|
|
2715
2739
|
if (action.id === "add-repoprompt") {
|
|
2716
2740
|
await this.runBusy(async () => {
|
|
2717
|
-
const result = await this.callbacks.addRepoPrompt();
|
|
2741
|
+
const result = await this.callbacks.addRepoPrompt(this.sharedConfigTarget);
|
|
2718
2742
|
this.callbacks.markSetupCompleted();
|
|
2719
2743
|
this.notice = { text: `Added ${result.serverName} to ${result.path}. Pi will reload after this panel closes.`, tone: "success" };
|
|
2720
2744
|
});
|
|
@@ -2723,7 +2747,7 @@ var init_mcp_setup_panel = __esm({
|
|
|
2723
2747
|
if (action.id === "add-known-server" && action.preset) {
|
|
2724
2748
|
const preset = action.preset;
|
|
2725
2749
|
await this.runBusy(async () => {
|
|
2726
|
-
const result = await this.callbacks.addKnownServer(preset);
|
|
2750
|
+
const result = await this.callbacks.addKnownServer(preset, this.sharedConfigTarget);
|
|
2727
2751
|
this.callbacks.markSetupCompleted();
|
|
2728
2752
|
this.notice = { text: `Added ${result.serverName} to ${result.path}. Pi will reload after this panel closes.`, tone: "success" };
|
|
2729
2753
|
});
|
|
@@ -2812,8 +2836,11 @@ var init_mcp_setup_panel = __esm({
|
|
|
2812
2836
|
for (let index = start; index < end; index++) {
|
|
2813
2837
|
const action = actions[index];
|
|
2814
2838
|
if (!action) continue;
|
|
2839
|
+
if (action.id === "select-shared-target" && (index === start || actions[index - 1]?.id !== "select-shared-target")) {
|
|
2840
|
+
lines.push(this.padLine(fg(this.t.title, "Choose where new shared servers go"), innerW));
|
|
2841
|
+
}
|
|
2815
2842
|
if (action.id === "add-known-server" && (index === start || actions[index - 1]?.id !== "add-known-server")) {
|
|
2816
|
-
lines.push(this.padLine(fg(this.t.title,
|
|
2843
|
+
lines.push(this.padLine(fg(this.t.title, `Add a known server to ${this.sharedTargetLabel()}`), innerW));
|
|
2817
2844
|
}
|
|
2818
2845
|
const selected = index === this.actionCursor;
|
|
2819
2846
|
const cursor = selected ? fg(this.t.selected, "\u203A") : " ";
|
|
@@ -2878,12 +2905,12 @@ var init_mcp_setup_panel = __esm({
|
|
|
2878
2905
|
const hostNote = this.discovery.hostConfigs.length > 0 ? ` Host discovery is ${this.discovery.hostConfigDiscovery}; ${this.discovery.hostConfigs.length} host source${this.discovery.hostConfigs.length === 1 ? "" : "s"} detected.` : "";
|
|
2879
2906
|
const conflictNote = this.discovery.conflicts.length > 0 ? ` ${this.discovery.conflicts.length} same-name conflict${this.discovery.conflicts.length === 1 ? "" : "s"} reported.` : "";
|
|
2880
2907
|
if (!this.discovery.hasAnyConfig) {
|
|
2881
|
-
return `
|
|
2908
|
+
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}`;
|
|
2882
2909
|
}
|
|
2883
2910
|
if (this.discovery.totalServerCount === 0 && this.discovery.imports.length > 0) {
|
|
2884
2911
|
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}`;
|
|
2885
2912
|
}
|
|
2886
|
-
return `
|
|
2913
|
+
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}`;
|
|
2887
2914
|
}
|
|
2888
2915
|
visibleActionRange(total) {
|
|
2889
2916
|
if (total <= COMPACT_ACTION_ROWS) return { start: 0, end: total };
|
|
@@ -2913,6 +2940,12 @@ var init_mcp_setup_panel = __esm({
|
|
|
2913
2940
|
],
|
|
2914
2941
|
previewW
|
|
2915
2942
|
);
|
|
2943
|
+
case "select-shared-target":
|
|
2944
|
+
return this.formatPreview([
|
|
2945
|
+
action.target === "project" ? "Project target: .mcp.json" : "Global target: ~/.config/mcp/mcp.json",
|
|
2946
|
+
"Known server presets and starter configs will be written to the selected normal MCP setup path.",
|
|
2947
|
+
"Pi-owned mcp.json files remain compatibility and adapter-only override state."
|
|
2948
|
+
], previewW);
|
|
2916
2949
|
case "view-example":
|
|
2917
2950
|
return this.formatPreview([
|
|
2918
2951
|
"Example shared `.mcp.json`:",
|
|
@@ -2925,10 +2958,17 @@ var init_mcp_setup_panel = __esm({
|
|
|
2925
2958
|
" }",
|
|
2926
2959
|
"}",
|
|
2927
2960
|
"",
|
|
2928
|
-
"Use Scaffold
|
|
2961
|
+
"Use Scaffold selected config when you want a safe empty shell instead of a live example server."
|
|
2929
2962
|
], previewW);
|
|
2930
2963
|
case "show-precedence":
|
|
2931
2964
|
return this.formatPreview([
|
|
2965
|
+
"Recommended shared config:",
|
|
2966
|
+
" project/team: .mcp.json",
|
|
2967
|
+
" all projects: ~/.config/mcp/mcp.json",
|
|
2968
|
+
"",
|
|
2969
|
+
"Advanced compatibility and Pi-owned layers:",
|
|
2970
|
+
" host imports, .agents files, package MCP manifests, and Pi overrides",
|
|
2971
|
+
"",
|
|
2932
2972
|
"Read order (later entries win):",
|
|
2933
2973
|
"0. detected host configs (opt-in lowest-precedence fallback)",
|
|
2934
2974
|
"1. ~/.config/mcp/mcp.json",
|
|
@@ -2947,7 +2987,7 @@ var init_mcp_setup_panel = __esm({
|
|
|
2947
2987
|
return this.formatPreview(this.getDetectedPaths().length > 0 ? ["Detected paths:", ...this.getDetectedPaths()] : ["No config paths were detected."], previewW);
|
|
2948
2988
|
case "add-repoprompt": {
|
|
2949
2989
|
const repoPrompt = this.discovery.repoPrompt;
|
|
2950
|
-
const preview = this.callbacks.previewRepoPrompt();
|
|
2990
|
+
const preview = this.callbacks.previewRepoPrompt(this.sharedConfigTarget);
|
|
2951
2991
|
if (!preview) {
|
|
2952
2992
|
return this.formatPreview(["RepoPrompt is not available to add from this setup screen."], previewW);
|
|
2953
2993
|
}
|
|
@@ -2956,7 +2996,7 @@ var init_mcp_setup_panel = __esm({
|
|
|
2956
2996
|
preview,
|
|
2957
2997
|
[
|
|
2958
2998
|
`Executable: ${repoPrompt.executablePath ?? "not found"}`,
|
|
2959
|
-
`Target: ${
|
|
2999
|
+
`Target: ${this.sharedTargetLabel()}`,
|
|
2960
3000
|
`Server name: ${repoPrompt.serverName ?? "repoprompt"}`
|
|
2961
3001
|
],
|
|
2962
3002
|
previewW
|
|
@@ -2967,17 +3007,17 @@ var init_mcp_setup_panel = __esm({
|
|
|
2967
3007
|
if (!preset) return this.formatPreview(["Known server preset is unavailable."], previewW);
|
|
2968
3008
|
return this.formatWritePreview(
|
|
2969
3009
|
`${preset.name} write preview`,
|
|
2970
|
-
this.callbacks.previewKnownServer(preset),
|
|
2971
|
-
[preset.summary],
|
|
3010
|
+
this.callbacks.previewKnownServer(preset, this.sharedConfigTarget),
|
|
3011
|
+
[preset.summary, `Target: ${this.sharedTargetLabel()}`],
|
|
2972
3012
|
previewW
|
|
2973
3013
|
);
|
|
2974
3014
|
}
|
|
2975
|
-
case "scaffold-
|
|
3015
|
+
case "scaffold-shared-config":
|
|
2976
3016
|
return this.formatWritePreview(
|
|
2977
|
-
|
|
2978
|
-
this.callbacks.
|
|
3017
|
+
`${this.sharedTargetLabel()} starter write preview`,
|
|
3018
|
+
this.callbacks.previewStarterConfig(this.sharedConfigTarget),
|
|
2979
3019
|
[
|
|
2980
|
-
"This writes a minimal
|
|
3020
|
+
"This writes a minimal config at the selected normal MCP setup path.",
|
|
2981
3021
|
"It intentionally avoids adding a fake placeholder server that would fail on first reload."
|
|
2982
3022
|
],
|
|
2983
3023
|
previewW
|
|
@@ -3373,6 +3413,7 @@ var init_mcp_panel = __esm({
|
|
|
3373
3413
|
}
|
|
3374
3414
|
const status = callbacks.getConnectionStatus(serverName);
|
|
3375
3415
|
const failureMessage = callbacks.getFailureMessage?.(serverName) ?? null;
|
|
3416
|
+
const serverDisabled = isServerDisabled(definition);
|
|
3376
3417
|
let directCount = 0;
|
|
3377
3418
|
let directTokens = 0;
|
|
3378
3419
|
for (const tool of tools) {
|
|
@@ -3388,6 +3429,8 @@ var init_mcp_panel = __esm({
|
|
|
3388
3429
|
...definition.includeTools !== void 0 ? { includeTools: definition.includeTools } : {},
|
|
3389
3430
|
...definition.excludeTools !== void 0 ? { excludeTools: definition.excludeTools } : {},
|
|
3390
3431
|
exposeResources: definition.exposeResources !== false,
|
|
3432
|
+
disabled: serverDisabled,
|
|
3433
|
+
wasDisabled: serverDisabled,
|
|
3391
3434
|
connectionStatus: status,
|
|
3392
3435
|
failureMessage,
|
|
3393
3436
|
tools,
|
|
@@ -3409,7 +3452,7 @@ var init_mcp_panel = __esm({
|
|
|
3409
3452
|
if (this.inactivityTimeout) clearTimeout(this.inactivityTimeout);
|
|
3410
3453
|
this.inactivityTimeout = setTimeout(() => {
|
|
3411
3454
|
this.cleanup();
|
|
3412
|
-
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map() });
|
|
3455
|
+
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
|
|
3413
3456
|
}, _McpPanel.INACTIVITY_MS);
|
|
3414
3457
|
}
|
|
3415
3458
|
cleanup() {
|
|
@@ -3460,11 +3503,15 @@ var init_mcp_panel = __esm({
|
|
|
3460
3503
|
}
|
|
3461
3504
|
}
|
|
3462
3505
|
updateDirty() {
|
|
3463
|
-
this.dirty = this.servers.some((s) => s.tools.some((t) => t.isDirect !== t.wasDirect));
|
|
3506
|
+
this.dirty = this.servers.some((s) => s.disabled !== s.wasDisabled || s.tools.some((t) => t.isDirect !== t.wasDirect));
|
|
3464
3507
|
}
|
|
3465
3508
|
buildResult() {
|
|
3466
3509
|
const changes = /* @__PURE__ */ new Map();
|
|
3510
|
+
const disabledChanges = /* @__PURE__ */ new Map();
|
|
3467
3511
|
for (const server2 of this.servers) {
|
|
3512
|
+
if (server2.disabled !== server2.wasDisabled) {
|
|
3513
|
+
disabledChanges.set(server2.name, server2.disabled);
|
|
3514
|
+
}
|
|
3468
3515
|
const changed = server2.tools.some((t) => t.isDirect !== t.wasDirect);
|
|
3469
3516
|
if (!changed) continue;
|
|
3470
3517
|
const directTools = server2.tools.filter((t) => t.isDirect);
|
|
@@ -3476,7 +3523,7 @@ var init_mcp_panel = __esm({
|
|
|
3476
3523
|
changes.set(server2.name, directTools.map((t) => t.name));
|
|
3477
3524
|
}
|
|
3478
3525
|
}
|
|
3479
|
-
return { changes, cancelled: false };
|
|
3526
|
+
return { changes, disabledChanges, cancelled: false };
|
|
3480
3527
|
}
|
|
3481
3528
|
handleInput(data) {
|
|
3482
3529
|
this.resetInactivityTimeout();
|
|
@@ -3488,7 +3535,7 @@ var init_mcp_panel = __esm({
|
|
|
3488
3535
|
}
|
|
3489
3536
|
if (matchesKey3(data, "ctrl+c")) {
|
|
3490
3537
|
this.cleanup();
|
|
3491
|
-
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map() });
|
|
3538
|
+
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
|
|
3492
3539
|
return;
|
|
3493
3540
|
}
|
|
3494
3541
|
if (this.keys.save(data)) {
|
|
@@ -3546,7 +3593,7 @@ var init_mcp_panel = __esm({
|
|
|
3546
3593
|
return;
|
|
3547
3594
|
}
|
|
3548
3595
|
this.cleanup();
|
|
3549
|
-
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map() });
|
|
3596
|
+
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
|
|
3550
3597
|
return;
|
|
3551
3598
|
}
|
|
3552
3599
|
if (this.keys.selectUp(data)) {
|
|
@@ -3599,6 +3646,16 @@ var init_mcp_panel = __esm({
|
|
|
3599
3646
|
if (server2) this.reconnectServer(server2);
|
|
3600
3647
|
return;
|
|
3601
3648
|
}
|
|
3649
|
+
if (matchesKey3(data, "ctrl+d")) {
|
|
3650
|
+
const item = this.visibleItems[this.cursorIndex];
|
|
3651
|
+
if (!item || item.type !== "server" || this.authOnly) return;
|
|
3652
|
+
const server2 = this.servers[item.serverIndex];
|
|
3653
|
+
if (!server2) return;
|
|
3654
|
+
server2.disabled = !server2.disabled;
|
|
3655
|
+
this.updateDirty();
|
|
3656
|
+
this.tui.requestRender();
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3602
3659
|
if (matchesKey3(data, "ctrl+y")) {
|
|
3603
3660
|
const item = this.visibleItems[this.cursorIndex];
|
|
3604
3661
|
if (!item) return;
|
|
@@ -3737,7 +3794,7 @@ var init_mcp_panel = __esm({
|
|
|
3737
3794
|
handleDiscardInput(data) {
|
|
3738
3795
|
if (matchesKey3(data, "ctrl+c")) {
|
|
3739
3796
|
this.cleanup();
|
|
3740
|
-
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map() });
|
|
3797
|
+
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
|
|
3741
3798
|
return;
|
|
3742
3799
|
}
|
|
3743
3800
|
if (matchesKey3(data, "escape") || data === "n" || data === "N") {
|
|
@@ -3747,7 +3804,7 @@ var init_mcp_panel = __esm({
|
|
|
3747
3804
|
if (this.keys.selectConfirm(data)) {
|
|
3748
3805
|
this.cleanup();
|
|
3749
3806
|
if (this.discardSelected === 0) {
|
|
3750
|
-
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map() });
|
|
3807
|
+
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
|
|
3751
3808
|
} else {
|
|
3752
3809
|
this.done(this.buildResult());
|
|
3753
3810
|
}
|
|
@@ -3755,7 +3812,7 @@ var init_mcp_panel = __esm({
|
|
|
3755
3812
|
}
|
|
3756
3813
|
if (data === "y" || data === "Y") {
|
|
3757
3814
|
this.cleanup();
|
|
3758
|
-
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map() });
|
|
3815
|
+
this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
|
|
3759
3816
|
return;
|
|
3760
3817
|
}
|
|
3761
3818
|
if (matchesKey3(data, "left") || matchesKey3(data, "right") || matchesKey3(data, "tab")) {
|
|
@@ -3949,6 +4006,7 @@ var init_mcp_panel = __esm({
|
|
|
3949
4006
|
italic("\u23CE") + " expand/auth",
|
|
3950
4007
|
italic("ctrl+a") + " auth",
|
|
3951
4008
|
italic("ctrl+r") + " reconnect",
|
|
4009
|
+
italic("ctrl+d") + " disable/enable",
|
|
3952
4010
|
...this.selectedServerHasFailureMessage() ? [italic("ctrl+y") + " copy error"] : [],
|
|
3953
4011
|
italic("?") + " desc search",
|
|
3954
4012
|
...saveLabel ? [italic(saveLabel) + " save"] : [],
|
|
@@ -4048,7 +4106,7 @@ var init_mcp_panel = __esm({
|
|
|
4048
4106
|
renderConnectionStatus(server2) {
|
|
4049
4107
|
const t = this.t;
|
|
4050
4108
|
if (this.authInFlight === server2.name) return ` ${fg2(t.needsAuth, "authenticating")}`;
|
|
4051
|
-
if (server2.
|
|
4109
|
+
if (server2.disabled) return ` ${fg2(t.description, "disabled")}`;
|
|
4052
4110
|
if (server2.connectionStatus === "needs-auth") return ` ${fg2(t.needsAuth, "needs auth")}`;
|
|
4053
4111
|
if (server2.connectionStatus === "connecting") return ` ${fg2(t.needsAuth, "connecting")}`;
|
|
4054
4112
|
if (server2.connectionStatus === "failed") return ` ${fg2(t.cancel, "failed")}`;
|
|
@@ -4179,6 +4237,89 @@ function wrapError(error, context) {
|
|
|
4179
4237
|
...cause !== void 0 ? { cause } : {}
|
|
4180
4238
|
});
|
|
4181
4239
|
}
|
|
4240
|
+
var INPUT_REQUIRED_NEEDS_UI = "input_required_needs_ui";
|
|
4241
|
+
var INPUT_REQUIRED_METHODS = /* @__PURE__ */ new Set([
|
|
4242
|
+
"elicitation/create",
|
|
4243
|
+
"sampling/createMessage"
|
|
4244
|
+
]);
|
|
4245
|
+
var MAX_INPUT_REQUIRED_SERVER_LENGTH = 96;
|
|
4246
|
+
var MAX_INPUT_REQUIRED_TARGET_LENGTH = 160;
|
|
4247
|
+
var MAX_INPUT_REQUIRED_DETAIL_LENGTH = 128;
|
|
4248
|
+
function asRecord(value) {
|
|
4249
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4250
|
+
}
|
|
4251
|
+
function boundedText(value, maxLength) {
|
|
4252
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
4253
|
+
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}\u2026`;
|
|
4254
|
+
}
|
|
4255
|
+
function findEmbeddedInputRequest(error) {
|
|
4256
|
+
let current = error;
|
|
4257
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4258
|
+
for (let depth = 0; depth < 8 && current !== void 0 && current !== null; depth++) {
|
|
4259
|
+
if (seen.has(current)) return void 0;
|
|
4260
|
+
seen.add(current);
|
|
4261
|
+
const record = asRecord(current);
|
|
4262
|
+
if (!record) return void 0;
|
|
4263
|
+
if (record.code === INPUT_REQUIRED_NEEDS_UI) {
|
|
4264
|
+
const details = asRecord(record.details);
|
|
4265
|
+
const inputKey = boundedText(details?.inputKey, MAX_INPUT_REQUIRED_DETAIL_LENGTH);
|
|
4266
|
+
const inputMethod = boundedText(details?.inputMethod, MAX_INPUT_REQUIRED_DETAIL_LENGTH);
|
|
4267
|
+
if (inputKey && inputMethod && INPUT_REQUIRED_METHODS.has(inputMethod)) {
|
|
4268
|
+
const server2 = boundedText(details?.server, MAX_INPUT_REQUIRED_SERVER_LENGTH);
|
|
4269
|
+
const tool = boundedText(details?.tool, MAX_INPUT_REQUIRED_TARGET_LENGTH);
|
|
4270
|
+
const resourceUri = boundedText(details?.resourceUri, MAX_INPUT_REQUIRED_TARGET_LENGTH);
|
|
4271
|
+
return {
|
|
4272
|
+
inputKey,
|
|
4273
|
+
inputMethod,
|
|
4274
|
+
...server2 ? { server: server2 } : {},
|
|
4275
|
+
...tool ? { tool } : {},
|
|
4276
|
+
...resourceUri ? { resourceUri } : {}
|
|
4277
|
+
};
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
if (record.code === "CAPABILITY_NOT_SUPPORTED") {
|
|
4281
|
+
const data = asRecord(record.data);
|
|
4282
|
+
const inputKey = boundedText(data?.key, MAX_INPUT_REQUIRED_DETAIL_LENGTH);
|
|
4283
|
+
const inputMethod = boundedText(data?.method, MAX_INPUT_REQUIRED_DETAIL_LENGTH);
|
|
4284
|
+
if (inputKey && inputMethod && INPUT_REQUIRED_METHODS.has(inputMethod)) {
|
|
4285
|
+
return { inputKey, inputMethod };
|
|
4286
|
+
}
|
|
4287
|
+
}
|
|
4288
|
+
current = record.cause;
|
|
4289
|
+
}
|
|
4290
|
+
return void 0;
|
|
4291
|
+
}
|
|
4292
|
+
function getInputRequiredNeedsUiDetails(error, identity) {
|
|
4293
|
+
const request = findEmbeddedInputRequest(error);
|
|
4294
|
+
if (!request) return void 0;
|
|
4295
|
+
const server2 = boundedText(request.server ?? identity.server, MAX_INPUT_REQUIRED_SERVER_LENGTH) ?? "unknown";
|
|
4296
|
+
const tool = boundedText(request.tool ?? identity.tool, MAX_INPUT_REQUIRED_TARGET_LENGTH);
|
|
4297
|
+
const resourceUri = boundedText(request.resourceUri ?? identity.resourceUri, MAX_INPUT_REQUIRED_TARGET_LENGTH);
|
|
4298
|
+
const target = resourceUri ? `read resource "${resourceUri}"` : tool ? `call tool "${tool}"` : "complete the MCP request";
|
|
4299
|
+
const guidance = request.inputMethod === "elicitation/create" ? "Run this call in an interactive Pi session with elicitation enabled, then retry." : "Run this call in an interactive Pi session with the required MCP capability enabled, then retry.";
|
|
4300
|
+
const message = `MCP server "${server2}" requested input to ${target}, but this session has no handler for "${request.inputMethod}" (input "${request.inputKey}"). ${guidance}`;
|
|
4301
|
+
return {
|
|
4302
|
+
error: INPUT_REQUIRED_NEEDS_UI,
|
|
4303
|
+
server: server2,
|
|
4304
|
+
...tool ? { tool } : {},
|
|
4305
|
+
...resourceUri ? { resourceUri } : {},
|
|
4306
|
+
inputKey: request.inputKey,
|
|
4307
|
+
inputMethod: request.inputMethod,
|
|
4308
|
+
message
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
var InputRequiredNeedsUiError = class extends McpUiError {
|
|
4312
|
+
constructor(details, cause) {
|
|
4313
|
+
super(details.message, {
|
|
4314
|
+
code: INPUT_REQUIRED_NEEDS_UI,
|
|
4315
|
+
context: { ...details },
|
|
4316
|
+
recoveryHint: "Run this call in an interactive Pi session with the required MCP input handler enabled, then retry.",
|
|
4317
|
+
...cause ? { cause } : {}
|
|
4318
|
+
});
|
|
4319
|
+
this.details = details;
|
|
4320
|
+
this.name = "InputRequiredNeedsUiError";
|
|
4321
|
+
}
|
|
4322
|
+
};
|
|
4182
4323
|
|
|
4183
4324
|
// packages/coding-agent/src/humain/mcp-adapter/vendor/logger.ts
|
|
4184
4325
|
var LEVEL_PRIORITY = {
|
|
@@ -4968,14 +5109,14 @@ function readNpxCachePayload(cachePath) {
|
|
|
4968
5109
|
return null;
|
|
4969
5110
|
}
|
|
4970
5111
|
}
|
|
4971
|
-
function
|
|
5112
|
+
function asRecord2(value) {
|
|
4972
5113
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
4973
5114
|
}
|
|
4974
5115
|
function createCacheEntries() {
|
|
4975
5116
|
return /* @__PURE__ */ Object.create(null);
|
|
4976
5117
|
}
|
|
4977
5118
|
function toNpxCacheEntry(value) {
|
|
4978
|
-
const raw =
|
|
5119
|
+
const raw = asRecord2(value);
|
|
4979
5120
|
if (!raw) return null;
|
|
4980
5121
|
if (typeof raw.resolvedBin !== "string") return null;
|
|
4981
5122
|
if (typeof raw.resolvedAt !== "number" || !Number.isFinite(raw.resolvedAt)) return null;
|
|
@@ -4989,9 +5130,9 @@ function toNpxCacheEntry(value) {
|
|
|
4989
5130
|
};
|
|
4990
5131
|
}
|
|
4991
5132
|
function toNpxCache(value) {
|
|
4992
|
-
const raw =
|
|
5133
|
+
const raw = asRecord2(value);
|
|
4993
5134
|
if (!raw || raw.version !== CACHE_VERSION) return null;
|
|
4994
|
-
const rawEntries =
|
|
5135
|
+
const rawEntries = asRecord2(raw.entries);
|
|
4995
5136
|
if (!rawEntries) return null;
|
|
4996
5137
|
const entries = createCacheEntries();
|
|
4997
5138
|
for (const [key, rawEntry] of Object.entries(rawEntries)) {
|
|
@@ -5002,7 +5143,7 @@ function toNpxCache(value) {
|
|
|
5002
5143
|
}
|
|
5003
5144
|
function clearLegacyCache() {
|
|
5004
5145
|
const cachePath = getNpxCachePath();
|
|
5005
|
-
const raw =
|
|
5146
|
+
const raw = asRecord2(readNpxCachePayload(cachePath));
|
|
5006
5147
|
if (raw?.version !== 1) return false;
|
|
5007
5148
|
try {
|
|
5008
5149
|
unlinkSync(cachePath);
|
|
@@ -8713,6 +8854,9 @@ function appendStderrTail(tail, chunk) {
|
|
|
8713
8854
|
return combined.length > MAX_CAPTURED_STDERR_BYTES ? Buffer.from(combined.subarray(combined.length - MAX_CAPTURED_STDERR_BYTES)) : combined;
|
|
8714
8855
|
}
|
|
8715
8856
|
var KEEP_ALIVE_REFRESH_TIMEOUT_MS = 5e3;
|
|
8857
|
+
var LISTEN_RETRY_DELAY_MS = 5e3;
|
|
8858
|
+
var RECENT_RESOURCE_TTL_MS = 10 * 6e4;
|
|
8859
|
+
var MAX_RESOURCE_SUBSCRIPTIONS = 32;
|
|
8716
8860
|
function isTransientHttpConnectError(error) {
|
|
8717
8861
|
let current = error;
|
|
8718
8862
|
while (current instanceof Error) {
|
|
@@ -8729,6 +8873,7 @@ var McpServerManager = class {
|
|
|
8729
8873
|
this.connectPromises = /* @__PURE__ */ new Map();
|
|
8730
8874
|
this.reconnectPromises = /* @__PURE__ */ new Map();
|
|
8731
8875
|
this.uiStreamListeners = /* @__PURE__ */ new Map();
|
|
8876
|
+
this.resourceUpdatedListeners = /* @__PURE__ */ new Map();
|
|
8732
8877
|
this.pendingMetadataPublications = /* @__PURE__ */ new Map();
|
|
8733
8878
|
this.authStorageOptions = {};
|
|
8734
8879
|
this.acceptedUrlElicitations = /* @__PURE__ */ new Map();
|
|
@@ -8743,6 +8888,9 @@ var McpServerManager = class {
|
|
|
8743
8888
|
setMetadataListChangedListener(listener) {
|
|
8744
8889
|
this.metadataListChangedListener = listener;
|
|
8745
8890
|
}
|
|
8891
|
+
setListenStateChangedListener(listener) {
|
|
8892
|
+
this.listenStateChangedListener = listener;
|
|
8893
|
+
}
|
|
8746
8894
|
publishMetadataChanged(name, expectedConnection, reason) {
|
|
8747
8895
|
const current = this.connections.get(name);
|
|
8748
8896
|
if (current !== expectedConnection || current.status !== "connected") return false;
|
|
@@ -8830,6 +8978,10 @@ var McpServerManager = class {
|
|
|
8830
8978
|
throw new Error(`MCP connection for ${name} was closed while connecting`);
|
|
8831
8979
|
}
|
|
8832
8980
|
this.connections.set(name, connection);
|
|
8981
|
+
this.watchListenSubscription(name, connection, connection.listenSubscription);
|
|
8982
|
+
if ([...this.resourceUpdatedListeners.values()].some((registration) => registration.serverName === name)) {
|
|
8983
|
+
void this.ensureListen(name, connection);
|
|
8984
|
+
}
|
|
8833
8985
|
return connection;
|
|
8834
8986
|
} finally {
|
|
8835
8987
|
if (this.connectPromises.get(name) === promise) this.connectPromises.delete(name);
|
|
@@ -8876,6 +9028,7 @@ var McpServerManager = class {
|
|
|
8876
9028
|
if (current !== expectedConnection || current.status !== "connected") {
|
|
8877
9029
|
return "superseded";
|
|
8878
9030
|
}
|
|
9031
|
+
await this.ensureListen(name, expectedConnection);
|
|
8879
9032
|
const requestOptions = this.buildRequestOptions(expectedConnection.definition, signal);
|
|
8880
9033
|
const timeout = Math.min(requestOptions?.timeout ?? KEEP_ALIVE_REFRESH_TIMEOUT_MS, KEEP_ALIVE_REFRESH_TIMEOUT_MS);
|
|
8881
9034
|
const healthOptions = {
|
|
@@ -8947,6 +9100,198 @@ var McpServerManager = class {
|
|
|
8947
9100
|
this.pendingMetadataPublications.delete(name);
|
|
8948
9101
|
}
|
|
8949
9102
|
}
|
|
9103
|
+
setListenState(name, connection, state) {
|
|
9104
|
+
if (connection.listenState === state) return;
|
|
9105
|
+
connection.listenState = state;
|
|
9106
|
+
if (this.connections.get(name) === connection) {
|
|
9107
|
+
try {
|
|
9108
|
+
this.listenStateChangedListener?.(name, state);
|
|
9109
|
+
} catch {
|
|
9110
|
+
}
|
|
9111
|
+
}
|
|
9112
|
+
}
|
|
9113
|
+
catalogListenFilter(connection) {
|
|
9114
|
+
const capabilities = connection.client.getServerCapabilities?.();
|
|
9115
|
+
return {
|
|
9116
|
+
...capabilities?.tools?.listChanged ? { toolsListChanged: true } : {},
|
|
9117
|
+
...capabilities?.prompts?.listChanged ? { promptsListChanged: true } : {},
|
|
9118
|
+
...capabilities?.resources?.listChanged ? { resourcesListChanged: true } : {}
|
|
9119
|
+
};
|
|
9120
|
+
}
|
|
9121
|
+
currentListenFilter(name, connection) {
|
|
9122
|
+
const now = Date.now();
|
|
9123
|
+
const recent = connection.recentResourceUris;
|
|
9124
|
+
if (recent) {
|
|
9125
|
+
for (const [uri, touchedAt] of recent) {
|
|
9126
|
+
if (now - touchedAt > RECENT_RESOURCE_TTL_MS) recent.delete(uri);
|
|
9127
|
+
}
|
|
9128
|
+
}
|
|
9129
|
+
const openResourceUris = [...new Set(
|
|
9130
|
+
[...this.resourceUpdatedListeners.values()].filter((registration) => registration.serverName === name).map((registration) => registration.uri)
|
|
9131
|
+
)].slice(-MAX_RESOURCE_SUBSCRIPTIONS);
|
|
9132
|
+
const openSet = new Set(openResourceUris);
|
|
9133
|
+
const recentSlots = MAX_RESOURCE_SUBSCRIPTIONS - openResourceUris.length;
|
|
9134
|
+
const recentResourceUris = recentSlots > 0 ? [...recent?.keys() ?? []].filter((uri) => !openSet.has(uri)).slice(-recentSlots) : [];
|
|
9135
|
+
const resourceSubscriptions = [...openResourceUris, ...recentResourceUris];
|
|
9136
|
+
return {
|
|
9137
|
+
...this.catalogListenFilter(connection),
|
|
9138
|
+
...resourceSubscriptions.length > 0 ? { resourceSubscriptions } : {}
|
|
9139
|
+
};
|
|
9140
|
+
}
|
|
9141
|
+
watchListenSubscription(name, connection, subscription) {
|
|
9142
|
+
if (!subscription) return;
|
|
9143
|
+
void subscription.closed.then((cause) => {
|
|
9144
|
+
if (this.stopped || this.connections.get(name) !== connection || connection.status !== "connected" || connection.listenSubscription !== subscription) return;
|
|
9145
|
+
if (cause === "remote") {
|
|
9146
|
+
const staleUris = connection.listenFilter?.resourceSubscriptions ?? [];
|
|
9147
|
+
if (staleUris.length > 0) connection.resourceReadRefreshUris = new Set(staleUris);
|
|
9148
|
+
connection.listenCatalogStale = true;
|
|
9149
|
+
connection.listenRetryAfter = Date.now();
|
|
9150
|
+
this.setListenState(name, connection, "dropped");
|
|
9151
|
+
} else if (cause === "graceful" || connection.listenState !== "re-establishing") {
|
|
9152
|
+
connection.listenStopped = true;
|
|
9153
|
+
this.setListenState(name, connection, "not-listening");
|
|
9154
|
+
}
|
|
9155
|
+
});
|
|
9156
|
+
}
|
|
9157
|
+
/** Quietly repairs a modern catalog listen at an existing activity boundary. */
|
|
9158
|
+
async ensureListen(name, expectedConnection) {
|
|
9159
|
+
if (this.stopped || this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected" || expectedConnection.listenStopped || expectedConnection.client.getProtocolEra?.() !== "modern") return;
|
|
9160
|
+
const filter = this.currentListenFilter(name, expectedConnection);
|
|
9161
|
+
if (Object.keys(filter).length === 0) {
|
|
9162
|
+
this.setListenState(name, expectedConnection, "not-listening");
|
|
9163
|
+
return;
|
|
9164
|
+
}
|
|
9165
|
+
if (expectedConnection.listenPromise) {
|
|
9166
|
+
await expectedConnection.listenPromise;
|
|
9167
|
+
return this.ensureListen(name, expectedConnection);
|
|
9168
|
+
}
|
|
9169
|
+
if ((expectedConnection.listenRetryAfter ?? 0) > Date.now()) return;
|
|
9170
|
+
const sameFilter = isDeepStrictEqual(expectedConnection.listenFilter, filter);
|
|
9171
|
+
if (expectedConnection.listenState === "active" && sameFilter) {
|
|
9172
|
+
if (!expectedConnection.listenCatalogStale) return;
|
|
9173
|
+
const attempt2 = (async () => {
|
|
9174
|
+
this.setListenState(name, expectedConnection, "re-establishing");
|
|
9175
|
+
const confirmed = await this.reconcileCatalogAfterListen(name, expectedConnection, this.buildRequestOptions(expectedConnection.definition));
|
|
9176
|
+
if (!confirmed) expectedConnection.listenRetryAfter = Date.now() + LISTEN_RETRY_DELAY_MS;
|
|
9177
|
+
this.setListenState(name, expectedConnection, "active");
|
|
9178
|
+
})().finally(() => {
|
|
9179
|
+
if (expectedConnection.listenPromise === attempt2) delete expectedConnection.listenPromise;
|
|
9180
|
+
});
|
|
9181
|
+
expectedConnection.listenPromise = attempt2;
|
|
9182
|
+
return attempt2;
|
|
9183
|
+
}
|
|
9184
|
+
const attempt = (async () => {
|
|
9185
|
+
const recoverDroppedListen = expectedConnection.listenState === "dropped";
|
|
9186
|
+
this.setListenState(name, expectedConnection, "re-establishing");
|
|
9187
|
+
const previous = expectedConnection.listenSubscription;
|
|
9188
|
+
if (previous) await previous.close().catch(() => {
|
|
9189
|
+
});
|
|
9190
|
+
if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return;
|
|
9191
|
+
try {
|
|
9192
|
+
const requestOptions = this.buildRequestOptions(expectedConnection.definition);
|
|
9193
|
+
const subscription = await expectedConnection.client.listen(
|
|
9194
|
+
filter,
|
|
9195
|
+
{
|
|
9196
|
+
...requestOptions,
|
|
9197
|
+
timeout: Math.min(requestOptions?.timeout ?? KEEP_ALIVE_REFRESH_TIMEOUT_MS, KEEP_ALIVE_REFRESH_TIMEOUT_MS)
|
|
9198
|
+
}
|
|
9199
|
+
);
|
|
9200
|
+
if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") {
|
|
9201
|
+
await subscription.close().catch(() => {
|
|
9202
|
+
});
|
|
9203
|
+
return;
|
|
9204
|
+
}
|
|
9205
|
+
expectedConnection.listenSubscription = subscription;
|
|
9206
|
+
expectedConnection.listenFilter = filter;
|
|
9207
|
+
delete expectedConnection.listenStopped;
|
|
9208
|
+
delete expectedConnection.listenRetryAfter;
|
|
9209
|
+
this.watchListenSubscription(name, expectedConnection, subscription);
|
|
9210
|
+
const confirmed = recoverDroppedListen ? await this.reconcileCatalogAfterListen(name, expectedConnection, requestOptions) : true;
|
|
9211
|
+
if (!confirmed) expectedConnection.listenRetryAfter = Date.now() + LISTEN_RETRY_DELAY_MS;
|
|
9212
|
+
this.setListenState(name, expectedConnection, "active");
|
|
9213
|
+
} catch (error) {
|
|
9214
|
+
if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return;
|
|
9215
|
+
if (expectedConnection.listenSubscription) await expectedConnection.listenSubscription.close().catch(() => {
|
|
9216
|
+
});
|
|
9217
|
+
expectedConnection.listenRetryAfter = Date.now() + LISTEN_RETRY_DELAY_MS;
|
|
9218
|
+
this.setListenState(name, expectedConnection, "dropped");
|
|
9219
|
+
logger.debug(`MCP: catalog listen repair failed for ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9220
|
+
}
|
|
9221
|
+
})().finally(() => {
|
|
9222
|
+
if (expectedConnection.listenPromise === attempt) delete expectedConnection.listenPromise;
|
|
9223
|
+
});
|
|
9224
|
+
expectedConnection.listenPromise = attempt;
|
|
9225
|
+
return attempt;
|
|
9226
|
+
}
|
|
9227
|
+
async reconcileCatalogAfterListen(name, expectedConnection, requestOptions) {
|
|
9228
|
+
const timeout = Math.min(requestOptions?.timeout ?? KEEP_ALIVE_REFRESH_TIMEOUT_MS, KEEP_ALIVE_REFRESH_TIMEOUT_MS);
|
|
9229
|
+
const refreshSignal = combineAbortSignals(requestOptions?.signal, AbortSignal.timeout(timeout));
|
|
9230
|
+
const refreshOptions = {
|
|
9231
|
+
...requestOptions,
|
|
9232
|
+
timeout,
|
|
9233
|
+
cacheMode: "refresh",
|
|
9234
|
+
...refreshSignal ? { signal: refreshSignal } : {}
|
|
9235
|
+
};
|
|
9236
|
+
const [toolResult, resources, promptResult] = await Promise.allSettled([
|
|
9237
|
+
this.fetchAllTools(expectedConnection.client, refreshOptions),
|
|
9238
|
+
this.fetchAllResources(expectedConnection.client, refreshOptions, true),
|
|
9239
|
+
this.fetchAllPrompts(expectedConnection.client, refreshOptions)
|
|
9240
|
+
]);
|
|
9241
|
+
if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return false;
|
|
9242
|
+
const nextTools = toolResult.status === "fulfilled" ? toolResult.value : void 0;
|
|
9243
|
+
const nextResources = resources.status === "fulfilled" ? resources.value : void 0;
|
|
9244
|
+
const nextPrompts = promptResult.status === "fulfilled" && !promptResult.value.failed ? promptResult.value : void 0;
|
|
9245
|
+
const confirmed = nextTools !== void 0 && nextResources !== void 0 && nextPrompts !== void 0;
|
|
9246
|
+
const changed = nextTools !== void 0 && (!isDeepStrictEqual(expectedConnection.tools, nextTools.tools) || !isDeepStrictEqual(expectedConnection.toolListHints, nextTools.hints)) || nextResources !== void 0 && !isDeepStrictEqual(expectedConnection.resources, nextResources) || nextPrompts !== void 0 && (!isDeepStrictEqual(expectedConnection.prompts, nextPrompts.prompts) || expectedConnection.promptDiscoveryFailed !== false);
|
|
9247
|
+
if (!changed) {
|
|
9248
|
+
this.retryPendingMetadataPublication(name, expectedConnection);
|
|
9249
|
+
if (confirmed) delete expectedConnection.listenCatalogStale;
|
|
9250
|
+
else expectedConnection.listenCatalogStale = true;
|
|
9251
|
+
return confirmed;
|
|
9252
|
+
}
|
|
9253
|
+
if (nextTools !== void 0) {
|
|
9254
|
+
expectedConnection.tools = nextTools.tools;
|
|
9255
|
+
expectedConnection.toolListHints = nextTools.hints;
|
|
9256
|
+
expectedConnection.toolsRevision = (expectedConnection.toolsRevision ?? 0) + 1;
|
|
9257
|
+
}
|
|
9258
|
+
if (nextResources !== void 0) expectedConnection.resources = nextResources;
|
|
9259
|
+
if (nextPrompts !== void 0) {
|
|
9260
|
+
expectedConnection.prompts = nextPrompts.prompts;
|
|
9261
|
+
expectedConnection.promptDiscoveryFailed = false;
|
|
9262
|
+
}
|
|
9263
|
+
if (confirmed) delete expectedConnection.listenCatalogStale;
|
|
9264
|
+
else expectedConnection.listenCatalogStale = true;
|
|
9265
|
+
this.metadataListChangedListener?.(name, "listen-recovered");
|
|
9266
|
+
this.pendingMetadataPublications.delete(name);
|
|
9267
|
+
return confirmed;
|
|
9268
|
+
}
|
|
9269
|
+
async prepareResourceUse(name, uri, expectedConnection) {
|
|
9270
|
+
if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return false;
|
|
9271
|
+
const refreshRead = expectedConnection.listenState === "dropped" || expectedConnection.listenState === "re-establishing" || expectedConnection.resourceReadRefreshUris?.has(uri) === true;
|
|
9272
|
+
const recent = expectedConnection.recentResourceUris ?? /* @__PURE__ */ new Map();
|
|
9273
|
+
recent.delete(uri);
|
|
9274
|
+
recent.set(uri, Date.now());
|
|
9275
|
+
while (recent.size > MAX_RESOURCE_SUBSCRIPTIONS) {
|
|
9276
|
+
const oldest = recent.keys().next().value;
|
|
9277
|
+
if (oldest === void 0) break;
|
|
9278
|
+
recent.delete(oldest);
|
|
9279
|
+
}
|
|
9280
|
+
expectedConnection.recentResourceUris = recent;
|
|
9281
|
+
await this.ensureListen(name, expectedConnection);
|
|
9282
|
+
expectedConnection.resourceReadRefreshUris?.delete(uri);
|
|
9283
|
+
return refreshRead;
|
|
9284
|
+
}
|
|
9285
|
+
registerResourceUpdatedListener(token, serverName, uri, listener) {
|
|
9286
|
+
this.removeResourceUpdatedListener(token);
|
|
9287
|
+
this.resourceUpdatedListeners.set(token, { serverName, uri, listener });
|
|
9288
|
+
const connection = this.connections.get(serverName);
|
|
9289
|
+
if (!connection || connection.status !== "connected") return;
|
|
9290
|
+
void this.prepareResourceUse(serverName, uri, connection);
|
|
9291
|
+
}
|
|
9292
|
+
removeResourceUpdatedListener(token) {
|
|
9293
|
+
this.resourceUpdatedListeners.delete(token);
|
|
9294
|
+
}
|
|
8950
9295
|
async doReconnect(name, definition, staleConnection, signal) {
|
|
8951
9296
|
throwIfAborted(signal);
|
|
8952
9297
|
const current = this.connections.get(name);
|
|
@@ -9031,6 +9376,7 @@ var McpServerManager = class {
|
|
|
9031
9376
|
lastUsedAt: Date.now(),
|
|
9032
9377
|
inFlight: 0,
|
|
9033
9378
|
status: "needs-auth",
|
|
9379
|
+
listenState: "disconnected",
|
|
9034
9380
|
credentialsInvalidated: invalidated
|
|
9035
9381
|
};
|
|
9036
9382
|
}
|
|
@@ -9051,6 +9397,8 @@ var McpServerManager = class {
|
|
|
9051
9397
|
}
|
|
9052
9398
|
this.attachAdapterNotificationHandlers(name, client);
|
|
9053
9399
|
const instructions = client.getInstructions?.();
|
|
9400
|
+
const protocolEra = client.getProtocolEra?.();
|
|
9401
|
+
const autoOpenedSubscription = client.autoOpenedSubscription;
|
|
9054
9402
|
const connection = {
|
|
9055
9403
|
client,
|
|
9056
9404
|
transport,
|
|
@@ -9062,8 +9410,13 @@ var McpServerManager = class {
|
|
|
9062
9410
|
...instructions !== void 0 ? { instructions } : {},
|
|
9063
9411
|
lastUsedAt: Date.now(),
|
|
9064
9412
|
inFlight: 0,
|
|
9065
|
-
status: "connected"
|
|
9413
|
+
status: "connected",
|
|
9414
|
+
listenState: protocolEra === "modern" ? autoOpenedSubscription ? "active" : "not-listening" : "legacy",
|
|
9415
|
+
...autoOpenedSubscription ? {
|
|
9416
|
+
listenSubscription: autoOpenedSubscription
|
|
9417
|
+
} : {}
|
|
9066
9418
|
};
|
|
9419
|
+
if (autoOpenedSubscription) connection.listenFilter = this.catalogListenFilter(connection);
|
|
9067
9420
|
client.onclose = () => {
|
|
9068
9421
|
if (this.connections.get(name) === connection) {
|
|
9069
9422
|
connection.status = "closed";
|
|
@@ -9106,6 +9459,7 @@ var McpServerManager = class {
|
|
|
9106
9459
|
lastUsedAt: Date.now(),
|
|
9107
9460
|
inFlight: 0,
|
|
9108
9461
|
status: "needs-auth",
|
|
9462
|
+
listenState: "disconnected",
|
|
9109
9463
|
credentialsInvalidated: invalidated
|
|
9110
9464
|
};
|
|
9111
9465
|
}
|
|
@@ -9316,7 +9670,16 @@ var McpServerManager = class {
|
|
|
9316
9670
|
this.authStorageOptions,
|
|
9317
9671
|
this.oauthRuntime?.signal
|
|
9318
9672
|
);
|
|
9319
|
-
let
|
|
9673
|
+
let implicitStoredAuth;
|
|
9674
|
+
if (definition.auth === void 0 && supportsOAuth(definition)) {
|
|
9675
|
+
try {
|
|
9676
|
+
implicitStoredAuth = inspectAuthForUrl(serverName, serverUrl, this.authStorageOptions);
|
|
9677
|
+
} catch {
|
|
9678
|
+
}
|
|
9679
|
+
}
|
|
9680
|
+
const hasImplicitStoredTokens = implicitStoredAuth?.status === "present" && implicitStoredAuth.entry.tokens !== void 0;
|
|
9681
|
+
if (hasImplicitStoredTokens) invalidateAuthEntryCache(serverName);
|
|
9682
|
+
let authState = supportsOAuth(definition) ? definition.auth === void 0 ? hasImplicitStoredTokens ? { status: "implicit-stored", provider: createAuthProvider() } : { status: "implicit-deferred" } : { status: "explicit", provider: createAuthProvider() } : { status: "disabled" };
|
|
9320
9683
|
const attempt = async (kind2) => {
|
|
9321
9684
|
const authProvider = "provider" in authState ? authState.provider : void 0;
|
|
9322
9685
|
const transportOptions = {
|
|
@@ -9421,7 +9784,7 @@ var McpServerManager = class {
|
|
|
9421
9784
|
return { prompts: [], failed: true };
|
|
9422
9785
|
}
|
|
9423
9786
|
}
|
|
9424
|
-
async fetchAllResources(client, requestOptions) {
|
|
9787
|
+
async fetchAllResources(client, requestOptions, strict = false) {
|
|
9425
9788
|
const capabilities = client.getServerCapabilities?.();
|
|
9426
9789
|
if (!capabilities?.resources) return [];
|
|
9427
9790
|
try {
|
|
@@ -9438,10 +9801,24 @@ var McpServerManager = class {
|
|
|
9438
9801
|
throwIfAborted(requestOptions.signal);
|
|
9439
9802
|
}
|
|
9440
9803
|
if (isUnauthorizedHttpError(error)) throw error;
|
|
9804
|
+
if (strict) throw error;
|
|
9441
9805
|
return [];
|
|
9442
9806
|
}
|
|
9443
9807
|
}
|
|
9444
9808
|
attachAdapterNotificationHandlers(serverName, client) {
|
|
9809
|
+
client.setNotificationHandler("notifications/resources/updated", (notification) => {
|
|
9810
|
+
const uri = notification.params.uri;
|
|
9811
|
+
const connection = this.connections.get(serverName);
|
|
9812
|
+
if (!connection || connection.client !== client || connection.status !== "connected") return;
|
|
9813
|
+
for (const registration of this.resourceUpdatedListeners.values()) {
|
|
9814
|
+
if (registration.serverName === serverName && registration.uri === uri) {
|
|
9815
|
+
try {
|
|
9816
|
+
registration.listener(serverName, uri);
|
|
9817
|
+
} catch {
|
|
9818
|
+
}
|
|
9819
|
+
}
|
|
9820
|
+
}
|
|
9821
|
+
});
|
|
9445
9822
|
client.setNotificationHandler(
|
|
9446
9823
|
SERVER_STREAM_RESULT_PATCH_METHOD,
|
|
9447
9824
|
{ params: serverStreamResultPatchNotificationSchema.shape.params },
|
|
@@ -9466,6 +9843,7 @@ var McpServerManager = class {
|
|
|
9466
9843
|
try {
|
|
9467
9844
|
this.touch(name);
|
|
9468
9845
|
this.incrementInFlight(name);
|
|
9846
|
+
await this.ensureListen(name, connection);
|
|
9469
9847
|
return await connection.client.getPrompt(
|
|
9470
9848
|
{ name: promptName, ...args ? { arguments: args } : {} },
|
|
9471
9849
|
this.getRequestOptions(name, signal)
|
|
@@ -9486,7 +9864,12 @@ var McpServerManager = class {
|
|
|
9486
9864
|
try {
|
|
9487
9865
|
this.touch(name);
|
|
9488
9866
|
this.incrementInFlight(name);
|
|
9489
|
-
|
|
9867
|
+
const refreshRead = await this.prepareResourceUse(name, uri, connection);
|
|
9868
|
+
const requestOptions = this.getRequestOptions(name, signal);
|
|
9869
|
+
return await connection.client.readResource(
|
|
9870
|
+
{ uri },
|
|
9871
|
+
refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions
|
|
9872
|
+
);
|
|
9490
9873
|
} finally {
|
|
9491
9874
|
this.decrementInFlight(name);
|
|
9492
9875
|
this.touch(name);
|
|
@@ -9546,6 +9929,7 @@ var McpServerManager = class {
|
|
|
9546
9929
|
const lateResults = await Promise.allSettled(lateNames.map((name) => this.close(name)));
|
|
9547
9930
|
const failures = [...pendingResults, ...results, ...lateResults].flatMap((result) => result.status === "rejected" ? [result.reason] : []).filter((error) => this.containsCleanupFailure(error));
|
|
9548
9931
|
this.uiStreamListeners.clear();
|
|
9932
|
+
this.resourceUpdatedListeners.clear();
|
|
9549
9933
|
this.acceptedUrlElicitations.clear();
|
|
9550
9934
|
this.pendingMetadataPublications.clear();
|
|
9551
9935
|
this.samplingConfig = void 0;
|
|
@@ -10320,7 +10704,14 @@ var UiResourceHandler = class {
|
|
|
10320
10704
|
...options.onNeedsAuth ? { onNeedsAuth: options.onNeedsAuth } : {}
|
|
10321
10705
|
},
|
|
10322
10706
|
serverName,
|
|
10323
|
-
(connection) =>
|
|
10707
|
+
async (connection) => {
|
|
10708
|
+
const refreshRead = await this.manager.prepareResourceUse?.(serverName, uri, connection);
|
|
10709
|
+
const requestOptions = this.manager.getRequestOptions(serverName, options.signal);
|
|
10710
|
+
return connection.client.readResource(
|
|
10711
|
+
{ uri },
|
|
10712
|
+
refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions
|
|
10713
|
+
);
|
|
10714
|
+
}
|
|
10324
10715
|
);
|
|
10325
10716
|
} finally {
|
|
10326
10717
|
this.manager.decrementInFlight(serverName);
|
|
@@ -10331,6 +10722,10 @@ var UiResourceHandler = class {
|
|
|
10331
10722
|
}
|
|
10332
10723
|
} catch (error) {
|
|
10333
10724
|
if (error instanceof UrlElicitationRequiredError || error instanceof SessionRecoveryAuthRequiredError) throw error;
|
|
10725
|
+
const inputRequired = getInputRequiredNeedsUiDetails(error, { server: serverName, resourceUri: uri });
|
|
10726
|
+
if (inputRequired) {
|
|
10727
|
+
throw new InputRequiredNeedsUiError(inputRequired, error instanceof Error ? error : void 0);
|
|
10728
|
+
}
|
|
10334
10729
|
const message = error instanceof Error ? error.message : String(error);
|
|
10335
10730
|
log.error("Failed to read resource", error instanceof Error ? error : void 0);
|
|
10336
10731
|
throw new ResourceFetchError(uri, message, {
|
|
@@ -10538,7 +10933,9 @@ function createMcpStatusSnapshot(state) {
|
|
|
10538
10933
|
servers.push({
|
|
10539
10934
|
name,
|
|
10540
10935
|
status,
|
|
10936
|
+
listenState: connection?.status === "connected" ? connection.listenState : "disconnected",
|
|
10541
10937
|
toolCount,
|
|
10938
|
+
...connection?.status === "connected" && connection.listenCatalogStale ? { catalogStale: true } : {},
|
|
10542
10939
|
...resourceCount !== void 0 ? { resourceCount } : {},
|
|
10543
10940
|
...status === "failed" && failedAgoSeconds !== null ? { failedAgoSeconds } : {},
|
|
10544
10941
|
disabled
|
|
@@ -10725,6 +11122,10 @@ async function initializeMcp(pi, ctx, owner = createMcpRuntimeOwner(), options =
|
|
|
10725
11122
|
notifyToolMetadataUpdated(state, serverName, reason);
|
|
10726
11123
|
updateStatusBar(state);
|
|
10727
11124
|
});
|
|
11125
|
+
manager.setListenStateChangedListener?.(() => {
|
|
11126
|
+
if (!owner.isActive()) return;
|
|
11127
|
+
updateStatusBar(state);
|
|
11128
|
+
});
|
|
10728
11129
|
owner.addCleanup(() => lifecycle.gracefulShutdown());
|
|
10729
11130
|
owner.addCleanup(() => {
|
|
10730
11131
|
if (state.uiServer) {
|
|
@@ -11087,6 +11488,7 @@ async function lazyConnect(state, serverName, signal) {
|
|
|
11087
11488
|
return false;
|
|
11088
11489
|
}
|
|
11089
11490
|
if (connection?.status === "connected") {
|
|
11491
|
+
await state.manager.ensureListen?.(serverName, connection);
|
|
11090
11492
|
updateServerMetadata(state, serverName);
|
|
11091
11493
|
markKeepAliveAfterConnect(state, serverName);
|
|
11092
11494
|
return true;
|
|
@@ -11209,6 +11611,13 @@ function canRenderPanel(ctx) {
|
|
|
11209
11611
|
async function showStatus(state, ctx) {
|
|
11210
11612
|
if (!ctx.hasUI) return;
|
|
11211
11613
|
const lines = ["MCP Server Status:", ""];
|
|
11614
|
+
if (!state.programmaticConfig) {
|
|
11615
|
+
lines.push(
|
|
11616
|
+
"Shared MCP config: .mcp.json for this project/team or ~/.config/mcp/mcp.json for all projects.",
|
|
11617
|
+
"Pi-owned files hold compatibility imports and adapter-specific overrides.",
|
|
11618
|
+
""
|
|
11619
|
+
);
|
|
11620
|
+
}
|
|
11212
11621
|
for (const name of Object.keys(state.config.mcpServers)) {
|
|
11213
11622
|
const definition = state.config.mcpServers[name];
|
|
11214
11623
|
if (isServerDisabled(definition)) {
|
|
@@ -11219,11 +11628,11 @@ async function showStatus(state, ctx) {
|
|
|
11219
11628
|
const metadata = state.toolMetadata.get(name);
|
|
11220
11629
|
const toolCount = metadata?.length ?? 0;
|
|
11221
11630
|
const failedAgo = getFailureAgeSeconds(state, name);
|
|
11222
|
-
let status = "not
|
|
11631
|
+
let status = "not listening (disconnected)";
|
|
11223
11632
|
let statusIcon = "\u25CB";
|
|
11224
11633
|
let failed = false;
|
|
11225
11634
|
if (connection?.status === "connected") {
|
|
11226
|
-
status = "connected";
|
|
11635
|
+
status = connection.listenState === "active" ? connection.listenCatalogStale ? "connected; listen active; catalog may be stale" : "connected; listen active" : connection.listenState === "dropped" ? "connected; catalog may be stale \u2014 will reconcile on next keep-alive or tool use" : connection.listenState === "re-establishing" ? "connected; re-establishing listen" : connection.listenState === "legacy" ? "connected; legacy notification path" : "connected; not listening for catalog updates";
|
|
11227
11636
|
statusIcon = "\u2713";
|
|
11228
11637
|
} else if (connection?.status === "needs-auth") {
|
|
11229
11638
|
status = "needs auth";
|
|
@@ -11234,14 +11643,17 @@ async function showStatus(state, ctx) {
|
|
|
11234
11643
|
statusIcon = "\u2717";
|
|
11235
11644
|
failed = true;
|
|
11236
11645
|
} else if (metadata !== void 0) {
|
|
11237
|
-
status = "cached";
|
|
11646
|
+
status = "cached; not listening (disconnected)";
|
|
11238
11647
|
}
|
|
11239
|
-
const toolSuffix = failed ? "" : ` (${toolCount} tools${status
|
|
11648
|
+
const toolSuffix = failed ? "" : ` (${toolCount} tools${status.startsWith("cached") ? ", cached" : ""})`;
|
|
11240
11649
|
lines.push(`${statusIcon} ${name}: ${status}${toolSuffix}`);
|
|
11241
11650
|
}
|
|
11651
|
+
if (state.config.settings?.freezeDirectTools === true) {
|
|
11652
|
+
lines.push("", "Direct tools frozen; active registrations may differ from current metadata.");
|
|
11653
|
+
}
|
|
11242
11654
|
if (Object.keys(state.config.mcpServers).length === 0) {
|
|
11243
11655
|
lines.push("No MCP servers configured");
|
|
11244
|
-
lines.push("Run /mcp setup to
|
|
11656
|
+
lines.push("Run /mcp setup to add a server to .mcp.json or ~/.config/mcp/mcp.json");
|
|
11245
11657
|
}
|
|
11246
11658
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
11247
11659
|
}
|
|
@@ -11530,15 +11942,17 @@ async function manageBearerToken(action, serverName, state, ctx) {
|
|
|
11530
11942
|
function buildSharedConfigNoticeLines(configOverridePath, cwd) {
|
|
11531
11943
|
const discovery = getMcpStandardConfigSummary(configOverridePath, cwd);
|
|
11532
11944
|
const onboardingState = loadOnboardingState();
|
|
11533
|
-
|
|
11945
|
+
const sharedSources = discovery.sources.filter(
|
|
11946
|
+
(source) => (source.id === "shared-project" || source.id === "shared-global") && source.serverCount > 0
|
|
11947
|
+
);
|
|
11948
|
+
if (sharedSources.length === 0 || onboardingState.sharedConfigHintShown) {
|
|
11534
11949
|
return { lines: [], fingerprint: null };
|
|
11535
11950
|
}
|
|
11536
|
-
const sharedSources = discovery.sources.filter((source) => source.kind === "shared" && source.serverCount > 0);
|
|
11537
11951
|
const sourceList = sharedSources.map((source) => source.path).join(", ");
|
|
11538
11952
|
return {
|
|
11539
11953
|
lines: [
|
|
11540
11954
|
`Using standard MCP config from ${sourceList}.`,
|
|
11541
|
-
"Pi only writes compatibility imports and adapter-specific overrides into Pi-owned files when needed."
|
|
11955
|
+
"Use .mcp.json for project/team config or ~/.config/mcp/mcp.json for all projects. Pi only writes compatibility imports and adapter-specific overrides into Pi-owned files when needed."
|
|
11542
11956
|
],
|
|
11543
11957
|
fingerprint: discovery.fingerprint
|
|
11544
11958
|
};
|
|
@@ -11559,34 +11973,34 @@ async function openMcpSetup(state, pi, ctx, configOverridePath, mode = "setup",
|
|
|
11559
11973
|
let configChanged = false;
|
|
11560
11974
|
const callbacks = {
|
|
11561
11975
|
previewImports: (imports) => previewCompatibilityImports(imports, configOverridePath),
|
|
11562
|
-
|
|
11563
|
-
previewRepoPrompt: () => {
|
|
11976
|
+
previewStarterConfig: (target) => previewStarterSharedConfig(target, ctx.cwd),
|
|
11977
|
+
previewRepoPrompt: (target) => {
|
|
11564
11978
|
const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
|
|
11565
11979
|
if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) return null;
|
|
11566
|
-
return previewSharedServerEntry(
|
|
11980
|
+
return previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
|
|
11567
11981
|
},
|
|
11568
|
-
previewKnownServer: (preset) => previewSharedServerEntry(
|
|
11982
|
+
previewKnownServer: (preset, target) => previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry),
|
|
11569
11983
|
adoptImports: async (imports) => {
|
|
11570
11984
|
const result = ensureCompatibilityImports(imports, configOverridePath);
|
|
11571
11985
|
if (result.added.length > 0) configChanged = true;
|
|
11572
11986
|
return result;
|
|
11573
11987
|
},
|
|
11574
|
-
|
|
11575
|
-
const path2 =
|
|
11988
|
+
scaffoldConfig: async (target) => {
|
|
11989
|
+
const path2 = writeStarterSharedConfig(target, ctx.cwd);
|
|
11576
11990
|
configChanged = true;
|
|
11577
11991
|
return { path: path2 };
|
|
11578
11992
|
},
|
|
11579
|
-
addRepoPrompt: async () => {
|
|
11993
|
+
addRepoPrompt: async (target) => {
|
|
11580
11994
|
const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
|
|
11581
11995
|
if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) {
|
|
11582
11996
|
throw new Error("RepoPrompt is not available to add from this setup screen.");
|
|
11583
11997
|
}
|
|
11584
|
-
const path2 = writeSharedServerEntry(
|
|
11998
|
+
const path2 = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
|
|
11585
11999
|
configChanged = true;
|
|
11586
12000
|
return { path: path2, serverName: repoPrompt.serverName };
|
|
11587
12001
|
},
|
|
11588
|
-
addKnownServer: async (preset) => {
|
|
11589
|
-
const path2 = writeSharedServerEntry(
|
|
12002
|
+
addKnownServer: async (preset, target) => {
|
|
12003
|
+
const path2 = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry);
|
|
11590
12004
|
configChanged = true;
|
|
11591
12005
|
return { path: path2, serverName: preset.name };
|
|
11592
12006
|
},
|
|
@@ -11689,6 +12103,18 @@ async function openMcpPanel(state, pi, ctx, configOverridePath, onDirectToolsCon
|
|
|
11689
12103
|
(tui, _theme, keybindings, done) => {
|
|
11690
12104
|
return createMcpPanel2(config, cache, provenanceMap, callbacks, tui, (result) => {
|
|
11691
12105
|
void (async () => {
|
|
12106
|
+
if (!result.cancelled && result.disabledChanges.size > 0) {
|
|
12107
|
+
for (const [serverName, disabled] of result.disabledChanges) {
|
|
12108
|
+
try {
|
|
12109
|
+
const override = writeProjectServerDisabledOverride(configPath, ctx.cwd, serverName, disabled);
|
|
12110
|
+
if (override.changed) {
|
|
12111
|
+
configChanged = true;
|
|
12112
|
+
}
|
|
12113
|
+
} catch (error) {
|
|
12114
|
+
ctx.ui.notify(`Failed to ${disabled ? "disable" : "enable"} server "${serverName}": ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
}
|
|
11692
12118
|
if (!result.cancelled && result.changes.size > 0) {
|
|
11693
12119
|
writeDirectToolsConfig(result.changes, provenanceMap, config);
|
|
11694
12120
|
await onDirectToolsConfigChanged?.(result.changes);
|
|
@@ -12171,7 +12597,7 @@ async function compactMcpResultOmission(summary, marker, maxBytes) {
|
|
|
12171
12597
|
}
|
|
12172
12598
|
async function summarizeMcpResult(result, raw, rawBytes) {
|
|
12173
12599
|
const { path: fullResultPath, error: resultWriteError } = await saveArtifact("mcp-result", raw);
|
|
12174
|
-
const record =
|
|
12600
|
+
const record = asRecord3(result);
|
|
12175
12601
|
const content = Array.isArray(record?.content) ? record.content : [];
|
|
12176
12602
|
const summary = {
|
|
12177
12603
|
omitted: true,
|
|
@@ -12198,7 +12624,7 @@ async function summarizeMcpResult(result, raw, rawBytes) {
|
|
|
12198
12624
|
}
|
|
12199
12625
|
function summarizeContent(content) {
|
|
12200
12626
|
const summaries = content.slice(0, CONTENT_SUMMARY_LIMIT).map((block) => {
|
|
12201
|
-
const record =
|
|
12627
|
+
const record = asRecord3(block);
|
|
12202
12628
|
if (!record) return { type: typeof block, omitted: true };
|
|
12203
12629
|
if (record.type === "text") {
|
|
12204
12630
|
const text = typeof record.text === "string" ? record.text : "";
|
|
@@ -12216,7 +12642,7 @@ function summarizeContent(content) {
|
|
|
12216
12642
|
return summaries;
|
|
12217
12643
|
}
|
|
12218
12644
|
function summarizeValue(value) {
|
|
12219
|
-
const record =
|
|
12645
|
+
const record = asRecord3(value);
|
|
12220
12646
|
if (!record) {
|
|
12221
12647
|
return { type: value === null ? "null" : typeof value, estimatedBytes: estimateValueBytes(value), omitted: true };
|
|
12222
12648
|
}
|
|
@@ -12230,7 +12656,7 @@ function summarizeValue(value) {
|
|
|
12230
12656
|
};
|
|
12231
12657
|
}
|
|
12232
12658
|
function summarizeStructuredContent(value) {
|
|
12233
|
-
const record =
|
|
12659
|
+
const record = asRecord3(value);
|
|
12234
12660
|
if (!record || Array.isArray(value)) return summarizeValue(value);
|
|
12235
12661
|
const keys = Object.keys(record);
|
|
12236
12662
|
const entries = Object.entries(record).slice(0, KEY_PREVIEW_LIMIT);
|
|
@@ -12260,7 +12686,7 @@ function estimateValueBytes(value, depth = 0) {
|
|
|
12260
12686
|
if (value === null || value === void 0) return 0;
|
|
12261
12687
|
if (typeof value === "string") return byteLength(value);
|
|
12262
12688
|
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return byteLength(String(value));
|
|
12263
|
-
const record =
|
|
12689
|
+
const record = asRecord3(value);
|
|
12264
12690
|
if (!record || depth >= 2) return 0;
|
|
12265
12691
|
const values = Array.isArray(value) ? value.slice(0, KEY_PREVIEW_LIMIT) : Object.values(record).slice(0, KEY_PREVIEW_LIMIT);
|
|
12266
12692
|
return values.reduce((total, item) => total + estimateValueBytes(item, depth + 1), 0);
|
|
@@ -12304,7 +12730,7 @@ async function discardArtifact(path2) {
|
|
|
12304
12730
|
} catch {
|
|
12305
12731
|
}
|
|
12306
12732
|
}
|
|
12307
|
-
function
|
|
12733
|
+
function asRecord3(value) {
|
|
12308
12734
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
12309
12735
|
}
|
|
12310
12736
|
function safeStringify(value) {
|
|
@@ -12357,6 +12783,8 @@ init_utils();
|
|
|
12357
12783
|
// packages/coding-agent/src/humain/mcp-adapter/vendor/host-html-template.ts
|
|
12358
12784
|
var DEFAULT_APP_BRIDGE_MODULE_URL = "/app-bridge.bundle.js";
|
|
12359
12785
|
var APP_SANDBOX = "allow-scripts allow-forms allow-modals allow-popups allow-downloads";
|
|
12786
|
+
var APP_PROXY_SANDBOX = `${APP_SANDBOX} allow-same-origin`;
|
|
12787
|
+
var APP_INNER_SANDBOX = APP_PROXY_SANDBOX;
|
|
12360
12788
|
function buildHostHtmlTemplate(input) {
|
|
12361
12789
|
const hostContext = input.hostContext ?? {};
|
|
12362
12790
|
const sessionToken = safeInlineJSON(input.sessionToken);
|
|
@@ -12369,6 +12797,9 @@ function buildHostHtmlTemplate(input) {
|
|
|
12369
12797
|
const requireToolConsent = safeInlineJSON(input.requireToolConsent);
|
|
12370
12798
|
const cacheToolConsent = safeInlineJSON(input.cacheToolConsent);
|
|
12371
12799
|
const moduleUrl = safeInlineJSON(input.appBridgeModuleUrl ?? DEFAULT_APP_BRIDGE_MODULE_URL);
|
|
12800
|
+
const sandboxProxyUrl = safeInlineJSON(input.sandboxProxyUrl);
|
|
12801
|
+
const resourceCsp = safeInlineJSON(input.resource.meta.csp);
|
|
12802
|
+
const resourcePermissions = safeInlineJSON(input.resource.meta.permissions);
|
|
12372
12803
|
return `<!doctype html>
|
|
12373
12804
|
<html lang="en">
|
|
12374
12805
|
<head>
|
|
@@ -12449,7 +12880,7 @@ function buildHostHtmlTemplate(input) {
|
|
|
12449
12880
|
</div>
|
|
12450
12881
|
</header>
|
|
12451
12882
|
<main>
|
|
12452
|
-
<iframe id="mcp-app" sandbox="${
|
|
12883
|
+
<iframe id="mcp-app" sandbox="${APP_PROXY_SANDBOX}" referrerpolicy="no-referrer"></iframe>
|
|
12453
12884
|
</main>
|
|
12454
12885
|
<div class="overlay" id="error-overlay">
|
|
12455
12886
|
<div class="panel">
|
|
@@ -12475,6 +12906,10 @@ function buildHostHtmlTemplate(input) {
|
|
|
12475
12906
|
const ALLOW_ATTRIBUTE = ${allowAttribute};
|
|
12476
12907
|
const REQUIRE_TOOL_CONSENT = ${requireToolConsent};
|
|
12477
12908
|
const CACHE_TOOL_CONSENT = ${cacheToolConsent};
|
|
12909
|
+
const SANDBOX_PROXY_URL = ${sandboxProxyUrl};
|
|
12910
|
+
const RESOURCE_CSP = ${resourceCsp};
|
|
12911
|
+
const RESOURCE_PERMISSIONS = ${resourcePermissions};
|
|
12912
|
+
const INNER_SANDBOX = ${safeInlineJSON(APP_INNER_SANDBOX)};
|
|
12478
12913
|
const STREAM_CONTEXT_KEY = "pi-mcp-adapter/stream";
|
|
12479
12914
|
const STREAM_PATCH_METHOD = "notifications/pi-mcp-adapter/ui-result-patch";
|
|
12480
12915
|
|
|
@@ -12485,6 +12920,7 @@ function buildHostHtmlTemplate(input) {
|
|
|
12485
12920
|
const errorOverlay = document.getElementById("error-overlay");
|
|
12486
12921
|
const completionOverlay = document.getElementById("completion-overlay");
|
|
12487
12922
|
const errorMessage = document.getElementById("error-message");
|
|
12923
|
+
const sandboxProxyOrigin = new URL(SANDBOX_PROXY_URL).origin;
|
|
12488
12924
|
|
|
12489
12925
|
document.getElementById("server-name").textContent = SERVER_NAME;
|
|
12490
12926
|
document.getElementById("tool-name").textContent = TOOL_NAME;
|
|
@@ -12542,10 +12978,41 @@ function buildHostHtmlTemplate(input) {
|
|
|
12542
12978
|
const bridge = new AppBridge(
|
|
12543
12979
|
null,
|
|
12544
12980
|
{ name: "pi", version: "1.0.0" },
|
|
12545
|
-
{
|
|
12981
|
+
{
|
|
12982
|
+
serverTools: {},
|
|
12983
|
+
openLinks: {},
|
|
12984
|
+
logging: {},
|
|
12985
|
+
updateModelContext: {},
|
|
12986
|
+
message: {},
|
|
12987
|
+
sandbox: {
|
|
12988
|
+
...(RESOURCE_CSP ? { csp: RESOURCE_CSP } : {}),
|
|
12989
|
+
...(RESOURCE_PERMISSIONS ? { permissions: RESOURCE_PERMISSIONS } : {}),
|
|
12990
|
+
},
|
|
12991
|
+
},
|
|
12546
12992
|
{ hostContext: HOST_CONTEXT }
|
|
12547
12993
|
);
|
|
12548
12994
|
|
|
12995
|
+
let sandboxResourceSent = false;
|
|
12996
|
+
bridge.onsandboxready = async () => {
|
|
12997
|
+
if (sandboxResourceSent) return;
|
|
12998
|
+
sandboxResourceSent = true;
|
|
12999
|
+
try {
|
|
13000
|
+
const response = await fetch("/ui-app?resource=" + encodeURIComponent(UI_RESOURCE_TOKEN), {
|
|
13001
|
+
headers: { Accept: "text/html" },
|
|
13002
|
+
});
|
|
13003
|
+
if (!response.ok) throw new Error("UI resource request failed: HTTP " + response.status);
|
|
13004
|
+
const html = await response.text();
|
|
13005
|
+
await bridge.sendSandboxResourceReady({
|
|
13006
|
+
html,
|
|
13007
|
+
sandbox: INNER_SANDBOX,
|
|
13008
|
+
...(RESOURCE_CSP ? { csp: RESOURCE_CSP } : {}),
|
|
13009
|
+
...(RESOURCE_PERMISSIONS ? { permissions: RESOURCE_PERMISSIONS } : {}),
|
|
13010
|
+
});
|
|
13011
|
+
} catch (error) {
|
|
13012
|
+
showError("Failed to load MCP App resource: " + String(error));
|
|
13013
|
+
}
|
|
13014
|
+
};
|
|
13015
|
+
|
|
12549
13016
|
bridge.oncalltool = async (params) => {
|
|
12550
13017
|
if (!consentGranted) {
|
|
12551
13018
|
const accepted = window.confirm("Allow this UI to call server tools for this session?");
|
|
@@ -12577,7 +13044,7 @@ function buildHostHtmlTemplate(input) {
|
|
|
12577
13044
|
// Also listen for raw postMessage events with custom types (notify, prompt, intent, etc.)
|
|
12578
13045
|
// These bypass the AppBridge protocol but are used by some MCP UI implementations
|
|
12579
13046
|
window.addEventListener("message", async (event) => {
|
|
12580
|
-
if (event.source !== iframe.contentWindow) return;
|
|
13047
|
+
if (event.source !== iframe.contentWindow || event.origin !== sandboxProxyOrigin) return;
|
|
12581
13048
|
const data = event.data;
|
|
12582
13049
|
if (!data || typeof data !== "object") return;
|
|
12583
13050
|
|
|
@@ -12641,6 +13108,12 @@ function buildHostHtmlTemplate(input) {
|
|
|
12641
13108
|
}
|
|
12642
13109
|
|
|
12643
13110
|
// Connect bridge BEFORE loading iframe to ensure we're listening when the app sends ui/initialize
|
|
13111
|
+
const sandboxMessageGuard = (event) => {
|
|
13112
|
+
if (event.source === iframe.contentWindow && event.origin !== sandboxProxyOrigin) {
|
|
13113
|
+
event.stopImmediatePropagation();
|
|
13114
|
+
}
|
|
13115
|
+
};
|
|
13116
|
+
window.addEventListener("message", sandboxMessageGuard, true);
|
|
12644
13117
|
try {
|
|
12645
13118
|
const transport = new PostMessageTransport(iframe.contentWindow, iframe.contentWindow);
|
|
12646
13119
|
await bridge.connect(transport);
|
|
@@ -12652,7 +13125,7 @@ function buildHostHtmlTemplate(input) {
|
|
|
12652
13125
|
const iframeLoaded = new Promise((resolve) => {
|
|
12653
13126
|
iframe.onload = resolve;
|
|
12654
13127
|
});
|
|
12655
|
-
iframe.src =
|
|
13128
|
+
iframe.src = SANDBOX_PROXY_URL;
|
|
12656
13129
|
await iframeLoaded;
|
|
12657
13130
|
|
|
12658
13131
|
const eventSource = new EventSource("/events?session=" + encodeURIComponent(SESSION_TOKEN));
|
|
@@ -12677,6 +13150,9 @@ function buildHostHtmlTemplate(input) {
|
|
|
12677
13150
|
showError("Failed to forward cancellation: " + String(error));
|
|
12678
13151
|
}
|
|
12679
13152
|
});
|
|
13153
|
+
eventSource.addEventListener("resource-updated", () => {
|
|
13154
|
+
setStatus("Resource updated on the server. Reopen this UI to load the latest version.");
|
|
13155
|
+
});
|
|
12680
13156
|
eventSource.addEventListener("result-patch", async (event) => {
|
|
12681
13157
|
try {
|
|
12682
13158
|
await bridge.notification({
|
|
@@ -12764,12 +13240,205 @@ function sanitizeCspDomains(domains) {
|
|
|
12764
13240
|
))];
|
|
12765
13241
|
}
|
|
12766
13242
|
function safeInlineJSON(value) {
|
|
12767
|
-
|
|
13243
|
+
const json = JSON.stringify(value);
|
|
13244
|
+
if (json === void 0) return "undefined";
|
|
13245
|
+
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
12768
13246
|
}
|
|
12769
13247
|
function escapeHtml2(value) {
|
|
12770
13248
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
12771
13249
|
}
|
|
12772
13250
|
|
|
13251
|
+
// packages/coding-agent/src/humain/mcp-adapter/vendor/sandbox-proxy-template.ts
|
|
13252
|
+
var SANDBOX_PROXY_SANDBOX = "allow-scripts allow-forms allow-modals allow-popups allow-downloads allow-same-origin";
|
|
13253
|
+
var SANDBOX_INNER_SANDBOX = SANDBOX_PROXY_SANDBOX;
|
|
13254
|
+
var SANDBOX_PROXY_PATH = "/sandbox";
|
|
13255
|
+
function buildSandboxProxyHtml(input) {
|
|
13256
|
+
const parentOrigin = safeInlineJSON2(input.parentOrigin);
|
|
13257
|
+
return `<!doctype html>
|
|
13258
|
+
<html lang="en">
|
|
13259
|
+
<head>
|
|
13260
|
+
<meta charset="utf-8" />
|
|
13261
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
13262
|
+
<title>MCP App Sandbox</title>
|
|
13263
|
+
<style>
|
|
13264
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: transparent; }
|
|
13265
|
+
iframe { display: block; width: 100%; height: 100%; border: 0; }
|
|
13266
|
+
</style>
|
|
13267
|
+
</head>
|
|
13268
|
+
<body>
|
|
13269
|
+
<iframe id="mcp-app" title="MCP App" sandbox="${SANDBOX_INNER_SANDBOX}" referrerpolicy="no-referrer"></iframe>
|
|
13270
|
+
<script>
|
|
13271
|
+
const EXPECTED_PARENT_ORIGIN = ${parentOrigin};
|
|
13272
|
+
const SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
|
|
13273
|
+
const SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
|
|
13274
|
+
const INNER_SANDBOX = ${safeInlineJSON2(SANDBOX_INNER_SANDBOX)};
|
|
13275
|
+
const MAX_PENDING_MESSAGES = 64;
|
|
13276
|
+
const innerFrame = document.getElementById("mcp-app");
|
|
13277
|
+
const pendingToInner = [];
|
|
13278
|
+
let innerReady = false;
|
|
13279
|
+
|
|
13280
|
+
const isObject = (value) => value !== null && typeof value === "object";
|
|
13281
|
+
const isMessageFromParent = (event) =>
|
|
13282
|
+
event.source === window.parent && event.origin === EXPECTED_PARENT_ORIGIN;
|
|
13283
|
+
const isMessageFromInner = (event) =>
|
|
13284
|
+
event.source === innerFrame.contentWindow && event.origin === window.location.origin;
|
|
13285
|
+
|
|
13286
|
+
const postToParent = (data) => {
|
|
13287
|
+
window.parent.postMessage(data, EXPECTED_PARENT_ORIGIN);
|
|
13288
|
+
};
|
|
13289
|
+
|
|
13290
|
+
const postToInner = (data) => {
|
|
13291
|
+
if (!innerReady || !innerFrame.contentWindow) {
|
|
13292
|
+
if (pendingToInner.length < MAX_PENDING_MESSAGES) pendingToInner.push(data);
|
|
13293
|
+
return;
|
|
13294
|
+
}
|
|
13295
|
+
innerFrame.contentWindow.postMessage(data, window.location.origin);
|
|
13296
|
+
};
|
|
13297
|
+
|
|
13298
|
+
const flushPendingMessages = () => {
|
|
13299
|
+
if (!innerFrame.contentWindow) return;
|
|
13300
|
+
innerReady = true;
|
|
13301
|
+
for (const data of pendingToInner.splice(0)) {
|
|
13302
|
+
innerFrame.contentWindow.postMessage(data, window.location.origin);
|
|
13303
|
+
}
|
|
13304
|
+
};
|
|
13305
|
+
|
|
13306
|
+
const sanitizeDomains = (domains) => {
|
|
13307
|
+
if (!Array.isArray(domains)) return [];
|
|
13308
|
+
return [...new Set(domains.filter((domain) =>
|
|
13309
|
+
typeof domain === "string" &&
|
|
13310
|
+
domain.length > 0 &&
|
|
13311
|
+
/^[\\x21-\\x7E]+$/.test(domain) &&
|
|
13312
|
+
!/[;'"]/.test(domain),
|
|
13313
|
+
))];
|
|
13314
|
+
};
|
|
13315
|
+
|
|
13316
|
+
const toDirective = (name, trustedSources, domains) =>
|
|
13317
|
+
name + " " + [...new Set([...trustedSources, ...domains])].join(" ");
|
|
13318
|
+
|
|
13319
|
+
const buildResourceCsp = (csp) => {
|
|
13320
|
+
const resourceDomains = sanitizeDomains(csp?.resourceDomains);
|
|
13321
|
+
const connectDomains = sanitizeDomains(csp?.connectDomains);
|
|
13322
|
+
const frameDomains = sanitizeDomains(csp?.frameDomains);
|
|
13323
|
+
const baseUriDomains = sanitizeDomains(csp?.baseUriDomains);
|
|
13324
|
+
return [
|
|
13325
|
+
"default-src 'none'",
|
|
13326
|
+
"sandbox " + INNER_SANDBOX,
|
|
13327
|
+
toDirective("script-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
13328
|
+
toDirective("style-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
13329
|
+
toDirective("font-src", ["'self'"], resourceDomains),
|
|
13330
|
+
toDirective("img-src", ["'self'", "data:"], resourceDomains),
|
|
13331
|
+
toDirective("media-src", ["'self'", "data:"], resourceDomains),
|
|
13332
|
+
connectDomains.length > 0 ? "connect-src " + connectDomains.join(" ") : "connect-src 'none'",
|
|
13333
|
+
frameDomains.length > 0 ? "frame-src " + frameDomains.join(" ") : "frame-src 'none'",
|
|
13334
|
+
"worker-src 'none'",
|
|
13335
|
+
"object-src 'none'",
|
|
13336
|
+
baseUriDomains.length > 0 ? "base-uri " + baseUriDomains.join(" ") : "base-uri 'self'",
|
|
13337
|
+
].join("; ");
|
|
13338
|
+
};
|
|
13339
|
+
|
|
13340
|
+
const escapeAttribute = (value) => value
|
|
13341
|
+
.replace(/&/g, "&")
|
|
13342
|
+
.replace(/"/g, """)
|
|
13343
|
+
.replace(/</g, "<")
|
|
13344
|
+
.replace(/>/g, ">");
|
|
13345
|
+
|
|
13346
|
+
const injectCsp = (html, csp) => {
|
|
13347
|
+
const meta = '<meta http-equiv="Content-Security-Policy" content="' + escapeAttribute(csp) + '">';
|
|
13348
|
+
const head = html.match(/<head(?:\\s[^>]*)?>/i);
|
|
13349
|
+
if (head && head.index !== undefined) {
|
|
13350
|
+
const end = head.index + head[0].length;
|
|
13351
|
+
return html.slice(0, end) + meta + html.slice(end);
|
|
13352
|
+
}
|
|
13353
|
+
return meta + html;
|
|
13354
|
+
};
|
|
13355
|
+
|
|
13356
|
+
const safeSandbox = (requested) => {
|
|
13357
|
+
const requestedTokens = typeof requested === "string" ? requested.split(/\\s+/) : [];
|
|
13358
|
+
const allowedTokens = new Set(INNER_SANDBOX.split(" "));
|
|
13359
|
+
const tokens = requestedTokens.filter((token) => allowedTokens.has(token));
|
|
13360
|
+
// Provider HTML needs both script execution and a real proxy-origin
|
|
13361
|
+
// storage context. Never accept popup escape or top-navigation tokens.
|
|
13362
|
+
return [...new Set([
|
|
13363
|
+
"allow-scripts",
|
|
13364
|
+
"allow-same-origin",
|
|
13365
|
+
...tokens,
|
|
13366
|
+
])].filter((token) => allowedTokens.has(token)).join(" ");
|
|
13367
|
+
};
|
|
13368
|
+
|
|
13369
|
+
const buildAllowAttribute = (permissions) => {
|
|
13370
|
+
if (!isObject(permissions) || Array.isArray(permissions)) return "";
|
|
13371
|
+
const allowed = [];
|
|
13372
|
+
if (permissions.camera) allowed.push("camera");
|
|
13373
|
+
if (permissions.microphone) allowed.push("microphone");
|
|
13374
|
+
if (permissions.geolocation) allowed.push("geolocation");
|
|
13375
|
+
if (permissions.clipboardWrite) allowed.push("clipboard-write");
|
|
13376
|
+
return allowed.join("; ");
|
|
13377
|
+
};
|
|
13378
|
+
|
|
13379
|
+
const loadResource = (params) => {
|
|
13380
|
+
if (!isObject(params) || typeof params.html !== "string" || !innerFrame.contentDocument) return;
|
|
13381
|
+
innerFrame.setAttribute("sandbox", safeSandbox(params.sandbox));
|
|
13382
|
+
const allow = buildAllowAttribute(params.permissions);
|
|
13383
|
+
if (allow) innerFrame.setAttribute("allow", allow);
|
|
13384
|
+
else innerFrame.removeAttribute("allow");
|
|
13385
|
+
innerReady = false;
|
|
13386
|
+
const document = innerFrame.contentDocument;
|
|
13387
|
+
document.open();
|
|
13388
|
+
document.write(injectCsp(params.html, buildResourceCsp(params.csp)));
|
|
13389
|
+
document.close();
|
|
13390
|
+
flushPendingMessages();
|
|
13391
|
+
};
|
|
13392
|
+
|
|
13393
|
+
window.addEventListener("message", (event) => {
|
|
13394
|
+
if (isMessageFromParent(event)) {
|
|
13395
|
+
const data = event.data;
|
|
13396
|
+
if (!isObject(data)) return;
|
|
13397
|
+
if (data.method === SANDBOX_RESOURCE_READY_METHOD) {
|
|
13398
|
+
loadResource(data.params);
|
|
13399
|
+
return;
|
|
13400
|
+
}
|
|
13401
|
+
if (data.method === SANDBOX_PROXY_READY_METHOD) return;
|
|
13402
|
+
if (typeof data.method === "string" && data.method.startsWith("ui/notifications/sandbox-")) return;
|
|
13403
|
+
postToInner(data);
|
|
13404
|
+
return;
|
|
13405
|
+
}
|
|
13406
|
+
|
|
13407
|
+
if (!isMessageFromInner(event)) return;
|
|
13408
|
+
const data = event.data;
|
|
13409
|
+
if (!isObject(data)) return;
|
|
13410
|
+
if (typeof data.method === "string" && data.method.startsWith("ui/notifications/sandbox-")) return;
|
|
13411
|
+
postToParent(data);
|
|
13412
|
+
});
|
|
13413
|
+
|
|
13414
|
+
postToParent({
|
|
13415
|
+
jsonrpc: "2.0",
|
|
13416
|
+
method: SANDBOX_PROXY_READY_METHOD,
|
|
13417
|
+
params: {},
|
|
13418
|
+
});
|
|
13419
|
+
</script>
|
|
13420
|
+
</body>
|
|
13421
|
+
</html>`;
|
|
13422
|
+
}
|
|
13423
|
+
function buildSandboxProxyCsp() {
|
|
13424
|
+
return [
|
|
13425
|
+
"default-src 'none'",
|
|
13426
|
+
"script-src 'unsafe-inline'",
|
|
13427
|
+
"style-src 'unsafe-inline'",
|
|
13428
|
+
"frame-src 'self'",
|
|
13429
|
+
"connect-src 'none'",
|
|
13430
|
+
"worker-src 'none'",
|
|
13431
|
+
"object-src 'none'",
|
|
13432
|
+
"base-uri 'none'",
|
|
13433
|
+
`sandbox ${SANDBOX_PROXY_SANDBOX}`
|
|
13434
|
+
].join("; ");
|
|
13435
|
+
}
|
|
13436
|
+
function safeInlineJSON2(value) {
|
|
13437
|
+
const json = JSON.stringify(value);
|
|
13438
|
+
if (json === void 0) return "undefined";
|
|
13439
|
+
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
13440
|
+
}
|
|
13441
|
+
|
|
12773
13442
|
// packages/coding-agent/src/humain/mcp-adapter/vendor/tool-approval.ts
|
|
12774
13443
|
import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
|
|
12775
13444
|
init_types();
|
|
@@ -12939,6 +13608,11 @@ async function startUiServer(options) {
|
|
|
12939
13608
|
const eventLog = [];
|
|
12940
13609
|
let latestCheckpointEventId;
|
|
12941
13610
|
let streamSummary;
|
|
13611
|
+
let server2 = null;
|
|
13612
|
+
let sandboxProxyServer = null;
|
|
13613
|
+
let sandboxProxyUrl = null;
|
|
13614
|
+
let closeTimer = null;
|
|
13615
|
+
let listenersClosed = false;
|
|
12942
13616
|
const sessionMessages = {
|
|
12943
13617
|
prompts: [],
|
|
12944
13618
|
notifications: [],
|
|
@@ -13086,6 +13760,32 @@ data: ${JSON.stringify(payload)}
|
|
|
13086
13760
|
clearInterval(watchdog);
|
|
13087
13761
|
watchdog = null;
|
|
13088
13762
|
};
|
|
13763
|
+
const closeListeners = () => {
|
|
13764
|
+
if (listenersClosed) return;
|
|
13765
|
+
listenersClosed = true;
|
|
13766
|
+
stopWatchdog();
|
|
13767
|
+
if (closeTimer) {
|
|
13768
|
+
clearTimeout(closeTimer);
|
|
13769
|
+
closeTimer = null;
|
|
13770
|
+
}
|
|
13771
|
+
try {
|
|
13772
|
+
server2?.close();
|
|
13773
|
+
} catch {
|
|
13774
|
+
}
|
|
13775
|
+
try {
|
|
13776
|
+
sandboxProxyServer?.close();
|
|
13777
|
+
} catch {
|
|
13778
|
+
}
|
|
13779
|
+
closeSse();
|
|
13780
|
+
};
|
|
13781
|
+
const scheduleListenerClose = () => {
|
|
13782
|
+
if (closeTimer || listenersClosed) return;
|
|
13783
|
+
closeTimer = setTimeout(() => {
|
|
13784
|
+
closeTimer = null;
|
|
13785
|
+
closeListeners();
|
|
13786
|
+
}, 20);
|
|
13787
|
+
closeTimer.unref();
|
|
13788
|
+
};
|
|
13089
13789
|
const markCompleted = (reason) => {
|
|
13090
13790
|
if (completed) return;
|
|
13091
13791
|
log.debug("Session completed", { reason });
|
|
@@ -13093,8 +13793,9 @@ data: ${JSON.stringify(payload)}
|
|
|
13093
13793
|
completed = true;
|
|
13094
13794
|
stopWatchdog();
|
|
13095
13795
|
options.onComplete?.(reason);
|
|
13796
|
+
scheduleListenerClose();
|
|
13096
13797
|
};
|
|
13097
|
-
const
|
|
13798
|
+
const hostServer = http.createServer(async (req, res) => {
|
|
13098
13799
|
try {
|
|
13099
13800
|
const method = req.method || "GET";
|
|
13100
13801
|
const hostHeader = req.headers.host;
|
|
@@ -13118,6 +13819,10 @@ data: ${JSON.stringify(payload)}
|
|
|
13118
13819
|
}
|
|
13119
13820
|
if (!validateTokenQuery(url, sessionToken, res)) return;
|
|
13120
13821
|
touchHeartbeat();
|
|
13822
|
+
if (!sandboxProxyUrl) {
|
|
13823
|
+
sendText(res, 503, "Sandbox proxy is not ready");
|
|
13824
|
+
return;
|
|
13825
|
+
}
|
|
13121
13826
|
const html = buildHostHtmlTemplate({
|
|
13122
13827
|
sessionToken,
|
|
13123
13828
|
uiResourceToken,
|
|
@@ -13128,7 +13833,8 @@ data: ${JSON.stringify(payload)}
|
|
|
13128
13833
|
allowAttribute: buildAllowAttribute(options.resource.meta.permissions),
|
|
13129
13834
|
requireToolConsent: options.consentManager.requiresPrompt(options.serverName),
|
|
13130
13835
|
cacheToolConsent: options.consentManager.shouldCacheConsent(),
|
|
13131
|
-
hostContext
|
|
13836
|
+
hostContext,
|
|
13837
|
+
sandboxProxyUrl
|
|
13132
13838
|
});
|
|
13133
13839
|
res.writeHead(200, {
|
|
13134
13840
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -13292,8 +13998,14 @@ data: ${JSON.stringify(payload)}
|
|
|
13292
13998
|
...options.onNeedsAuth ? { onNeedsAuth: options.onNeedsAuth } : {}
|
|
13293
13999
|
},
|
|
13294
14000
|
options.serverName,
|
|
13295
|
-
(conn) =>
|
|
13296
|
-
|
|
14001
|
+
async (conn) => {
|
|
14002
|
+
await options.manager.ensureListen?.(options.serverName, conn);
|
|
14003
|
+
return conn.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
|
|
14004
|
+
}
|
|
14005
|
+
) : await (async () => {
|
|
14006
|
+
await options.manager.ensureListen?.(options.serverName, connection);
|
|
14007
|
+
return connection.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
|
|
14008
|
+
})();
|
|
13297
14009
|
sendJson(res, 200, { ok: true, result });
|
|
13298
14010
|
} finally {
|
|
13299
14011
|
options.manager.decrementInFlight(options.serverName);
|
|
@@ -13393,13 +14105,6 @@ data: ${JSON.stringify(payload)}
|
|
|
13393
14105
|
const reason = typeof params.reason === "string" ? params.reason : "done";
|
|
13394
14106
|
markCompleted(reason);
|
|
13395
14107
|
sendJson(res, 200, { ok: true, result: {} });
|
|
13396
|
-
setTimeout(() => {
|
|
13397
|
-
try {
|
|
13398
|
-
server2.close();
|
|
13399
|
-
} catch {
|
|
13400
|
-
}
|
|
13401
|
-
closeSse();
|
|
13402
|
-
}, 20).unref();
|
|
13403
14108
|
return;
|
|
13404
14109
|
}
|
|
13405
14110
|
sendJson(res, 404, { ok: false, error: "Not found" });
|
|
@@ -13418,6 +14123,7 @@ data: ${JSON.stringify(payload)}
|
|
|
13418
14123
|
sendJson(res, status, { ok: false, error: wrapped.message });
|
|
13419
14124
|
}
|
|
13420
14125
|
});
|
|
14126
|
+
server2 = hostServer;
|
|
13421
14127
|
if (options.initialResultPromise) {
|
|
13422
14128
|
options.initialResultPromise.then(
|
|
13423
14129
|
(result) => pushEvent("tool-result", result),
|
|
@@ -13431,22 +14137,78 @@ data: ${JSON.stringify(payload)}
|
|
|
13431
14137
|
if (completed) return;
|
|
13432
14138
|
if (Date.now() - lastHeartbeatAt <= ABANDONED_GRACE_MS) return;
|
|
13433
14139
|
markCompleted("stale");
|
|
13434
|
-
try {
|
|
13435
|
-
server2.close();
|
|
13436
|
-
} catch {
|
|
13437
|
-
}
|
|
13438
|
-
closeSse();
|
|
13439
14140
|
}, WATCHDOG_INTERVAL_MS);
|
|
13440
14141
|
watchdog.unref();
|
|
13441
14142
|
return new Promise((resolve7, reject) => {
|
|
13442
14143
|
const candidates = resolvePortCandidates(options.port);
|
|
13443
14144
|
let candidateIndex = 0;
|
|
14145
|
+
const startSandboxProxy = (parentOrigin) => {
|
|
14146
|
+
const proxy = http.createServer((req, res) => {
|
|
14147
|
+
try {
|
|
14148
|
+
const method = req.method || "GET";
|
|
14149
|
+
const hostHeader = req.headers.host;
|
|
14150
|
+
const url = new URL(req.url || "/", `http://${hostHeader || "127.0.0.1"}`);
|
|
14151
|
+
if (hostHeader !== void 0 && !isAllowedHost(url.hostname)) {
|
|
14152
|
+
sendText(res, 403, "Invalid host");
|
|
14153
|
+
return;
|
|
14154
|
+
}
|
|
14155
|
+
if (method === "HEAD" && url.pathname === SANDBOX_PROXY_PATH) {
|
|
14156
|
+
res.writeHead(200, {
|
|
14157
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
14158
|
+
"Cache-Control": "no-store",
|
|
14159
|
+
"Content-Security-Policy": buildSandboxProxyCsp()
|
|
14160
|
+
});
|
|
14161
|
+
res.end();
|
|
14162
|
+
return;
|
|
14163
|
+
}
|
|
14164
|
+
if (method === "GET" && url.pathname === SANDBOX_PROXY_PATH) {
|
|
14165
|
+
res.writeHead(200, {
|
|
14166
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
14167
|
+
"Cache-Control": "no-store",
|
|
14168
|
+
"Content-Security-Policy": buildSandboxProxyCsp(),
|
|
14169
|
+
"Referrer-Policy": "no-referrer",
|
|
14170
|
+
"X-Content-Type-Options": "nosniff"
|
|
14171
|
+
});
|
|
14172
|
+
res.end(buildSandboxProxyHtml({ parentOrigin }));
|
|
14173
|
+
return;
|
|
14174
|
+
}
|
|
14175
|
+
sendJson(res, 404, { ok: false, error: "Not found" });
|
|
14176
|
+
} catch (error) {
|
|
14177
|
+
sendJson(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
14178
|
+
}
|
|
14179
|
+
});
|
|
14180
|
+
sandboxProxyServer = proxy;
|
|
14181
|
+
return new Promise((resolveProxy, rejectProxy) => {
|
|
14182
|
+
const onError2 = (error) => {
|
|
14183
|
+
proxy.off("listening", onListening2);
|
|
14184
|
+
rejectProxy(error);
|
|
14185
|
+
};
|
|
14186
|
+
const onListening2 = () => {
|
|
14187
|
+
proxy.off("error", onError2);
|
|
14188
|
+
const address = proxy.address();
|
|
14189
|
+
if (!address || typeof address === "string") {
|
|
14190
|
+
rejectProxy(new ServerError("invalid sandbox proxy address"));
|
|
14191
|
+
return;
|
|
14192
|
+
}
|
|
14193
|
+
resolveProxy(address.port);
|
|
14194
|
+
};
|
|
14195
|
+
proxy.once("error", onError2);
|
|
14196
|
+
proxy.listen(0, "127.0.0.1", onListening2);
|
|
14197
|
+
});
|
|
14198
|
+
};
|
|
13444
14199
|
const listen = () => {
|
|
13445
|
-
|
|
13446
|
-
|
|
14200
|
+
const candidate = candidates[candidateIndex];
|
|
14201
|
+
if (candidate === void 0) {
|
|
14202
|
+
const error = new ServerError("no UI server port candidates available");
|
|
14203
|
+
closeListeners();
|
|
14204
|
+
reject(error);
|
|
14205
|
+
return;
|
|
14206
|
+
}
|
|
14207
|
+
hostServer.once("error", onError);
|
|
14208
|
+
hostServer.listen(candidate, "127.0.0.1", onListening);
|
|
13447
14209
|
};
|
|
13448
14210
|
const onError = (error) => {
|
|
13449
|
-
|
|
14211
|
+
hostServer.off("listening", onListening);
|
|
13450
14212
|
if (error.code === "EADDRINUSE" && candidateIndex < candidates.length - 1) {
|
|
13451
14213
|
candidateIndex += 1;
|
|
13452
14214
|
listen();
|
|
@@ -13454,56 +14216,73 @@ data: ${JSON.stringify(payload)}
|
|
|
13454
14216
|
}
|
|
13455
14217
|
log.error("Failed to start server", error);
|
|
13456
14218
|
const port = candidates[candidateIndex];
|
|
14219
|
+
closeListeners();
|
|
13457
14220
|
reject(new ServerError(error.message, {
|
|
13458
14221
|
...port !== void 0 ? { port } : {},
|
|
13459
14222
|
cause: error
|
|
13460
14223
|
}));
|
|
13461
14224
|
};
|
|
13462
14225
|
const onListening = () => {
|
|
13463
|
-
|
|
13464
|
-
const address =
|
|
14226
|
+
hostServer.off("error", onError);
|
|
14227
|
+
const address = hostServer.address();
|
|
13465
14228
|
if (!address || typeof address === "string") {
|
|
13466
14229
|
const err = new ServerError("invalid address");
|
|
13467
14230
|
log.error("Invalid server address", err);
|
|
14231
|
+
closeListeners();
|
|
13468
14232
|
reject(err);
|
|
13469
14233
|
return;
|
|
13470
14234
|
}
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
14235
|
+
const parentOrigin = `http://localhost:${address.port}`;
|
|
14236
|
+
void startSandboxProxy(parentOrigin).then((proxyPort) => {
|
|
14237
|
+
if (completed || listenersClosed) {
|
|
14238
|
+
closeListeners();
|
|
14239
|
+
reject(new ServerError("UI session completed before sandbox proxy was ready"));
|
|
14240
|
+
return;
|
|
14241
|
+
}
|
|
14242
|
+
sandboxProxyUrl = `http://localhost:${proxyPort}${SANDBOX_PROXY_PATH}`;
|
|
14243
|
+
log.debug("Servers started", { port: address.port, proxyPort });
|
|
14244
|
+
rememberMoshiDiscoveryPort(address.port);
|
|
14245
|
+
const handle = {
|
|
14246
|
+
url: `http://localhost:${address.port}/?session=${sessionToken}`,
|
|
14247
|
+
port: address.port,
|
|
14248
|
+
proxyUrl: sandboxProxyUrl,
|
|
14249
|
+
proxyPort,
|
|
14250
|
+
sessionToken,
|
|
14251
|
+
serverName: options.serverName,
|
|
14252
|
+
toolName: options.toolName,
|
|
14253
|
+
close: (reason) => {
|
|
14254
|
+
markCompleted(reason ?? "closed");
|
|
14255
|
+
closeListeners();
|
|
14256
|
+
},
|
|
14257
|
+
sendToolInput: (args) => {
|
|
14258
|
+
pushEvent("tool-input", { arguments: args });
|
|
14259
|
+
},
|
|
14260
|
+
sendToolResult: (result) => {
|
|
14261
|
+
pushEvent("tool-result", result);
|
|
14262
|
+
},
|
|
14263
|
+
sendResultPatch: (result) => {
|
|
14264
|
+
pushEvent("result-patch", result);
|
|
14265
|
+
},
|
|
14266
|
+
sendToolCancelled: (reason) => {
|
|
14267
|
+
pushEvent("tool-cancelled", { reason });
|
|
14268
|
+
},
|
|
14269
|
+
sendResourceUpdated: (uri) => {
|
|
14270
|
+
pushEvent("resource-updated", { uri });
|
|
14271
|
+
},
|
|
14272
|
+
sendHostContext: (context) => {
|
|
14273
|
+
Object.assign(hostContext, context);
|
|
14274
|
+
pushEvent("host-context", context);
|
|
14275
|
+
},
|
|
14276
|
+
getSessionMessages: () => ({ ...sessionMessages }),
|
|
14277
|
+
getStreamSummary: () => streamSummary ? { ...streamSummary, phases: [...streamSummary.phases] } : void 0
|
|
14278
|
+
};
|
|
14279
|
+
resolve7(handle);
|
|
14280
|
+
}).catch((error) => {
|
|
14281
|
+
log.error("Failed to start sandbox proxy", error instanceof Error ? error : void 0);
|
|
14282
|
+
closeListeners();
|
|
14283
|
+
const wrapped = error instanceof ServerError ? error : new ServerError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
14284
|
+
reject(wrapped);
|
|
14285
|
+
});
|
|
13507
14286
|
};
|
|
13508
14287
|
listen();
|
|
13509
14288
|
});
|
|
@@ -13749,10 +14528,12 @@ function remoteAccessHint(opts) {
|
|
|
13749
14528
|
lines.push(`Browser launch failed: ${opts.openError}`);
|
|
13750
14529
|
}
|
|
13751
14530
|
if (opts.moshi) {
|
|
13752
|
-
lines.push(
|
|
14531
|
+
lines.push(
|
|
14532
|
+
`Moshi: tap the preview button in the terminal title bar and pick this MCP UI server (it must reach ports ${opts.port} and ${opts.proxyPort}).`
|
|
14533
|
+
);
|
|
13753
14534
|
}
|
|
13754
14535
|
lines.push(
|
|
13755
|
-
`SSH: run \`ssh -L ${opts.port}:127.0.0.1:${opts.port} <this-host>\` on your local machine, then open the URL above.`,
|
|
14536
|
+
`SSH: run \`ssh -L ${opts.port}:127.0.0.1:${opts.port} -L ${opts.proxyPort}:127.0.0.1:${opts.proxyPort} <this-host>\` on your local machine, then open the URL above.`,
|
|
13756
14537
|
"mosh can't forward ports - run that ssh command in a separate terminal."
|
|
13757
14538
|
);
|
|
13758
14539
|
return lines.join("\n");
|
|
@@ -13773,7 +14554,7 @@ async function maybeStartUiSession(state, request) {
|
|
|
13773
14554
|
const streamToken2 = streamMode2 ? randomUUID4() : void 0;
|
|
13774
14555
|
let active2 = true;
|
|
13775
14556
|
let nextStreamSequence2 = 0;
|
|
13776
|
-
const
|
|
14557
|
+
const cleanupStreamListener = () => {
|
|
13777
14558
|
if (streamToken2) {
|
|
13778
14559
|
state.manager.removeUiStreamListener(streamToken2);
|
|
13779
14560
|
}
|
|
@@ -13827,7 +14608,7 @@ async function maybeStartUiSession(state, request) {
|
|
|
13827
14608
|
},
|
|
13828
14609
|
close: () => {
|
|
13829
14610
|
active2 = false;
|
|
13830
|
-
|
|
14611
|
+
cleanupStreamListener();
|
|
13831
14612
|
}
|
|
13832
14613
|
};
|
|
13833
14614
|
}
|
|
@@ -13859,10 +14640,12 @@ async function maybeStartUiSession(state, request) {
|
|
|
13859
14640
|
let active = true;
|
|
13860
14641
|
let nextStreamSequence = 0;
|
|
13861
14642
|
let handle;
|
|
13862
|
-
const
|
|
14643
|
+
const resourceListenerToken = randomUUID4();
|
|
14644
|
+
const cleanupListeners = () => {
|
|
13863
14645
|
if (streamToken) {
|
|
13864
14646
|
state.manager.removeUiStreamListener(streamToken);
|
|
13865
14647
|
}
|
|
14648
|
+
state.manager.removeResourceUpdatedListener?.(resourceListenerToken);
|
|
13866
14649
|
};
|
|
13867
14650
|
handle = await startUiServer({
|
|
13868
14651
|
serverName: request.serverName,
|
|
@@ -13935,7 +14718,7 @@ ${update.summary}` }],
|
|
|
13935
14718
|
},
|
|
13936
14719
|
onComplete: (reason) => {
|
|
13937
14720
|
active = false;
|
|
13938
|
-
|
|
14721
|
+
cleanupListeners();
|
|
13939
14722
|
if (state.uiServer === handle) {
|
|
13940
14723
|
const messages = handle.getSessionMessages();
|
|
13941
14724
|
const stream = handle.getStreamSummary();
|
|
@@ -13982,6 +14765,15 @@ ${update.summary}` }],
|
|
|
13982
14765
|
handle.sendResultPatch(withStreamEnvelope(notification.result, streamId, nextStreamSequence));
|
|
13983
14766
|
});
|
|
13984
14767
|
}
|
|
14768
|
+
state.manager.registerResourceUpdatedListener?.(
|
|
14769
|
+
resourceListenerToken,
|
|
14770
|
+
request.serverName,
|
|
14771
|
+
request.uiResourceUri,
|
|
14772
|
+
(_serverName, uri) => {
|
|
14773
|
+
if (!active || state.uiServer !== handle) return;
|
|
14774
|
+
handle.sendResourceUpdated(uri);
|
|
14775
|
+
}
|
|
14776
|
+
);
|
|
13985
14777
|
state.uiServer = handle;
|
|
13986
14778
|
const viewerPref = process.env.MCP_UI_VIEWER?.toLowerCase();
|
|
13987
14779
|
const uiSuppressed = viewerPref === "none" || viewerPref === "off" || viewerPref === "disabled";
|
|
@@ -13991,7 +14783,11 @@ ${update.summary}` }],
|
|
|
13991
14783
|
if (uiSuppressed) {
|
|
13992
14784
|
viewer = "suppressed";
|
|
13993
14785
|
windowOpen = false;
|
|
13994
|
-
state.ui?.notify(
|
|
14786
|
+
state.ui?.notify(
|
|
14787
|
+
`MCP UI window suppressed (MCP_UI_VIEWER=${viewerPref}). Open manually: ${handle.url}
|
|
14788
|
+
If this session is remote, run ssh -L ${handle.port}:127.0.0.1:${handle.port} -L ${handle.proxyPort}:127.0.0.1:${handle.proxyPort} <this-host> first.`,
|
|
14789
|
+
"info"
|
|
14790
|
+
);
|
|
13995
14791
|
log.info("Suppressing MCP UI window (MCP_UI_VIEWER=" + viewerPref + ")", { url: handle.url });
|
|
13996
14792
|
} else {
|
|
13997
14793
|
const remoteLikely = remoteByEnv || await hasActiveRemoteLogin();
|
|
@@ -13999,6 +14795,7 @@ ${update.summary}` }],
|
|
|
13999
14795
|
state.ui?.notify(remoteAccessHint({
|
|
14000
14796
|
url: handle.url,
|
|
14001
14797
|
port: handle.port,
|
|
14798
|
+
proxyPort: handle.proxyPort,
|
|
14002
14799
|
moshi: await probeMoshiGateway(),
|
|
14003
14800
|
openError,
|
|
14004
14801
|
openedOnHost
|
|
@@ -14075,12 +14872,12 @@ ${update.summary}` }],
|
|
|
14075
14872
|
},
|
|
14076
14873
|
close: (reason) => {
|
|
14077
14874
|
active = false;
|
|
14078
|
-
|
|
14875
|
+
cleanupListeners();
|
|
14079
14876
|
handle.close(reason);
|
|
14080
14877
|
}
|
|
14081
14878
|
};
|
|
14082
14879
|
} catch (error) {
|
|
14083
|
-
if (error instanceof UrlElicitationRequiredError2 || isAbortError(error, runtimeSignal)) throw error;
|
|
14880
|
+
if (error instanceof UrlElicitationRequiredError2 || error instanceof InputRequiredNeedsUiError || isAbortError(error, runtimeSignal)) throw error;
|
|
14084
14881
|
const message = error instanceof Error ? error.message : String(error);
|
|
14085
14882
|
log.error("Failed to start UI session", error instanceof Error ? error : void 0);
|
|
14086
14883
|
state.ui?.notify(
|
|
@@ -14475,7 +15272,13 @@ function createDirectToolExecutor(getState, getInitPromise, spec) {
|
|
|
14475
15272
|
onNeedsAuth: recoverAuthConnection
|
|
14476
15273
|
},
|
|
14477
15274
|
spec.serverName,
|
|
14478
|
-
(conn) =>
|
|
15275
|
+
async (conn) => {
|
|
15276
|
+
const refreshRead = await state.manager.prepareResourceUse?.(spec.serverName, spec.resourceUri, conn);
|
|
15277
|
+
return conn.client.readResource(
|
|
15278
|
+
{ uri: spec.resourceUri },
|
|
15279
|
+
refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions
|
|
15280
|
+
);
|
|
15281
|
+
}
|
|
14479
15282
|
);
|
|
14480
15283
|
const content2 = transformMcpResourceContents(result2.contents ?? [], state.owner?.signal);
|
|
14481
15284
|
const guarded2 = await guardMcpOutput(content2.length > 0 ? content2 : [{ type: "text", text: "(empty resource)" }], {
|
|
@@ -14505,11 +15308,14 @@ function createDirectToolExecutor(getState, getInitPromise, spec) {
|
|
|
14505
15308
|
onNeedsAuth: recoverAuthConnection
|
|
14506
15309
|
},
|
|
14507
15310
|
spec.serverName,
|
|
14508
|
-
(conn) =>
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
15311
|
+
async (conn) => {
|
|
15312
|
+
await state.manager.ensureListen?.(spec.serverName, conn);
|
|
15313
|
+
return abortable(conn.client.callTool({
|
|
15314
|
+
name: spec.originalName,
|
|
15315
|
+
arguments: normalizedParams,
|
|
15316
|
+
_meta: uiSession?.requestMeta
|
|
15317
|
+
}, requestOptions), ownedSignal);
|
|
15318
|
+
}
|
|
14513
15319
|
);
|
|
14514
15320
|
uiSession?.sendToolResult(result);
|
|
14515
15321
|
if (result.isError) {
|
|
@@ -14581,6 +15387,14 @@ ${uiSummary.message}`,
|
|
|
14581
15387
|
details: { error: "url_elicitation_required", server: spec.serverName, action }
|
|
14582
15388
|
};
|
|
14583
15389
|
}
|
|
15390
|
+
const inputRequired = getInputRequiredNeedsUiDetails(error, spec.resourceUri ? { server: spec.serverName, resourceUri: spec.resourceUri } : { server: spec.serverName, tool: spec.originalName });
|
|
15391
|
+
if (inputRequired) {
|
|
15392
|
+
uiSession?.sendToolCancelled(inputRequired.message);
|
|
15393
|
+
return {
|
|
15394
|
+
content: [{ type: "text", text: inputRequired.message }],
|
|
15395
|
+
details: { ...inputRequired }
|
|
15396
|
+
};
|
|
15397
|
+
}
|
|
14584
15398
|
const message = error instanceof Error ? error.message : String(error);
|
|
14585
15399
|
const aborted = isAbortError(error, ownedSignal);
|
|
14586
15400
|
if (!aborted) {
|
|
@@ -15451,7 +16265,16 @@ function executeStatus(state) {
|
|
|
15451
16265
|
status = "cached";
|
|
15452
16266
|
}
|
|
15453
16267
|
const toolCount = status === "failed" ? 0 : metadata?.length ?? 0;
|
|
15454
|
-
|
|
16268
|
+
const listenState = connection?.status === "connected" ? connection.listenState : "disconnected";
|
|
16269
|
+
servers.push({
|
|
16270
|
+
name,
|
|
16271
|
+
status,
|
|
16272
|
+
listenState,
|
|
16273
|
+
...connection?.status === "connected" && connection.listenCatalogStale ? { catalogStale: true } : {},
|
|
16274
|
+
toolCount,
|
|
16275
|
+
failedAgo,
|
|
16276
|
+
...disabled ? { disabled: true } : {}
|
|
16277
|
+
});
|
|
15455
16278
|
}
|
|
15456
16279
|
const disabledCount = servers.filter((s) => s.disabled).length;
|
|
15457
16280
|
const enabledServers = servers.filter((s) => !s.disabled);
|
|
@@ -15467,7 +16290,8 @@ function executeStatus(state) {
|
|
|
15467
16290
|
continue;
|
|
15468
16291
|
}
|
|
15469
16292
|
if (server2.status === "connected") {
|
|
15470
|
-
|
|
16293
|
+
const listen = server2.listenState === "active" ? server2.catalogStale ? ", listen active, catalog may be stale" : ", listen active" : server2.listenState === "dropped" ? ", catalog may be stale; will reconcile on next keep-alive or tool use" : server2.listenState === "re-establishing" ? ", re-establishing listen" : server2.listenState === "legacy" ? ", legacy notification path" : ", not listening for catalog updates";
|
|
16294
|
+
text += `\u2713 ${server2.name} (${server2.toolCount} tools${listen})
|
|
15471
16295
|
`;
|
|
15472
16296
|
continue;
|
|
15473
16297
|
}
|
|
@@ -15477,7 +16301,7 @@ function executeStatus(state) {
|
|
|
15477
16301
|
continue;
|
|
15478
16302
|
}
|
|
15479
16303
|
if (server2.status === "cached") {
|
|
15480
|
-
text += `\u25CB ${server2.name} (${server2.toolCount} tools, cached)
|
|
16304
|
+
text += `\u25CB ${server2.name} (${server2.toolCount} tools, cached; not listening)
|
|
15481
16305
|
`;
|
|
15482
16306
|
continue;
|
|
15483
16307
|
}
|
|
@@ -15486,16 +16310,20 @@ function executeStatus(state) {
|
|
|
15486
16310
|
`;
|
|
15487
16311
|
continue;
|
|
15488
16312
|
}
|
|
15489
|
-
text += `\u25CB ${server2.name} (not
|
|
16313
|
+
text += `\u25CB ${server2.name} (not listening; disconnected)
|
|
15490
16314
|
`;
|
|
15491
16315
|
}
|
|
16316
|
+
const directToolsFrozen = state.config.settings?.freezeDirectTools === true;
|
|
16317
|
+
if (directToolsFrozen) {
|
|
16318
|
+
text += "\nDirect tools frozen; active registrations may differ from current metadata.\n";
|
|
16319
|
+
}
|
|
15492
16320
|
if (servers.length > 0) {
|
|
15493
16321
|
text += `
|
|
15494
16322
|
mcp({ server: "name" }) to list tools, mcp({ search: "..." }) to search`;
|
|
15495
16323
|
}
|
|
15496
16324
|
return {
|
|
15497
16325
|
content: [{ type: "text", text: text.trim() }],
|
|
15498
|
-
details: { mode: "status", servers, totalTools, connectedCount, disabledCount }
|
|
16326
|
+
details: { mode: "status", servers, totalTools, connectedCount, disabledCount, directToolsFrozen }
|
|
15499
16327
|
};
|
|
15500
16328
|
}
|
|
15501
16329
|
async function executeAuthStart(state, serverName, signal) {
|
|
@@ -15810,7 +16638,14 @@ Use mcp({ instructions: "${server2}" }) for the full text.`;
|
|
|
15810
16638
|
details: { mode: "list", server: server2, tools: [], count: 0, error: "not_connected", hasInstructions: Boolean(instructions) }
|
|
15811
16639
|
};
|
|
15812
16640
|
}
|
|
15813
|
-
|
|
16641
|
+
let cachedNote = "";
|
|
16642
|
+
if (connection?.status !== "connected") {
|
|
16643
|
+
if (connection?.status === "needs-auth") {
|
|
16644
|
+
cachedNote = ` (needs auth \u2014 run mcp({ action: "auth-start", server: "${server2}" }))`;
|
|
16645
|
+
} else {
|
|
16646
|
+
cachedNote = ` (lazy: tools from cache, not connected yet \u2014 mcp({ connect: "${server2}" }) to connect)`;
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
15814
16649
|
let text = `${server2} (${toolNames.length} tools${cachedNote}):
|
|
15815
16650
|
|
|
15816
16651
|
`;
|
|
@@ -16301,7 +17136,13 @@ async function executeCall(state, toolName, args, serverOverride, getPiTools, si
|
|
|
16301
17136
|
onNeedsAuth: recoverAuthConnection
|
|
16302
17137
|
},
|
|
16303
17138
|
serverName,
|
|
16304
|
-
(conn) =>
|
|
17139
|
+
async (conn) => {
|
|
17140
|
+
const refreshRead = await state.manager.prepareResourceUse?.(serverName, toolMeta.resourceUri, conn);
|
|
17141
|
+
return conn.client.readResource(
|
|
17142
|
+
{ uri: toolMeta.resourceUri },
|
|
17143
|
+
refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions
|
|
17144
|
+
);
|
|
17145
|
+
}
|
|
16305
17146
|
);
|
|
16306
17147
|
const content2 = transformMcpResourceContents(result2.contents ?? [], state.owner?.signal);
|
|
16307
17148
|
const guarded2 = await guardMcpOutput(content2.length > 0 ? content2 : [{ type: "text", text: "(empty resource)" }], outputGuardOptions);
|
|
@@ -16327,11 +17168,14 @@ async function executeCall(state, toolName, args, serverOverride, getPiTools, si
|
|
|
16327
17168
|
onNeedsAuth: recoverAuthConnection
|
|
16328
17169
|
},
|
|
16329
17170
|
serverName,
|
|
16330
|
-
(conn) =>
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
17171
|
+
async (conn) => {
|
|
17172
|
+
await state.manager.ensureListen?.(serverName, conn);
|
|
17173
|
+
return abortable(conn.client.callTool({
|
|
17174
|
+
name: toolMeta.originalName,
|
|
17175
|
+
arguments: normalizedArgs,
|
|
17176
|
+
_meta: uiSession?.requestMeta
|
|
17177
|
+
}, requestOptions), ownedSignal);
|
|
17178
|
+
}
|
|
16335
17179
|
);
|
|
16336
17180
|
if (toolMeta.uiResourceUri) {
|
|
16337
17181
|
uiSession?.sendToolResult(result);
|
|
@@ -16406,6 +17250,14 @@ ${formatSchema(toolMeta.inputSchema)}` : "";
|
|
|
16406
17250
|
details: { mode: "call", error: "url_elicitation_required", ...callIdentity, action }
|
|
16407
17251
|
};
|
|
16408
17252
|
}
|
|
17253
|
+
const inputRequired = getInputRequiredNeedsUiDetails(error, callIdentity);
|
|
17254
|
+
if (inputRequired) {
|
|
17255
|
+
uiSession?.sendToolCancelled(inputRequired.message);
|
|
17256
|
+
return {
|
|
17257
|
+
content: [{ type: "text", text: inputRequired.message }],
|
|
17258
|
+
details: { mode: "call", ...inputRequired }
|
|
17259
|
+
};
|
|
17260
|
+
}
|
|
16409
17261
|
const message = error instanceof Error ? error.message : String(error);
|
|
16410
17262
|
uiSession?.sendToolCancelled(message);
|
|
16411
17263
|
const schemaText = toolMeta.inputSchema ? `
|
|
@@ -16748,7 +17600,13 @@ function createMcpToolResultRenderer(renderOptions) {
|
|
|
16748
17600
|
function toolErrorOverride(details) {
|
|
16749
17601
|
if (details && typeof details === "object" && "error" in details) {
|
|
16750
17602
|
const code = details.error;
|
|
16751
|
-
if (code === "tool_error" || code === "call_failed") {
|
|
17603
|
+
if (code === "tool_error" || code === "call_failed" || code === INPUT_REQUIRED_NEEDS_UI) {
|
|
17604
|
+
return { isError: true };
|
|
17605
|
+
}
|
|
17606
|
+
}
|
|
17607
|
+
if (details && typeof details === "object" && details.mode === "script") {
|
|
17608
|
+
const calls = details.calls;
|
|
17609
|
+
if (Array.isArray(calls) && calls.some((call) => !!call && typeof call === "object" && call.error === INPUT_REQUIRED_NEEDS_UI)) {
|
|
16752
17610
|
return { isError: true };
|
|
16753
17611
|
}
|
|
16754
17612
|
}
|
|
@@ -17860,7 +18718,7 @@ function installMcpAdapter(pi, options) {
|
|
|
17860
18718
|
initPromise = null;
|
|
17861
18719
|
if (earlyConfig.settings?.freezeDirectTools === true) {
|
|
17862
18720
|
directToolsFrozen = true;
|
|
17863
|
-
logger.info(
|
|
18721
|
+
logger.info("MCP: direct tools frozen after initial sync \u2014 metadata can refresh without rebuilding the active tool surface");
|
|
17864
18722
|
}
|
|
17865
18723
|
}).catch(async (err) => {
|
|
17866
18724
|
if (!owner.isActive() || generation !== lifecycleGeneration) {
|
|
@@ -18053,7 +18911,6 @@ function installMcpAdapter(pi, options) {
|
|
|
18053
18911
|
case "reconnect":
|
|
18054
18912
|
commandOwner?.throwIfInactive();
|
|
18055
18913
|
await reconnectServers(state, commandCtx, targetServer);
|
|
18056
|
-
if (directToolsFrozen) syncToolSurface(commandCtx);
|
|
18057
18914
|
break;
|
|
18058
18915
|
case "tools":
|
|
18059
18916
|
await showTools(state, commandCtx);
|
|
@@ -18364,7 +19221,7 @@ function installMcpAdapter(pi, options) {
|
|
|
18364
19221
|
}
|
|
18365
19222
|
if (params.connect) {
|
|
18366
19223
|
const result = await executeConnect(state, params.connect, signal);
|
|
18367
|
-
syncToolSurface(_ctx);
|
|
19224
|
+
if (!directToolsFrozen) syncToolSurface(_ctx);
|
|
18368
19225
|
return result;
|
|
18369
19226
|
}
|
|
18370
19227
|
if (params.describe) {
|