@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
|
@@ -17,8 +17,8 @@ export interface McpUiErrorContext {
|
|
|
17
17
|
export class McpUiError extends Error {
|
|
18
18
|
readonly code: string;
|
|
19
19
|
readonly context: McpUiErrorContext;
|
|
20
|
-
readonly recoveryHint
|
|
21
|
-
readonly cause
|
|
20
|
+
readonly recoveryHint: string | undefined;
|
|
21
|
+
readonly cause: Error | undefined;
|
|
22
22
|
|
|
23
23
|
constructor(
|
|
24
24
|
message: string,
|
|
@@ -65,9 +65,9 @@ export class ResourceFetchError extends McpUiError {
|
|
|
65
65
|
) {
|
|
66
66
|
super(`Failed to fetch UI resource "${uri}": ${reason}`, {
|
|
67
67
|
code: "RESOURCE_FETCH_ERROR",
|
|
68
|
-
context: { uri, server: options
|
|
68
|
+
context: { uri, ...(options?.server !== undefined ? { server: options.server } : {}) },
|
|
69
69
|
recoveryHint: "Check that the MCP server is connected and the resource URI is valid.",
|
|
70
|
-
cause: options
|
|
70
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
71
71
|
});
|
|
72
72
|
this.name = "ResourceFetchError";
|
|
73
73
|
}
|
|
@@ -84,7 +84,11 @@ export class ResourceParseError extends McpUiError {
|
|
|
84
84
|
) {
|
|
85
85
|
super(`Invalid UI resource "${uri}": ${reason}`, {
|
|
86
86
|
code: "RESOURCE_PARSE_ERROR",
|
|
87
|
-
context: {
|
|
87
|
+
context: {
|
|
88
|
+
uri,
|
|
89
|
+
...(options?.server !== undefined ? { server: options.server } : {}),
|
|
90
|
+
...(options?.mimeType !== undefined ? { mimeType: options.mimeType } : {}),
|
|
91
|
+
},
|
|
88
92
|
recoveryHint: "Ensure the resource returns valid HTML with the correct MIME type.",
|
|
89
93
|
});
|
|
90
94
|
this.name = "ResourceParseError";
|
|
@@ -98,9 +102,9 @@ export class BridgeConnectionError extends McpUiError {
|
|
|
98
102
|
constructor(reason: string, options?: { session?: string; cause?: Error }) {
|
|
99
103
|
super(`AppBridge connection failed: ${reason}`, {
|
|
100
104
|
code: "BRIDGE_CONNECTION_ERROR",
|
|
101
|
-
context: { session: options
|
|
105
|
+
context: options?.session !== undefined ? { session: options.session } : {},
|
|
102
106
|
recoveryHint: "Check browser console for detailed errors. The iframe may have failed to load.",
|
|
103
|
-
cause: options
|
|
107
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
104
108
|
});
|
|
105
109
|
this.name = "BridgeConnectionError";
|
|
106
110
|
}
|
|
@@ -142,9 +146,9 @@ export class SessionError extends McpUiError {
|
|
|
142
146
|
) {
|
|
143
147
|
super(`Session error: ${reason}`, {
|
|
144
148
|
code: "SESSION_ERROR",
|
|
145
|
-
context: { session: options
|
|
149
|
+
context: options?.session !== undefined ? { session: options.session } : {},
|
|
146
150
|
recoveryHint: "The session may have expired or been closed. Try opening the UI again.",
|
|
147
|
-
cause: options
|
|
151
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
148
152
|
});
|
|
149
153
|
this.name = "SessionError";
|
|
150
154
|
}
|
|
@@ -160,9 +164,9 @@ export class ServerError extends McpUiError {
|
|
|
160
164
|
) {
|
|
161
165
|
super(`UI server error: ${reason}`, {
|
|
162
166
|
code: "SERVER_ERROR",
|
|
163
|
-
context: { port: options
|
|
167
|
+
context: options?.port !== undefined ? { port: options.port } : {},
|
|
164
168
|
recoveryHint: "Check if the port is available. Another process may be using it.",
|
|
165
|
-
cause: options
|
|
169
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
166
170
|
});
|
|
167
171
|
this.name = "ServerError";
|
|
168
172
|
}
|
|
@@ -179,9 +183,9 @@ export class McpServerError extends McpUiError {
|
|
|
179
183
|
) {
|
|
180
184
|
super(`MCP server "${server}" error: ${reason}`, {
|
|
181
185
|
code: "MCP_SERVER_ERROR",
|
|
182
|
-
context: { server, tool: options
|
|
186
|
+
context: { server, ...(options?.tool !== undefined ? { tool: options.tool } : {}) },
|
|
183
187
|
recoveryHint: "Check that the MCP server is running and responsive.",
|
|
184
|
-
cause: options
|
|
188
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
185
189
|
});
|
|
186
190
|
this.name = "McpServerError";
|
|
187
191
|
}
|
|
@@ -196,8 +200,8 @@ export function wrapError(error: unknown, context?: McpUiErrorContext): McpUiErr
|
|
|
196
200
|
return new McpUiError(error.message, {
|
|
197
201
|
code: error.code,
|
|
198
202
|
context: { ...error.context, ...context },
|
|
199
|
-
recoveryHint: error.recoveryHint,
|
|
200
|
-
cause: error.cause,
|
|
203
|
+
...(error.recoveryHint !== undefined ? { recoveryHint: error.recoveryHint } : {}),
|
|
204
|
+
...(error.cause !== undefined ? { cause: error.cause } : {}),
|
|
201
205
|
});
|
|
202
206
|
}
|
|
203
207
|
|
|
@@ -206,8 +210,8 @@ export function wrapError(error: unknown, context?: McpUiErrorContext): McpUiErr
|
|
|
206
210
|
|
|
207
211
|
return new McpUiError(message, {
|
|
208
212
|
code: "UNKNOWN_ERROR",
|
|
209
|
-
context,
|
|
210
|
-
cause,
|
|
213
|
+
...(context !== undefined ? { context } : {}),
|
|
214
|
+
...(cause !== undefined ? { cause } : {}),
|
|
211
215
|
});
|
|
212
216
|
}
|
|
213
217
|
|
|
@@ -2,9 +2,11 @@ import type { UiHostContext, UiResourceContent, UiResourceCsp } from "./types.ts
|
|
|
2
2
|
|
|
3
3
|
// Use locally bundled AppBridge to avoid CDN Zod bundling issues
|
|
4
4
|
const DEFAULT_APP_BRIDGE_MODULE_URL = "/app-bridge.bundle.js";
|
|
5
|
+
const APP_SANDBOX = "allow-scripts allow-forms allow-modals allow-popups allow-downloads";
|
|
5
6
|
|
|
6
7
|
export interface HostHtmlTemplateInput {
|
|
7
8
|
sessionToken: string;
|
|
9
|
+
uiResourceToken: string;
|
|
8
10
|
serverName: string;
|
|
9
11
|
toolName: string;
|
|
10
12
|
toolArgs: Record<string, unknown>;
|
|
@@ -20,6 +22,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
20
22
|
const hostContext = input.hostContext ?? {};
|
|
21
23
|
|
|
22
24
|
const sessionToken = safeInlineJSON(input.sessionToken);
|
|
25
|
+
const uiResourceToken = safeInlineJSON(input.uiResourceToken);
|
|
23
26
|
const toolArgs = safeInlineJSON(input.toolArgs);
|
|
24
27
|
const serverName = safeInlineJSON(input.serverName);
|
|
25
28
|
const toolName = safeInlineJSON(input.toolName);
|
|
@@ -109,7 +112,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
109
112
|
</div>
|
|
110
113
|
</header>
|
|
111
114
|
<main>
|
|
112
|
-
<iframe id="mcp-app" sandbox="
|
|
115
|
+
<iframe id="mcp-app" sandbox="${APP_SANDBOX}" referrerpolicy="no-referrer"></iframe>
|
|
113
116
|
</main>
|
|
114
117
|
<div class="overlay" id="error-overlay">
|
|
115
118
|
<div class="panel">
|
|
@@ -127,6 +130,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
127
130
|
import { AppBridge, PostMessageTransport } from ${moduleUrl};
|
|
128
131
|
|
|
129
132
|
const SESSION_TOKEN = ${sessionToken};
|
|
133
|
+
const UI_RESOURCE_TOKEN = ${uiResourceToken};
|
|
130
134
|
const SERVER_NAME = ${serverName};
|
|
131
135
|
const TOOL_NAME = ${toolName};
|
|
132
136
|
const TOOL_ARGS = ${toolArgs};
|
|
@@ -222,10 +226,10 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
222
226
|
}
|
|
223
227
|
const result = await post("/proxy/tools/call", params);
|
|
224
228
|
// Notify agent about the tool call
|
|
225
|
-
await post("/proxy/ui/
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
await post("/proxy/ui/generated-tool-call-intent", {
|
|
230
|
+
tool: params.name,
|
|
231
|
+
arguments: params.arguments,
|
|
232
|
+
isError: result.isError
|
|
229
233
|
}).catch(() => {});
|
|
230
234
|
return result;
|
|
231
235
|
};
|
|
@@ -236,6 +240,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
236
240
|
// Also listen for raw postMessage events with custom types (notify, prompt, intent, etc.)
|
|
237
241
|
// These bypass the AppBridge protocol but are used by some MCP UI implementations
|
|
238
242
|
window.addEventListener("message", async (event) => {
|
|
243
|
+
if (event.source !== iframe.contentWindow) return;
|
|
239
244
|
const data = event.data;
|
|
240
245
|
if (!data || typeof data !== "object") return;
|
|
241
246
|
|
|
@@ -300,7 +305,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
300
305
|
|
|
301
306
|
// Connect bridge BEFORE loading iframe to ensure we're listening when the app sends ui/initialize
|
|
302
307
|
try {
|
|
303
|
-
const transport = new PostMessageTransport(iframe.contentWindow,
|
|
308
|
+
const transport = new PostMessageTransport(iframe.contentWindow, iframe.contentWindow);
|
|
304
309
|
await bridge.connect(transport);
|
|
305
310
|
} catch (error) {
|
|
306
311
|
console.error("[host] Bridge connection failed:", error);
|
|
@@ -310,7 +315,7 @@ export function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {
|
|
|
310
315
|
const iframeLoaded = new Promise((resolve) => {
|
|
311
316
|
iframe.onload = resolve;
|
|
312
317
|
});
|
|
313
|
-
iframe.src = "/ui-app?
|
|
318
|
+
iframe.src = "/ui-app?resource=" + encodeURIComponent(UI_RESOURCE_TOKEN);
|
|
314
319
|
await iframeLoaded;
|
|
315
320
|
|
|
316
321
|
const eventSource = new EventSource("/events?session=" + encodeURIComponent(SESSION_TOKEN));
|
|
@@ -399,6 +404,7 @@ export function buildCspMetaContent(csp: UiResourceCsp | undefined): string {
|
|
|
399
404
|
|
|
400
405
|
return [
|
|
401
406
|
"default-src 'none'",
|
|
407
|
+
`sandbox ${APP_SANDBOX}`,
|
|
402
408
|
toDirective("script-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
403
409
|
toDirective("style-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
404
410
|
toDirective("font-src", ["'self'"], resourceDomains),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext, ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { AgentToolUpdateCallback, ExtensionAPI, ExtensionContext, ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
3
|
import type { DirectToolSpec, McpAdapterOptions, McpConfig, PromptMetadata } from "./types.ts";
|
|
4
4
|
import type { McpOAuthRuntime } from "./mcp-auth-flow.ts";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
|
+
import type { TSchema } from "typebox";
|
|
6
7
|
import { showStatus, showTools, showPrompts, reconnectServer, reconnectServers, authenticateServer, logoutServer, openMcpAuthPanel, openMcpPanel, openMcpSetup } from "./commands.ts";
|
|
7
8
|
import { cloneMcpConfig, loadMcpConfig, writeProjectServerDisabledOverride } from "./config.ts";
|
|
8
9
|
import { buildProxyDescription, createDirectToolExecutor, getMissingConfiguredDirectToolServers, resolveDirectTools } from "./direct-tools.ts";
|
|
@@ -23,9 +24,14 @@ export type { McpAdapterOptions } from "./types.ts";
|
|
|
23
24
|
export {
|
|
24
25
|
MCP_STATUS_EVENT,
|
|
25
26
|
MCP_STATUS_SNAPSHOT_VERSION,
|
|
27
|
+
MCP_TOOL_APPROVAL_REQUEST_EVENT,
|
|
26
28
|
type McpServerRuntimeStatus,
|
|
27
29
|
type McpServerStatusSnapshot,
|
|
28
30
|
type McpStatusSnapshot,
|
|
31
|
+
type McpToolApprovalDecision,
|
|
32
|
+
type McpToolApprovalHandler,
|
|
33
|
+
type McpToolApprovalOrigin,
|
|
34
|
+
type McpToolApprovalRequest,
|
|
29
35
|
} from "./types.ts";
|
|
30
36
|
|
|
31
37
|
const INIT_WAIT_TIMEOUT_MS = 30_000;
|
|
@@ -46,6 +52,18 @@ async function awaitWithTimeout<T>(promise: Promise<T>, timeoutMs: number): Prom
|
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
|
|
55
|
+
// TypeBox 1.x annotates raw objects passed to Type.Optional with an enumerable
|
|
56
|
+
// "~optional" key that survives serialization into provider tool schemas (Gemini
|
|
57
|
+
// rejects it with 400 INVALID_ARGUMENT). Prefer a real Type.Number schema; fall
|
|
58
|
+
// back to a plain raw schema for host TypeBox shims that omit Type.Number, since
|
|
59
|
+
// a property left out of `required` is optional by default.
|
|
60
|
+
function optionalNumber(options: { minimum?: number; description: string }): TSchema {
|
|
61
|
+
const number = (Type as { Number?: (opts: typeof options) => TSchema }).Number;
|
|
62
|
+
return typeof number === "function"
|
|
63
|
+
? Type.Optional(number(options))
|
|
64
|
+
: ({ type: "number", ...options } as unknown as TSchema);
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
50
68
|
const sessionConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
|
|
51
69
|
const programmaticConfig = sessionConfig !== undefined;
|
|
@@ -259,7 +277,10 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
259
277
|
function startInitialization(ctx: ExtensionContext, owner: McpRuntimeOwner, oauthRuntime: McpOAuthRuntime, generation: number, staleReason: string): Promise<void> {
|
|
260
278
|
const promise = initializeMcp(pi, ctx, owner, {
|
|
261
279
|
...(programmaticConfig || options.configPath !== undefined
|
|
262
|
-
? {
|
|
280
|
+
? {
|
|
281
|
+
...(earlyConfigPath !== undefined ? { configPath: earlyConfigPath } : {}),
|
|
282
|
+
...(sessionConfig !== undefined ? { config: sessionConfig } : {}),
|
|
283
|
+
}
|
|
263
284
|
: {}),
|
|
264
285
|
oauthRuntime,
|
|
265
286
|
statusEvents: pi.events,
|
|
@@ -427,7 +448,11 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
427
448
|
}
|
|
428
449
|
|
|
429
450
|
const [, subcommand, argumentPrefix] = argumentMatch;
|
|
430
|
-
if (
|
|
451
|
+
if (
|
|
452
|
+
(subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable")
|
|
453
|
+
|| argumentPrefix === undefined
|
|
454
|
+
|| !state
|
|
455
|
+
) return null;
|
|
431
456
|
|
|
432
457
|
const servers = Object.keys(state.config.mcpServers)
|
|
433
458
|
.filter((serverName) => serverName.startsWith(argumentPrefix.trimStart()))
|
|
@@ -609,17 +634,16 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
609
634
|
|
|
610
635
|
if (earlyConfig.settings?.scriptMode !== false) {
|
|
611
636
|
(pi.registerTool as (tool: unknown) => unknown)({
|
|
612
|
-
name: "
|
|
637
|
+
name: "mcpScript",
|
|
613
638
|
label: "MCP Script",
|
|
614
639
|
description: "Run trusted JavaScript that makes multiple MCP tool calls in one request — loop, filter, chain, or fan out between calls. For a single MCP call, search, describe, status check, or auth action, use the mcp tool instead. Discover with await tools.search({ query }) — resolves to { items: [{ path, name, server, description? }], total, hasMore, nextOffset }, not an { ok, data } envelope. Inspect with await tools.describe({ path }) — resolves to the tool descriptor with inputTypeScript, or { path, error: { code, message, suggestions } }. Then call tools.call(path, args) — resolves to { ok: true, data } or { ok: false, error: { code, message } } — or use direct flat calls when the name is already known; use emit(value) for user-visible output. Load the mcp-scripting skill for the full workflow guide.",
|
|
615
640
|
promptSnippet: "Batch multiple MCP tool calls in one JavaScript request (loop, filter, chain)",
|
|
616
641
|
parameters: Type.Object({
|
|
617
642
|
code: Type.String({ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value)." }),
|
|
618
|
-
|
|
619
|
-
timeoutMs: Type.Optional({ type: "number", minimum: 1, description: "Execution timeout in milliseconds (default: 30000)" } as any),
|
|
643
|
+
timeoutMs: optionalNumber({ minimum: 1, description: "Execution timeout in milliseconds (default: 30000)" }),
|
|
620
644
|
}),
|
|
621
645
|
renderResult: renderMcpToolResult,
|
|
622
|
-
async execute(_toolCallId, params: { code: string; timeoutMs?: number }, signal) {
|
|
646
|
+
async execute(_toolCallId: string, params: { code: string; timeoutMs?: number }, signal: AbortSignal | undefined) {
|
|
623
647
|
const executeOwner = currentOwner;
|
|
624
648
|
if (!state && initPromise) {
|
|
625
649
|
try {
|
|
@@ -675,14 +699,13 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
675
699
|
search: Type.Optional(Type.String({ description: "Search tools by name/description" })),
|
|
676
700
|
regex: Type.Optional(Type.Boolean({ description: "Treat search as regex (default: substring match)" })),
|
|
677
701
|
includeSchemas: Type.Optional(Type.Boolean({ description: "Include parameter schemas in search results (default: true)" })),
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
offset: Type.Optional({ type: "number", minimum: 0, description: "Search result offset (default: 0)" } as any),
|
|
702
|
+
limit: optionalNumber({ minimum: 1, description: "Maximum search results to return (default: 12)" }),
|
|
703
|
+
offset: optionalNumber({ minimum: 0, description: "Search result offset (default: 0)" }),
|
|
681
704
|
server: Type.Optional(Type.String({ description: "Filter to specific server (also disambiguates tool calls)" })),
|
|
682
705
|
action: Type.Optional(Type.String({ description: "Action: 'ui-messages', 'auth-start', or 'auth-complete'" })),
|
|
683
706
|
}),
|
|
684
707
|
renderResult: renderMcpToolResult,
|
|
685
|
-
async execute(_toolCallId, params: {
|
|
708
|
+
async execute(_toolCallId: string, params: {
|
|
686
709
|
tool?: string;
|
|
687
710
|
args?: string | Record<string, unknown>;
|
|
688
711
|
connect?: string;
|
|
@@ -695,7 +718,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
695
718
|
offset?: number;
|
|
696
719
|
server?: string;
|
|
697
720
|
action?: string;
|
|
698
|
-
}, signal, _onUpdate, _ctx) {
|
|
721
|
+
}, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, _ctx: ExtensionContext) {
|
|
699
722
|
const executeOwner = currentOwner;
|
|
700
723
|
let parsedArgs: Record<string, unknown> | undefined;
|
|
701
724
|
if (params.args !== undefined && params.args !== "") {
|
|
@@ -849,8 +872,8 @@ export function createMcpAdapter(options: McpAdapterOptions = {}) {
|
|
|
849
872
|
const factoryConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
|
|
850
873
|
return function mcpAdapter(pi: ExtensionAPI) {
|
|
851
874
|
installMcpAdapter(pi, {
|
|
852
|
-
configPath: options.configPath,
|
|
853
|
-
|
|
875
|
+
...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
|
|
876
|
+
...(factoryConfig !== undefined ? { config: cloneMcpConfig(factoryConfig) } : {}),
|
|
854
877
|
});
|
|
855
878
|
};
|
|
856
879
|
}
|
|
@@ -124,7 +124,7 @@ export async function initializeMcp(
|
|
|
124
124
|
if (config.settings?.sampling !== false && (hasUI || samplingAutoApprove)) {
|
|
125
125
|
manager.setSamplingConfig({
|
|
126
126
|
autoApprove: samplingAutoApprove,
|
|
127
|
-
ui,
|
|
127
|
+
...(ui !== undefined ? { ui } : {}),
|
|
128
128
|
modelRegistry,
|
|
129
129
|
getCurrentModel: () => owner.isActive() ? ctx.model : undefined,
|
|
130
130
|
getSignal: () => owner.isActive()
|
|
@@ -166,6 +166,7 @@ export async function initializeMcp(
|
|
|
166
166
|
failureTracker,
|
|
167
167
|
failureMessages,
|
|
168
168
|
approvedToolCalls,
|
|
169
|
+
approvalEvents: pi.events,
|
|
169
170
|
uiResourceHandler,
|
|
170
171
|
consentManager,
|
|
171
172
|
uiServer: null,
|
|
@@ -175,12 +176,12 @@ export async function initializeMcp(
|
|
|
175
176
|
await openUrl(pi, url, process.env.BROWSER, owner.signal);
|
|
176
177
|
owner.throwIfInactive();
|
|
177
178
|
},
|
|
178
|
-
ui,
|
|
179
|
+
...(ui !== undefined ? { ui } : {}),
|
|
179
180
|
sendMessage: (message, options) => {
|
|
180
181
|
if (!owner.isActive()) return;
|
|
181
182
|
pi.sendMessage(message as unknown as Parameters<typeof pi.sendMessage>[0], options);
|
|
182
183
|
},
|
|
183
|
-
statusEvents: options.statusEvents,
|
|
184
|
+
...(options.statusEvents !== undefined ? { statusEvents: options.statusEvents } : {}),
|
|
184
185
|
};
|
|
185
186
|
if (ownsOAuthRuntime) owner.addCleanup(() => shutdownOAuth(oauthRuntime));
|
|
186
187
|
manager.setMetadataListChangedListener?.((serverName, reason) => {
|
|
@@ -345,8 +346,9 @@ export async function initializeMcp(
|
|
|
345
346
|
missingCacheServers.filter(name => !results.some(r => r.name === name && r.connection)),
|
|
346
347
|
10,
|
|
347
348
|
async (name) => {
|
|
348
|
-
const definition = config.mcpServers[name];
|
|
349
349
|
try {
|
|
350
|
+
const definition = config.mcpServers[name];
|
|
351
|
+
if (!definition) throw new Error(`MCP server "${name}" is not configured`);
|
|
350
352
|
const connection = await manager.connect(name, definition, runtimeSignal);
|
|
351
353
|
if (connection.status === "needs-auth") {
|
|
352
354
|
return { name, ok: false };
|
|
@@ -412,8 +414,8 @@ export async function initializeMcp(
|
|
|
412
414
|
|
|
413
415
|
export function markKeepAliveAfterConnect(state: McpExtensionState, serverName: string): void {
|
|
414
416
|
const definition = state.config.mcpServers[serverName];
|
|
415
|
-
if (isServerDisabled(definition)) return;
|
|
416
|
-
if ((definition
|
|
417
|
+
if (!definition || isServerDisabled(definition)) return;
|
|
418
|
+
if ((definition.lifecycle ?? "lazy") === "lazy-keep-alive") {
|
|
417
419
|
state.lifecycle.markKeepAlive(serverName, definition);
|
|
418
420
|
}
|
|
419
421
|
}
|
|
@@ -485,7 +487,7 @@ export function updateMetadataCache(
|
|
|
485
487
|
tools,
|
|
486
488
|
resources,
|
|
487
489
|
...(prompts !== undefined ? { prompts } : {}),
|
|
488
|
-
instructions: connection.instructions,
|
|
490
|
+
...(connection.instructions !== undefined ? { instructions: connection.instructions } : {}),
|
|
489
491
|
cachedAt: Date.now(),
|
|
490
492
|
};
|
|
491
493
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Ajv } from "ajv";
|
|
2
2
|
import Ajv2020Import from "ajv/dist/2020.js";
|
|
3
3
|
import addFormatsImport from "ajv-formats";
|
|
4
|
-
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/
|
|
4
|
+
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv";
|
|
5
5
|
import type {
|
|
6
6
|
JsonSchemaType,
|
|
7
7
|
JsonSchemaValidator,
|
|
8
8
|
jsonSchemaValidator as JsonSchemaValidatorProvider,
|
|
9
|
-
} from "@modelcontextprotocol/
|
|
9
|
+
} from "@modelcontextprotocol/client";
|
|
10
10
|
|
|
11
11
|
// ajv-formats types target its bundled ajv; the runtime accepts both instances.
|
|
12
12
|
const addFormats = addFormatsImport as unknown as (instance: Ajv) => void;
|
|
@@ -12,15 +12,15 @@ export class McpLifecycleManager {
|
|
|
12
12
|
private allServers = new Map<string, ServerDefinition>();
|
|
13
13
|
private serverSettings = new Map<string, { idleTimeout?: number }>();
|
|
14
14
|
private globalIdleTimeout = 10 * 60 * 1000;
|
|
15
|
-
private healthCheckInterval
|
|
16
|
-
private onReconnect
|
|
17
|
-
private onReconnectFailure
|
|
18
|
-
private onIdleShutdown
|
|
19
|
-
private activeHealthCheck
|
|
20
|
-
private shutdownPromise
|
|
15
|
+
private healthCheckInterval: NodeJS.Timeout | undefined;
|
|
16
|
+
private onReconnect: ReconnectCallback | undefined;
|
|
17
|
+
private onReconnectFailure: ReconnectFailureCallback | undefined;
|
|
18
|
+
private onIdleShutdown: ((serverName: string) => void) | undefined;
|
|
19
|
+
private activeHealthCheck: Promise<void> | undefined;
|
|
20
|
+
private shutdownPromise: Promise<void> | undefined;
|
|
21
21
|
private stopped = false;
|
|
22
|
-
private healthSignal
|
|
23
|
-
private removeHealthAbortListener
|
|
22
|
+
private healthSignal: AbortSignal | undefined;
|
|
23
|
+
private removeHealthAbortListener: (() => void) | undefined;
|
|
24
24
|
|
|
25
25
|
constructor(
|
|
26
26
|
private readonly manager: McpServerManager,
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
import {
|
|
8
8
|
auth as runSdkAuth,
|
|
9
9
|
extractWWWAuthenticateParams,
|
|
10
|
+
LATEST_PROTOCOL_VERSION,
|
|
10
11
|
UnauthorizedError,
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
type AuthOptions,
|
|
13
|
+
} from "@modelcontextprotocol/client"
|
|
13
14
|
import open from "open"
|
|
14
15
|
import { McpOAuthProvider, type McpOAuthConfig } from "./mcp-oauth-provider.ts"
|
|
15
16
|
import {
|
|
@@ -50,15 +51,17 @@ export interface AuthenticateOptions {
|
|
|
50
51
|
authStorageOptions?: AuthStorageOptions
|
|
51
52
|
signal?: AbortSignal
|
|
52
53
|
runtime?: McpOAuthRuntime
|
|
54
|
+
skipIssuerMetadataValidation?: boolean
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
type AuthDiscovery =
|
|
56
|
-
resourceMetadataUrl?: URL
|
|
57
|
-
scope?: string
|
|
58
|
-
}
|
|
57
|
+
type AuthDiscovery = Pick<AuthOptions, "resourceMetadataUrl" | "scope" | "skipIssuerMetadataValidation">
|
|
59
58
|
|
|
60
|
-
function
|
|
61
|
-
return
|
|
59
|
+
function applyOAuthConfig(discovery: AuthDiscovery, config: McpOAuthConfig): AuthDiscovery {
|
|
60
|
+
return {
|
|
61
|
+
...discovery,
|
|
62
|
+
...(config.scope !== undefined ? { scope: config.scope } : {}),
|
|
63
|
+
...(config.skipIssuerMetadataValidation === true ? { skipIssuerMetadataValidation: true } : {}),
|
|
64
|
+
}
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
type PendingAuth = {
|
|
@@ -208,6 +211,12 @@ export function extractOAuthConfig(definition: ServerEntry): McpOAuthConfig {
|
|
|
208
211
|
}
|
|
209
212
|
config.clientUri = clientUri
|
|
210
213
|
}
|
|
214
|
+
if (definition.oauth?.skipIssuerMetadataValidation !== undefined) {
|
|
215
|
+
if (typeof definition.oauth.skipIssuerMetadataValidation !== "boolean") {
|
|
216
|
+
throw new Error("OAuth skipIssuerMetadataValidation must be a boolean")
|
|
217
|
+
}
|
|
218
|
+
config.skipIssuerMetadataValidation = definition.oauth.skipIssuerMetadataValidation
|
|
219
|
+
}
|
|
211
220
|
return config
|
|
212
221
|
}
|
|
213
222
|
|
|
@@ -239,7 +248,7 @@ async function probeAuthDiscovery(serverUrl: string, definition?: ServerEntry, s
|
|
|
239
248
|
clientInfo: { name: "pi-mcp-adapter", version: "2.11.0" },
|
|
240
249
|
},
|
|
241
250
|
}),
|
|
242
|
-
signal: discoverySignal,
|
|
251
|
+
...(discoverySignal ? { signal: discoverySignal } : {}),
|
|
243
252
|
})
|
|
244
253
|
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response)
|
|
245
254
|
await response.body?.cancel().catch(() => {})
|
|
@@ -320,7 +329,7 @@ export async function startAuth(
|
|
|
320
329
|
},
|
|
321
330
|
}, authStorageOptions, runtime.signal)
|
|
322
331
|
try {
|
|
323
|
-
const discovery =
|
|
332
|
+
const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, definition, signal), config)
|
|
324
333
|
throwIfAborted(signal)
|
|
325
334
|
const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery }), signal)
|
|
326
335
|
throwIfAborted(signal)
|
|
@@ -386,7 +395,7 @@ export async function startAuth(
|
|
|
386
395
|
|
|
387
396
|
throwIfAborted(signal)
|
|
388
397
|
|
|
389
|
-
const discovery =
|
|
398
|
+
const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, definition, signal), config)
|
|
390
399
|
throwIfAborted(signal)
|
|
391
400
|
const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery }), signal)
|
|
392
401
|
throwIfAborted(signal)
|
|
@@ -606,6 +615,7 @@ export async function completeAuth(
|
|
|
606
615
|
const result = await abortable(runSdkAuth(pendingAuth.authProvider, {
|
|
607
616
|
serverUrl: pendingAuth.serverUrl,
|
|
608
617
|
authorizationCode: code,
|
|
618
|
+
...(iss !== undefined ? { iss } : {}),
|
|
609
619
|
...pendingAuth.discovery,
|
|
610
620
|
}), signal)
|
|
611
621
|
throwIfAborted(signal)
|
|
@@ -657,10 +667,12 @@ export async function authenticate(
|
|
|
657
667
|
}
|
|
658
668
|
|
|
659
669
|
const operation = (async (): Promise<AuthStatus> => {
|
|
660
|
-
|
|
661
|
-
|
|
670
|
+
const { authorizationUrl } = await startAuth(serverName, serverUrl, definition, {
|
|
671
|
+
...options,
|
|
672
|
+
...(signal ? { signal } : {}),
|
|
673
|
+
runtime,
|
|
674
|
+
})
|
|
662
675
|
|
|
663
|
-
// If no auth URL needed, already authenticated
|
|
664
676
|
if (!authorizationUrl) {
|
|
665
677
|
return "authenticated"
|
|
666
678
|
}
|
|
@@ -694,14 +706,16 @@ export async function authenticate(
|
|
|
694
706
|
console.warn(`MCP Auth: Failed to open browser for ${serverName}; waiting for manual callback`, { error })
|
|
695
707
|
}
|
|
696
708
|
|
|
697
|
-
// Wait for callback
|
|
698
709
|
const callbackResult = await abortable(callbackPromise, signal)
|
|
699
710
|
|
|
700
711
|
// The callback server accepted only the flow-local reserved state.
|
|
701
712
|
throwIfAborted(signal)
|
|
702
713
|
|
|
703
|
-
|
|
704
|
-
|
|
714
|
+
return await completeAuth(serverName, callbackResult, {
|
|
715
|
+
...options,
|
|
716
|
+
...(signal ? { signal } : {}),
|
|
717
|
+
runtime,
|
|
718
|
+
})
|
|
705
719
|
} catch (error) {
|
|
706
720
|
if (oauthState) cancelPendingCallback(oauthState)
|
|
707
721
|
try {
|
|
@@ -740,25 +754,21 @@ export async function getValidToken(
|
|
|
740
754
|
const authStorageOptions = options.authStorageOptions ?? {}
|
|
741
755
|
const signal = combineAbortSignals(runtime.signal, options.signal)
|
|
742
756
|
throwIfAborted(signal)
|
|
743
|
-
// Check if we have valid tokens
|
|
744
757
|
const entry = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
|
|
745
758
|
throwIfAborted(signal)
|
|
746
759
|
if (!entry?.tokens) {
|
|
747
760
|
return null
|
|
748
761
|
}
|
|
749
762
|
|
|
750
|
-
// Check expiration
|
|
751
763
|
const expired = await isTokenExpired(serverName, authStorageOptions)
|
|
752
764
|
if (expired === false) {
|
|
753
765
|
return entry.tokens
|
|
754
766
|
}
|
|
755
767
|
|
|
756
768
|
if (expired === true && entry.tokens.refreshToken) {
|
|
757
|
-
// Token is expired, try to refresh
|
|
758
769
|
console.log(`MCP Auth: Token expired for ${serverName}, attempting refresh`)
|
|
759
770
|
|
|
760
771
|
try {
|
|
761
|
-
// Create auth provider for token refresh
|
|
762
772
|
const authProvider = new McpOAuthProvider(serverName, serverUrl, {}, {
|
|
763
773
|
onRedirect: async () => {},
|
|
764
774
|
}, authStorageOptions, runtime.signal)
|
|
@@ -773,7 +783,11 @@ export async function getValidToken(
|
|
|
773
783
|
|
|
774
784
|
const discovery = await probeAuthDiscovery(serverUrl, undefined, signal)
|
|
775
785
|
throwIfAborted(signal)
|
|
776
|
-
const result = await abortable(runSdkAuth(authProvider, {
|
|
786
|
+
const result = await abortable(runSdkAuth(authProvider, {
|
|
787
|
+
serverUrl,
|
|
788
|
+
...discovery,
|
|
789
|
+
...(options.skipIssuerMetadataValidation === true ? { skipIssuerMetadataValidation: true } : {}),
|
|
790
|
+
}), signal)
|
|
777
791
|
throwIfAborted(signal)
|
|
778
792
|
if (result !== "AUTHORIZED") {
|
|
779
793
|
return null
|