@herbertgao/pi-extensions 2026.8.1 → 2026.8.2
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/README.md +5 -2
- package/node_modules/@czottmann/pi-automode/README.md +16 -2
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +35 -0
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +18 -9
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +6 -2
- package/node_modules/@czottmann/pi-automode/examples/automode.local.json +6 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +11 -7
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +100 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +32 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +78 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +16 -4
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +23 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +22 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +18 -2
- package/node_modules/@czottmann/pi-automode/package.json +4 -4
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +23 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +99 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +30 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +16 -0
- package/node_modules/@dietrichgebert/ponytail/AGENTS.md +32 -0
- package/node_modules/@dietrichgebert/ponytail/LICENSE +21 -0
- package/node_modules/@dietrichgebert/ponytail/README.es.md +298 -0
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +315 -0
- package/node_modules/@dietrichgebert/ponytail/README.md +351 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +21 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +62 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +115 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +44 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +21 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +96 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +169 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +98 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +130 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +85 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.ps1 +24 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.sh +18 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +77 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +26 -0
- package/node_modules/@dietrichgebert/ponytail/package.json +47 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +211 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/package.json +8 -0
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +60 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +120 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +41 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +44 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +50 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +71 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +57 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +10 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary.ts +6 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -83
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/markdown-enhance.ts +119 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +73 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-hover.ts +130 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-interaction.ts +65 -542
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-layout.ts +160 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-packets.ts +143 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-scroll.ts +184 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/diff-renderer.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-grouping.ts +9 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +91 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/agent-summary.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +2 -2
- package/node_modules/@herbertgao/pi-stash/README.md +9 -9
- package/node_modules/@herbertgao/pi-stash/package.json +1 -1
- package/node_modules/@herbertgao/pi-stash/src/index.ts +2 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +14 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +6 -2
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +85 -11
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +28 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +12 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +2 -0
- package/node_modules/@narumitw/pi-btw/LICENSE +21 -0
- package/node_modules/@narumitw/pi-btw/README.md +210 -0
- package/node_modules/@narumitw/pi-btw/package.json +53 -0
- package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +632 -0
- package/node_modules/@narumitw/pi-btw/src/btw.ts +996 -0
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +339 -0
- package/node_modules/@narumitw/pi-btw/src/index.ts +1 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +296 -0
- package/node_modules/@narumitw/pi-btw/src/settings.ts +237 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +238 -0
- package/node_modules/@narumitw/pi-btw/src/text.ts +10 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +620 -0
- package/node_modules/@pi-plugins/fast-mode/LICENSE +21 -0
- package/node_modules/@pi-plugins/fast-mode/README.md +92 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts +6 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +2026 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/package.json +73 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +59 -0
- package/node_modules/pi-mcp-adapter/README.md +62 -13
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +377 -0
- package/node_modules/pi-mcp-adapter/commands.ts +9 -7
- package/node_modules/pi-mcp-adapter/config.ts +110 -49
- package/node_modules/pi-mcp-adapter/direct-tools.ts +34 -19
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +37 -36
- package/node_modules/pi-mcp-adapter/errors.ts +21 -17
- package/node_modules/pi-mcp-adapter/host-html-template.ts +13 -7
- package/node_modules/pi-mcp-adapter/index.ts +37 -14
- package/node_modules/pi-mcp-adapter/init.ts +9 -7
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +2 -2
- package/node_modules/pi-mcp-adapter/lifecycle.ts +8 -8
- package/node_modules/pi-mcp-adapter/logger.ts +1 -1
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +36 -22
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +120 -2
- package/node_modules/pi-mcp-adapter/mcp-code.ts +13 -11
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +19 -13
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +15 -11
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +34 -15
- package/node_modules/pi-mcp-adapter/mcp-probe.ts +131 -34
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +2 -2
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +7 -4
- package/node_modules/pi-mcp-adapter/mcp-trace.ts +59 -64
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +34 -26
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +6 -4
- package/node_modules/pi-mcp-adapter/oauth-handler.ts +1 -1
- package/node_modules/pi-mcp-adapter/onboarding-state.ts +19 -11
- package/node_modules/pi-mcp-adapter/package.json +13 -8
- package/node_modules/pi-mcp-adapter/panel-keys.ts +20 -4
- package/node_modules/pi-mcp-adapter/prompts.ts +2 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +48 -11
- package/node_modules/pi-mcp-adapter/sampling-handler.ts +22 -15
- package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +165 -113
- package/node_modules/pi-mcp-adapter/session-recovery.ts +11 -12
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +3 -3
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +67 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -5
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +14 -2
- package/node_modules/pi-mcp-adapter/ts-shape.ts +9 -2
- package/node_modules/pi-mcp-adapter/types.ts +109 -22
- package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +40 -0
- package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +28 -19
- package/node_modules/pi-mcp-adapter/ui-server.ts +90 -58
- package/node_modules/pi-mcp-adapter/ui-session.ts +16 -14
- package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +4 -4
- package/node_modules/pi-mcp-adapter/utils.ts +23 -9
- package/package.json +22 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getToolUiResourceUri } from "
|
|
1
|
+
import { getToolUiResourceUri } from "./ui-app-bridge-helpers.ts";
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
3
|
import type { ToolMetadata, McpTool, McpResource, ServerEntry, ToolPrefix } from "./types.ts";
|
|
4
4
|
import { formatToolName, isToolAllowed, resolveToolPrefix } from "./types.ts";
|
|
@@ -44,14 +44,15 @@ export function buildToolMetadata(
|
|
|
44
44
|
} catch {
|
|
45
45
|
failedTools.push(tool.name);
|
|
46
46
|
}
|
|
47
|
+
const uiStreamMode = extractToolUiStreamMode(tool._meta);
|
|
47
48
|
metadata.push({
|
|
48
49
|
name,
|
|
49
50
|
originalName: tool.name,
|
|
50
51
|
description: tool.description ?? "",
|
|
51
|
-
inputSchema: tool.inputSchema,
|
|
52
|
-
uiResourceUri,
|
|
53
|
-
uiVisibility,
|
|
54
|
-
uiStreamMode:
|
|
52
|
+
...(tool.inputSchema !== undefined ? { inputSchema: tool.inputSchema } : {}),
|
|
53
|
+
...(uiResourceUri !== undefined ? { uiResourceUri } : {}),
|
|
54
|
+
...(uiVisibility !== undefined ? { uiVisibility } : {}),
|
|
55
|
+
...(uiStreamMode !== undefined ? { uiStreamMode } : {}),
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -41,6 +41,7 @@ class CollapsibleText implements Component {
|
|
|
41
41
|
private readonly fullText: Text;
|
|
42
42
|
private readonly footerText: Text;
|
|
43
43
|
private collapsedText: { charBudget: number; fullyIncluded: boolean; text: Text } | null = null;
|
|
44
|
+
private collapsedRender: { width: number; charBudget: number; lines: string[] } | null = null;
|
|
44
45
|
|
|
45
46
|
constructor(
|
|
46
47
|
private readonly text: string,
|
|
@@ -70,18 +71,29 @@ class CollapsibleText implements Component {
|
|
|
70
71
|
fullyIncluded: prefix === this.text,
|
|
71
72
|
text: new Text(prefix, 0, 0),
|
|
72
73
|
};
|
|
74
|
+
this.collapsedRender = null;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
const lines = this.collapsedText.text.render(width);
|
|
76
78
|
if (!this.preTruncated && this.collapsedText.fullyIncluded && lines.length <= this.maxCollapsedLines) return lines;
|
|
79
|
+
if (this.collapsedRender?.width === width && this.collapsedRender.charBudget === charBudget) {
|
|
80
|
+
return this.collapsedRender.lines;
|
|
81
|
+
}
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
const rendered = [
|
|
79
84
|
...lines.slice(0, this.maxCollapsedLines),
|
|
80
85
|
...this.footerText.render(width),
|
|
81
86
|
];
|
|
87
|
+
this.collapsedRender = { width, charBudget, lines: rendered };
|
|
88
|
+
return rendered;
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
invalidate(): void {
|
|
91
|
+
invalidate(): void {
|
|
92
|
+
this.fullText.invalidate();
|
|
93
|
+
this.footerText.invalidate();
|
|
94
|
+
this.collapsedText?.text.invalidate();
|
|
95
|
+
this.collapsedRender = null;
|
|
96
|
+
}
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
function truncateText(value: string, maxChars: number): string {
|
|
@@ -39,7 +39,10 @@ export function renderTsShape(inputSchema: unknown): string | null {
|
|
|
39
39
|
if (typeof schema.$ref !== "string") return null;
|
|
40
40
|
const match = schema.$ref.match(/^#\/(\$defs|definitions)\/([^/]+)$/);
|
|
41
41
|
if (!match) return null;
|
|
42
|
-
const
|
|
42
|
+
const definitionGroup = match[1];
|
|
43
|
+
const definitionName = match[2];
|
|
44
|
+
if (definitionGroup === undefined || definitionName === undefined) return null;
|
|
45
|
+
const definitionKey = `${definitionGroup}/${decodePointerToken(definitionName)}`;
|
|
43
46
|
if (!definitions.has(definitionKey)) return null;
|
|
44
47
|
return aliasFor(definitionKey);
|
|
45
48
|
}
|
|
@@ -108,7 +111,11 @@ function isSchema(value: unknown): value is Schema {
|
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
function hasUnsupportedKeyword(schema: Schema): boolean {
|
|
111
|
-
return UNSUPPORTED_KEYWORDS.some(keyword =>
|
|
114
|
+
return UNSUPPORTED_KEYWORDS.some(keyword => {
|
|
115
|
+
if (!Object.hasOwn(schema, keyword)) return false;
|
|
116
|
+
// `additionalProperties: false` is a closed-object constraint, not a shape that this renderer needs to understand.
|
|
117
|
+
return keyword !== "additionalProperties" || schema.additionalProperties !== false;
|
|
118
|
+
});
|
|
112
119
|
}
|
|
113
120
|
|
|
114
121
|
function decodePointerToken(token: string): string {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// types.ts - Core type definitions
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
ContentBlock as McpContentBlock,
|
|
4
|
+
ListPromptsResult,
|
|
5
|
+
ListResourcesResult,
|
|
6
|
+
ListToolsResult,
|
|
7
|
+
Transport as McpTransport,
|
|
8
|
+
} from "@modelcontextprotocol/client";
|
|
4
9
|
import type { TextContent, ImageContent } from "@earendil-works/pi-ai";
|
|
5
10
|
import type { UiStreamMode } from "./ui-stream-types.ts";
|
|
6
11
|
import type { UiToolVisibility } from "./ui-tool-visibility.ts";
|
|
@@ -48,36 +53,41 @@ export type ImportKind =
|
|
|
48
53
|
| "windsurf"
|
|
49
54
|
| "vscode";
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
type SdkTool = ListToolsResult["tools"][number];
|
|
57
|
+
type SdkResource = ListResourcesResult["resources"][number];
|
|
58
|
+
type SdkPrompt = ListPromptsResult["prompts"][number];
|
|
59
|
+
type SdkPromptArgument = NonNullable<SdkPrompt["arguments"]>[number];
|
|
60
|
+
|
|
61
|
+
// MCP wire definitions derive their field types from the installed SDK while
|
|
62
|
+
// retaining the adapter's deliberately smaller public surface.
|
|
52
63
|
export interface McpTool {
|
|
53
|
-
name:
|
|
54
|
-
title?:
|
|
55
|
-
description?:
|
|
56
|
-
inputSchema?:
|
|
57
|
-
_meta?:
|
|
64
|
+
name: SdkTool["name"];
|
|
65
|
+
title?: SdkTool["title"];
|
|
66
|
+
description?: SdkTool["description"];
|
|
67
|
+
inputSchema?: SdkTool["inputSchema"]; // JSON Schema
|
|
68
|
+
_meta?: SdkTool["_meta"];
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
// Resource definition from MCP server
|
|
61
71
|
export interface McpResource {
|
|
62
|
-
uri:
|
|
63
|
-
name:
|
|
64
|
-
description?:
|
|
65
|
-
mimeType?:
|
|
66
|
-
_meta?:
|
|
72
|
+
uri: SdkResource["uri"];
|
|
73
|
+
name: SdkResource["name"];
|
|
74
|
+
description?: SdkResource["description"];
|
|
75
|
+
mimeType?: SdkResource["mimeType"];
|
|
76
|
+
_meta?: SdkResource["_meta"];
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
export interface McpPromptArgument {
|
|
70
|
-
name:
|
|
71
|
-
description?:
|
|
72
|
-
required?:
|
|
80
|
+
name: SdkPromptArgument["name"];
|
|
81
|
+
description?: SdkPromptArgument["description"];
|
|
82
|
+
required?: SdkPromptArgument["required"];
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
export interface McpPrompt {
|
|
76
|
-
name:
|
|
77
|
-
title?:
|
|
78
|
-
description?:
|
|
79
|
-
arguments?:
|
|
80
|
-
_meta?:
|
|
86
|
+
name: SdkPrompt["name"];
|
|
87
|
+
title?: SdkPrompt["title"];
|
|
88
|
+
description?: SdkPrompt["description"];
|
|
89
|
+
arguments?: SdkPrompt["arguments"];
|
|
90
|
+
_meta?: SdkPrompt["_meta"];
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
export interface UiResourceMeta {
|
|
@@ -340,6 +350,8 @@ export interface OAuthConfig {
|
|
|
340
350
|
clientName?: string;
|
|
341
351
|
/** Client homepage URI for dynamic registration */
|
|
342
352
|
clientUri?: string;
|
|
353
|
+
/** Security-weakening escape hatch for known-misconfigured authorization servers. */
|
|
354
|
+
skipIssuerMetadataValidation?: boolean;
|
|
343
355
|
}
|
|
344
356
|
|
|
345
357
|
// Server configuration
|
|
@@ -387,6 +399,20 @@ export interface ServerEntry {
|
|
|
387
399
|
debug?: boolean; // Show server stderr (default: false)
|
|
388
400
|
/** Enable metadata-only JSONL protocol tracing for this server. */
|
|
389
401
|
trace?: boolean;
|
|
402
|
+
/** Force a specific HTTP MCP transport. Used by Agent Plugins, whose `type` declares the transport and forbids client fallback. */
|
|
403
|
+
httpTransport?: "streamable-http" | "sse";
|
|
404
|
+
/** Client-managed persistent data directory for Agent Plugin stdio servers. */
|
|
405
|
+
pluginDataDir?: string;
|
|
406
|
+
/** Treat env values as already resolved literals. Used for Agent Plugin env rules. */
|
|
407
|
+
literalEnv?: boolean;
|
|
408
|
+
/**
|
|
409
|
+
* MCP protocol era negotiation for this server. Defaults to `"legacy"`
|
|
410
|
+
* (byte-equivalent to pre-2026 behavior — no `versionNegotiation` is sent).
|
|
411
|
+
* `"auto"` offers the SDK's default 2026-07-28+ modern versions with
|
|
412
|
+
* legacy fallback; `"2026-07-28"` pins the connection to that revision
|
|
413
|
+
* with no fallback. `auto` and `2026-07-28` must be set explicitly.
|
|
414
|
+
*/
|
|
415
|
+
protocolVersion?: "legacy" | "auto" | "2026-07-28";
|
|
390
416
|
// Keep configuration visible without allowing connections or execution.
|
|
391
417
|
disabled?: boolean;
|
|
392
418
|
}
|
|
@@ -422,6 +448,23 @@ export interface McpTraceSettings {
|
|
|
422
448
|
maxEvents?: number;
|
|
423
449
|
}
|
|
424
450
|
|
|
451
|
+
export const MCP_TOOL_APPROVAL_REQUEST_EVENT = "pi-mcp-adapter:tool-approval-request" as const;
|
|
452
|
+
|
|
453
|
+
export type McpToolApprovalOrigin = "proxy" | "direct" | "script" | "resource" | "iframe";
|
|
454
|
+
export type McpToolApprovalDecision = "allow_once" | "allow_for_session" | "deny" | "abstain";
|
|
455
|
+
export type McpToolApprovalHandler = () => McpToolApprovalDecision | Promise<McpToolApprovalDecision>;
|
|
456
|
+
|
|
457
|
+
export interface McpToolApprovalRequest {
|
|
458
|
+
requestId: string;
|
|
459
|
+
serverName: string;
|
|
460
|
+
originalToolName: string;
|
|
461
|
+
prefixedToolName: string;
|
|
462
|
+
args: Record<string, unknown>;
|
|
463
|
+
origin: McpToolApprovalOrigin;
|
|
464
|
+
signal?: AbortSignal;
|
|
465
|
+
claim(handler: McpToolApprovalHandler): boolean;
|
|
466
|
+
}
|
|
467
|
+
|
|
425
468
|
export interface McpSettings {
|
|
426
469
|
toolPrefix?: ToolPrefix;
|
|
427
470
|
/** Show the plug prefix in MCP status and connection text (default: true). Set to false to disable it. */
|
|
@@ -430,6 +473,8 @@ export interface McpSettings {
|
|
|
430
473
|
mcpFooterStatus?: McpFooterStatus;
|
|
431
474
|
/** Discover detected host-specific MCP configs only when explicitly enabled. */
|
|
432
475
|
hostConfigDiscovery?: HostConfigDiscovery;
|
|
476
|
+
/** Agent Plugin package directories to load MCP servers from. */
|
|
477
|
+
agentPluginPaths?: string[];
|
|
433
478
|
idleTimeout?: number; // minutes, default 10, 0 to disable
|
|
434
479
|
requestTimeoutMs?: number; // milliseconds, overrides the SDK request timeout when > 0
|
|
435
480
|
directTools?: boolean;
|
|
@@ -622,6 +667,48 @@ export function resolveToolPrefix(
|
|
|
622
667
|
return definition?.toolPrefix ?? globalPrefix ?? "server";
|
|
623
668
|
}
|
|
624
669
|
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Resolve a configured MCP server name from a prefixed tool name.
|
|
673
|
+
*
|
|
674
|
+
* When the proxy tool is addressed with a fully-qualified name such as
|
|
675
|
+
* `searxng_searxng_web_search`, downstream policy systems (for example a
|
|
676
|
+
* permission gate) need to recover the owning server so they can evaluate
|
|
677
|
+
* server-scoped rules against the bare server name. This performs the inverse
|
|
678
|
+
* of {@link getServerPrefix}: it finds the longest configured server prefix
|
|
679
|
+
* that the tool name starts with and returns that server's name.
|
|
680
|
+
*
|
|
681
|
+
* @param toolName - the tool name as passed to the proxy `mcp({ tool })` call.
|
|
682
|
+
* @param serverNames - the configured MCP server names (keys of `mcpServers`).
|
|
683
|
+
* @param prefix - the active tool-prefix mode.
|
|
684
|
+
* @returns the resolved server name, or `undefined` when no prefix matches or
|
|
685
|
+
* the prefix mode is `"none"`.
|
|
686
|
+
*/
|
|
687
|
+
export function resolveServerFromToolName(
|
|
688
|
+
toolName: string,
|
|
689
|
+
serverNames: Iterable<string>,
|
|
690
|
+
prefix: ToolPrefix,
|
|
691
|
+
): string | undefined {
|
|
692
|
+
if (prefix === "none") return undefined;
|
|
693
|
+
const candidates: { name: string; prefix: string }[] = [];
|
|
694
|
+
for (const name of serverNames) {
|
|
695
|
+
const p = getServerPrefix(name, prefix);
|
|
696
|
+
if (p && toolName.startsWith(p + "_")) candidates.push({ name, prefix: p });
|
|
697
|
+
}
|
|
698
|
+
if (candidates.length === 0) return undefined;
|
|
699
|
+
candidates.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
700
|
+
const best = candidates[0];
|
|
701
|
+
// Fail safe: two distinct server names can normalize to the same prefix
|
|
702
|
+
// (e.g. my-server and my_server both -> my_server under "server" mode).
|
|
703
|
+
// When that happens the owning server is ambiguous; return undefined so a
|
|
704
|
+
// downstream permission gate falls back to its existing wildcard path rather
|
|
705
|
+
// than enforcing a rule against the wrong server.
|
|
706
|
+
if (candidates.some((c) => c.prefix === best!.prefix && c.name !== best!.name)) {
|
|
707
|
+
return undefined;
|
|
708
|
+
}
|
|
709
|
+
return best?.name;
|
|
710
|
+
}
|
|
711
|
+
|
|
625
712
|
export function sanitizePromptName(name: string): string {
|
|
626
713
|
const cleaned = name.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^[_-]+|[_-]+$/g, "");
|
|
627
714
|
if (!cleaned) return "prompt";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { UiResourcePermissions } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
4
|
+
|
|
5
|
+
const RESOURCE_URI_META_KEY = "ui/resourceUri";
|
|
6
|
+
|
|
7
|
+
export function getToolUiResourceUri(tool: { _meta?: Record<string, unknown> | undefined }): string | undefined {
|
|
8
|
+
const meta = tool._meta;
|
|
9
|
+
let resourceUri = getNestedResourceUri(meta);
|
|
10
|
+
if (resourceUri === undefined) {
|
|
11
|
+
resourceUri = meta?.[RESOURCE_URI_META_KEY];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof resourceUri === "string" && resourceUri.startsWith("ui://")) {
|
|
15
|
+
return resourceUri;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (resourceUri !== undefined) {
|
|
19
|
+
throw new Error(`Invalid UI resource URI: ${JSON.stringify(resourceUri)}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildAllowAttribute(permissions: UiResourcePermissions | undefined): string {
|
|
26
|
+
if (!permissions) return "";
|
|
27
|
+
|
|
28
|
+
const allowed: string[] = [];
|
|
29
|
+
if (permissions.camera) allowed.push("camera");
|
|
30
|
+
if (permissions.microphone) allowed.push("microphone");
|
|
31
|
+
if (permissions.geolocation) allowed.push("geolocation");
|
|
32
|
+
if (permissions.clipboardWrite) allowed.push("clipboard-write");
|
|
33
|
+
return allowed.join("; ");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getNestedResourceUri(meta: Record<string, unknown> | undefined): unknown {
|
|
37
|
+
const ui = meta?.ui;
|
|
38
|
+
if (!ui || typeof ui !== "object") return undefined;
|
|
39
|
+
return (ui as Record<string, unknown>).resourceUri;
|
|
40
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { RESOURCE_MIME_TYPE } from "
|
|
2
|
-
import { UrlElicitationRequiredError, type ReadResourceResult } from "@modelcontextprotocol/
|
|
1
|
+
import { RESOURCE_MIME_TYPE } from "./ui-app-bridge-helpers.ts";
|
|
2
|
+
import { UrlElicitationRequiredError, type ReadResourceResult } from "@modelcontextprotocol/client";
|
|
3
3
|
import { ResourceFetchError, ResourceParseError } from "./errors.ts";
|
|
4
4
|
import { logger } from "./logger.ts";
|
|
5
5
|
import { SessionRecoveryAuthRequiredError, withSessionRecovery, type SessionRecoveryDeps } from "./session-recovery.ts";
|
|
6
6
|
import type { McpServerManager } from "./server-manager.ts";
|
|
7
|
-
import { isServerDisabled, type McpConfig, type UiResourceContent, type UiResourceCsp, type UiResourceMeta } from "./types.ts";
|
|
7
|
+
import { isServerDisabled, type McpConfig, type UiResourceContent, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions } from "./types.ts";
|
|
8
8
|
|
|
9
9
|
interface ResourceContentRecord {
|
|
10
10
|
uri?: string;
|
|
@@ -23,7 +23,7 @@ interface ReadUiResourceOptions {
|
|
|
23
23
|
export class UiResourceHandler {
|
|
24
24
|
private log = logger.child({ component: "UiResourceHandler" });
|
|
25
25
|
|
|
26
|
-
constructor(private manager: McpServerManager, private config
|
|
26
|
+
constructor(private manager: McpServerManager, private config: McpConfig | undefined = undefined) {}
|
|
27
27
|
|
|
28
28
|
async readUiResource(serverName: string, uri: string, options: ReadUiResourceOptions = {}): Promise<UiResourceContent> {
|
|
29
29
|
const log = this.log.child({ server: serverName, uri });
|
|
@@ -45,7 +45,12 @@ export class UiResourceHandler {
|
|
|
45
45
|
this.manager.incrementInFlight(serverName);
|
|
46
46
|
try {
|
|
47
47
|
result = await withSessionRecovery(
|
|
48
|
-
{
|
|
48
|
+
{
|
|
49
|
+
manager: this.manager,
|
|
50
|
+
config,
|
|
51
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
52
|
+
...(options.onNeedsAuth ? { onNeedsAuth: options.onNeedsAuth } : {}),
|
|
53
|
+
},
|
|
49
54
|
serverName,
|
|
50
55
|
(connection) => connection.client.readResource({ uri }, this.manager.getRequestOptions(serverName, options.signal)),
|
|
51
56
|
);
|
|
@@ -62,7 +67,7 @@ export class UiResourceHandler {
|
|
|
62
67
|
log.error("Failed to read resource", error instanceof Error ? error : undefined);
|
|
63
68
|
throw new ResourceFetchError(uri, message, {
|
|
64
69
|
server: serverName,
|
|
65
|
-
|
|
70
|
+
...(error instanceof Error ? { cause: error } : {}),
|
|
66
71
|
});
|
|
67
72
|
}
|
|
68
73
|
|
|
@@ -97,10 +102,14 @@ export class UiResourceHandler {
|
|
|
97
102
|
html,
|
|
98
103
|
mimeType: mimeType ?? RESOURCE_MIME_TYPE,
|
|
99
104
|
meta: {
|
|
100
|
-
csp: contentMeta.csp ?? listMeta.csp,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
...((contentMeta.csp ?? listMeta.csp) !== undefined ? { csp: contentMeta.csp ?? listMeta.csp } : {}),
|
|
106
|
+
...((contentMeta.permissions ?? listMeta.permissions) !== undefined
|
|
107
|
+
? { permissions: contentMeta.permissions ?? listMeta.permissions }
|
|
108
|
+
: {}),
|
|
109
|
+
...((contentMeta.domain ?? listMeta.domain) !== undefined ? { domain: contentMeta.domain ?? listMeta.domain } : {}),
|
|
110
|
+
...((contentMeta.prefersBorder ?? listMeta.prefersBorder) !== undefined
|
|
111
|
+
? { prefersBorder: contentMeta.prefersBorder ?? listMeta.prefersBorder }
|
|
112
|
+
: {}),
|
|
104
113
|
},
|
|
105
114
|
};
|
|
106
115
|
}
|
|
@@ -128,7 +137,11 @@ function selectContent(result: ReadResourceResult, preferredUri: string): Resour
|
|
|
128
137
|
);
|
|
129
138
|
if (byHtmlMime) return byHtmlMime;
|
|
130
139
|
|
|
131
|
-
|
|
140
|
+
const firstContent = contents[0];
|
|
141
|
+
if (!firstContent) {
|
|
142
|
+
throw new Error(`No contents returned for UI resource: ${preferredUri}`);
|
|
143
|
+
}
|
|
144
|
+
return firstContent;
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
function isHtmlMimeType(mimeType: string): boolean {
|
|
@@ -166,10 +179,10 @@ function extractUiMeta(meta: Record<string, unknown> | undefined): UiResourceMet
|
|
|
166
179
|
|
|
167
180
|
const ui = isRecord(meta.ui) ? meta.ui : undefined;
|
|
168
181
|
const out: UiResourceMeta = {};
|
|
169
|
-
const openAiCsp =
|
|
182
|
+
const openAiCsp = Object.hasOwn(meta, "openai/widgetCSP")
|
|
170
183
|
? normalizeOpenAiWidgetCsp(meta["openai/widgetCSP"])
|
|
171
184
|
: undefined;
|
|
172
|
-
const hasStandardCsp = !!ui &&
|
|
185
|
+
const hasStandardCsp = !!ui && Object.hasOwn(ui, "csp");
|
|
173
186
|
const standardCspValue = hasStandardCsp ? ui.csp : undefined;
|
|
174
187
|
|
|
175
188
|
if (hasStandardCsp && !isRecord(standardCspValue)) {
|
|
@@ -183,7 +196,7 @@ function extractUiMeta(meta: Record<string, unknown> | undefined): UiResourceMet
|
|
|
183
196
|
out.csp = { ...openAiCsp, ...standardCsp };
|
|
184
197
|
if (isRecord(standardCspValue)) {
|
|
185
198
|
for (const [, standardField] of OPENAI_CSP_FIELD_MAPPINGS) {
|
|
186
|
-
if (
|
|
199
|
+
if (Object.hasOwn(standardCspValue, standardField) && !copyStringArray(standardCspValue[standardField])) {
|
|
187
200
|
delete out.csp[standardField];
|
|
188
201
|
}
|
|
189
202
|
}
|
|
@@ -192,7 +205,7 @@ function extractUiMeta(meta: Record<string, unknown> | undefined): UiResourceMet
|
|
|
192
205
|
}
|
|
193
206
|
|
|
194
207
|
if (ui && isRecord(ui.permissions)) {
|
|
195
|
-
out.permissions = ui.permissions as
|
|
208
|
+
out.permissions = ui.permissions as UiResourcePermissions;
|
|
196
209
|
}
|
|
197
210
|
if (ui && typeof ui.domain === "string") {
|
|
198
211
|
out.domain = ui.domain;
|
|
@@ -232,10 +245,6 @@ function copyStringArray(value: unknown): string[] | undefined {
|
|
|
232
245
|
: undefined;
|
|
233
246
|
}
|
|
234
247
|
|
|
235
|
-
function hasOwnProperty(record: Record<string, unknown>, property: string): boolean {
|
|
236
|
-
return Object.prototype.hasOwnProperty.call(record, property);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
248
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
240
249
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
241
250
|
}
|