@astralyn/pi 1.0.4 → 1.1.0
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 +53 -0
- package/dist/bundle/chunks/{anthropic-messages-YIOJIMVL.js → anthropic-messages-3K3DWVY6.js} +1 -1
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-LDW4FGAQ.js → azure-openai-responses-CH74HGP4.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
- package/dist/bundle/chunks/{chunk-UCXU7KLO.js → chunk-3MMB5I7J.js} +1 -1
- package/dist/bundle/chunks/{chunk-GIG5IHES.js → chunk-5KJH3GGH.js} +1 -1
- package/dist/bundle/chunks/chunk-6VYRLG5F.js +2 -0
- package/dist/bundle/chunks/{chunk-73WG7F7N.js → chunk-C2ARIVS3.js} +1 -1
- package/dist/bundle/chunks/{chunk-23OTKHCH.js → chunk-D3XW5X3Z.js} +490 -260
- package/dist/bundle/chunks/{chunk-IFELM7MW.js → chunk-EJEOEBSJ.js} +1 -1
- package/dist/bundle/chunks/chunk-H35KWBT5.js +11 -0
- package/dist/bundle/chunks/chunk-KAC2YPTW.js +2 -0
- package/dist/bundle/chunks/chunk-MAC4N3BW.js +2 -0
- package/dist/bundle/chunks/chunk-QLU3LESZ.js +2 -0
- package/dist/bundle/chunks/{chunk-A6ALEEY5.js → chunk-TEAAYBTS.js} +1 -1
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-XO45ZT3V.js +2 -0
- package/dist/bundle/chunks/codemode-worker.js +101 -5
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-S5A5IBU4.js → google-generative-ai-VVO64ZRQ.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-UBRQFDB4.js → google-vertex-LKS5NUHA.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{llama-cpp-classify-R7DQLTQN.js → llama-cpp-classify-H3IZDR5O.js} +2 -2
- package/dist/bundle/chunks/{mistral-conversations-A2N5FSOV.js → mistral-conversations-NJUOG4ET.js} +2 -2
- package/dist/bundle/chunks/openai-chatgpt.js +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-Q6JHCWLE.js → openai-codex-responses-TZWBTTYM.js} +2 -2
- package/dist/bundle/chunks/openai-codex.js +1 -1
- package/dist/bundle/chunks/{openai-completions-H4VMO7AU.js → openai-completions-7HTTU6FH.js} +1 -1
- package/dist/bundle/chunks/openai-decisions-776H4VDZ.js +5 -0
- package/dist/bundle/chunks/{openai-responses-G4PGNM2H.js → openai-responses-7A6BAQ5X.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-YP3YVB54.js → openrouter-images-TDLJV5N4.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-HZZ7GEEW.js → pi-messages-ZXNERWJG.js} +1 -1
- package/dist/bundle/chunks/typesafe-system-one-2DQLG4R2.js +2 -0
- package/dist/bundle/chunks/{virtual-modules-IJTAY26N.js → virtual-modules-2NHGQRS6.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts +2 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +24 -5
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +38 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +190 -186
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +40 -45
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/context-snapshot.d.ts +4 -1
- package/dist/core/context-snapshot.d.ts.map +1 -1
- package/dist/core/context-snapshot.js +45 -0
- package/dist/core/context-snapshot.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +2 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/types.d.ts +13 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/mcp-servers.d.ts +8 -0
- package/dist/core/mcp-servers.d.ts.map +1 -1
- package/dist/core/mcp-servers.js +30 -7
- package/dist/core/mcp-servers.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +2 -1
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/nested-tool-calls.d.ts +1 -0
- package/dist/core/nested-tool-calls.d.ts.map +1 -1
- package/dist/core/nested-tool-calls.js +1 -0
- package/dist/core/nested-tool-calls.js.map +1 -1
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -3
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +13 -4
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +19 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +13 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +37 -13
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +4 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +6 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +6 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +9 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tasks/session.d.ts +9 -0
- package/dist/core/tasks/session.d.ts.map +1 -1
- package/dist/core/tasks/session.js +21 -0
- package/dist/core/tasks/session.js.map +1 -1
- package/dist/core/tools/read.d.ts +13 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +18 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts.map +1 -1
- package/dist/core/tools/renderers/bash.js +8 -3
- package/dist/core/tools/renderers/bash.js.map +1 -1
- package/dist/core/tools/renderers/edit.js +1 -1
- package/dist/core/tools/renderers/edit.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +65 -8
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/index.d.ts.map +1 -1
- package/dist/extensions/codemode/index.js +1 -0
- package/dist/extensions/codemode/index.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts +9 -2
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +14 -6
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/index.js +2 -2
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +34 -11
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/extensions/mcp/cli.js +17 -23
- package/dist/extensions/mcp/cli.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +165 -64
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/oauth.d.ts +5 -3
- package/dist/extensions/mcp/oauth.d.ts.map +1 -1
- package/dist/extensions/mcp/oauth.js +36 -10
- package/dist/extensions/mcp/oauth.js.map +1 -1
- package/dist/extensions/mcp/resources.d.ts +3 -4
- package/dist/extensions/mcp/resources.d.ts.map +1 -1
- package/dist/extensions/mcp/resources.js +3 -4
- package/dist/extensions/mcp/resources.js.map +1 -1
- package/dist/extensions/mcp/runtime.d.ts +4 -1
- package/dist/extensions/mcp/runtime.d.ts.map +1 -1
- package/dist/extensions/mcp/runtime.js +22 -5
- package/dist/extensions/mcp/runtime.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts +2 -1
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +2 -1
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/extensions/mcp/ui.d.ts +3 -3
- package/dist/extensions/mcp/ui.d.ts.map +1 -1
- package/dist/extensions/mcp/ui.js +10 -2
- package/dist/extensions/mcp/ui.js.map +1 -1
- package/dist/extensions/search/code-search.d.ts +29 -0
- package/dist/extensions/search/code-search.d.ts.map +1 -0
- package/dist/extensions/search/code-search.js +120 -0
- package/dist/extensions/search/code-search.js.map +1 -0
- package/dist/extensions/search/command.d.ts +5 -0
- package/dist/extensions/search/command.d.ts.map +1 -0
- package/dist/extensions/search/command.js +69 -0
- package/dist/extensions/search/command.js.map +1 -0
- package/dist/extensions/search/credential.d.ts +25 -0
- package/dist/extensions/search/credential.d.ts.map +1 -0
- package/dist/extensions/search/credential.js +117 -0
- package/dist/extensions/search/credential.js.map +1 -0
- package/dist/extensions/search/devin.d.ts +34 -0
- package/dist/extensions/search/devin.d.ts.map +1 -0
- package/dist/extensions/search/devin.js +210 -0
- package/dist/extensions/search/devin.js.map +1 -0
- package/dist/extensions/search/index.d.ts +5 -0
- package/dist/extensions/search/index.d.ts.map +1 -0
- package/dist/extensions/search/index.js +110 -0
- package/dist/extensions/search/index.js.map +1 -0
- package/dist/extensions/search/panel.d.ts +53 -0
- package/dist/extensions/search/panel.d.ts.map +1 -0
- package/dist/extensions/search/panel.js +157 -0
- package/dist/extensions/search/panel.js.map +1 -0
- package/dist/extensions/search/prompt.d.ts +14 -0
- package/dist/extensions/search/prompt.d.ts.map +1 -0
- package/dist/extensions/search/prompt.js +221 -0
- package/dist/extensions/search/prompt.js.map +1 -0
- package/dist/extensions/search/render.d.ts +28 -0
- package/dist/extensions/search/render.d.ts.map +1 -0
- package/dist/extensions/search/render.js +86 -0
- package/dist/extensions/search/render.js.map +1 -0
- package/dist/extensions/search/workspace.d.ts +38 -0
- package/dist/extensions/search/workspace.d.ts.map +1 -0
- package/dist/extensions/search/workspace.js +245 -0
- package/dist/extensions/search/workspace.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +14 -14
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +5 -2
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +2 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +5 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +7 -2
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +1 -0
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +52 -59
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/program-status-reporter.d.ts +34 -0
- package/dist/modes/interactive/program-status-reporter.d.ts.map +1 -0
- package/dist/modes/interactive/program-status-reporter.js +107 -0
- package/dist/modes/interactive/program-status-reporter.js.map +1 -0
- package/dist/modes/interactive/tasks/host-hooks.d.ts +14 -0
- package/dist/modes/interactive/tasks/host-hooks.d.ts.map +1 -0
- package/dist/modes/interactive/tasks/host-hooks.js +31 -0
- package/dist/modes/interactive/tasks/host-hooks.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +1 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/tool-view/tool-execution.d.ts +5 -0
- package/dist/modes/interactive/tool-view/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/tool-view/tool-execution.js +7 -0
- package/dist/modes/interactive/tool-view/tool-execution.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +24 -0
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/ansi.d.ts +8 -0
- package/dist/utils/ansi.d.ts.map +1 -1
- package/dist/utils/ansi.js +30 -11
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +12 -11
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts +12 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -1
- package/dist/utils/image-resize-core.js +5 -0
- package/dist/utils/image-resize-core.js.map +1 -1
- package/dist/utils/image-resize-worker.js +3 -2
- package/dist/utils/image-resize-worker.js.map +1 -1
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +9 -10
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +6 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/docs/bundled/README.md +1 -1
- package/docs/bundled/extensions/provider.md +1 -1
- package/docs/bundled/extensions/question.md +1 -1
- package/docs/bundled/extensions/search.md +50 -0
- package/docs/bundled/extensions/statusline.md +15 -41
- package/docs/codemode.md +26 -5
- package/docs/docs.json +0 -4
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +6 -2
- package/docs/json.md +2 -2
- package/docs/llama-cpp.md +8 -1
- package/docs/mcp.md +3 -1
- package/docs/models.md +4 -1
- package/docs/providers.md +17 -0
- package/docs/rpc.md +6 -3
- package/docs/session-format.md +1 -0
- package/docs/settings.md +4 -4
- package/docs/terminal-setup.md +14 -0
- package/docs/usage.md +19 -5
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +54 -54
- package/package.json +13 -11
- package/dist/bundle/chunks/chunk-57XI7YJH.js +0 -2
- package/dist/bundle/chunks/chunk-JI4WGISU.js +0 -2
- package/dist/bundle/chunks/chunk-TCQ4HHQD.js +0 -2
- package/dist/bundle/chunks/chunk-TXJO4AJ7.js +0 -11
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-OWU5SM6J.js +0 -2
- package/dist/bundle/chunks/typesafe-system-one-KYKKNU3E.js +0 -2
- package/dist/extensions/web-search/auth.d.ts +0 -12
- package/dist/extensions/web-search/auth.d.ts.map +0 -1
- package/dist/extensions/web-search/auth.js +0 -90
- package/dist/extensions/web-search/auth.js.map +0 -1
- package/dist/extensions/web-search/constants.d.ts +0 -13
- package/dist/extensions/web-search/constants.d.ts.map +0 -1
- package/dist/extensions/web-search/constants.js +0 -26
- package/dist/extensions/web-search/constants.js.map +0 -1
- package/dist/extensions/web-search/execute.d.ts +0 -9
- package/dist/extensions/web-search/execute.d.ts.map +0 -1
- package/dist/extensions/web-search/execute.js +0 -96
- package/dist/extensions/web-search/execute.js.map +0 -1
- package/dist/extensions/web-search/format.d.ts +0 -21
- package/dist/extensions/web-search/format.d.ts.map +0 -1
- package/dist/extensions/web-search/format.js +0 -117
- package/dist/extensions/web-search/format.js.map +0 -1
- package/dist/extensions/web-search/index.d.ts +0 -6
- package/dist/extensions/web-search/index.d.ts.map +0 -1
- package/dist/extensions/web-search/index.js +0 -50
- package/dist/extensions/web-search/index.js.map +0 -1
- package/dist/extensions/web-search/providers/deepseek.d.ts +0 -13
- package/dist/extensions/web-search/providers/deepseek.d.ts.map +0 -1
- package/dist/extensions/web-search/providers/deepseek.js +0 -110
- package/dist/extensions/web-search/providers/deepseek.js.map +0 -1
- package/dist/extensions/web-search/providers/http.d.ts +0 -4
- package/dist/extensions/web-search/providers/http.d.ts.map +0 -1
- package/dist/extensions/web-search/providers/http.js +0 -42
- package/dist/extensions/web-search/providers/http.js.map +0 -1
- package/dist/extensions/web-search/providers/minimax.d.ts +0 -12
- package/dist/extensions/web-search/providers/minimax.d.ts.map +0 -1
- package/dist/extensions/web-search/providers/minimax.js +0 -61
- package/dist/extensions/web-search/providers/minimax.js.map +0 -1
- package/dist/extensions/web-search/render.d.ts +0 -15
- package/dist/extensions/web-search/render.d.ts.map +0 -1
- package/dist/extensions/web-search/render.js +0 -142
- package/dist/extensions/web-search/render.js.map +0 -1
- package/dist/extensions/web-search/results.d.ts +0 -35
- package/dist/extensions/web-search/results.d.ts.map +0 -1
- package/dist/extensions/web-search/results.js +0 -167
- package/dist/extensions/web-search/results.js.map +0 -1
- package/dist/extensions/web-search/schema.d.ts +0 -10
- package/dist/extensions/web-search/schema.d.ts.map +0 -1
- package/dist/extensions/web-search/schema.js +0 -36
- package/dist/extensions/web-search/schema.js.map +0 -1
- package/dist/extensions/web-search/types.d.ts +0 -51
- package/dist/extensions/web-search/types.d.ts.map +0 -1
- package/dist/extensions/web-search/types.js +0 -5
- package/dist/extensions/web-search/types.js.map +0 -1
- package/docs/bundled/extensions/web-search.md +0 -46
|
@@ -26,8 +26,11 @@ const CLIENT_METADATA_BASE_URL = "https://pi.dev/oauth";
|
|
|
26
26
|
const FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
27
27
|
/** Access tokens this close to expiry are refreshed before they are sent. */
|
|
28
28
|
const REFRESH_SKEW_MS = 30_000;
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Bounds each request to the authorization server, so an unresponsive one cannot hold the refresh lock,
|
|
31
|
+
* delay shutdown, or keep a sign-in waiting for long.
|
|
32
|
+
*/
|
|
33
|
+
const OAUTH_REQUEST_TIMEOUT_MS = 15_000;
|
|
31
34
|
/** A refresh lock that its holder stopped renewing (the process was killed) is taken over after this. */
|
|
32
35
|
const REFRESH_LOCK_STALE_MS = 20_000;
|
|
33
36
|
/** How long to wait for another process's refresh: longer than a stale lock lives. */
|
|
@@ -154,6 +157,13 @@ export class McpOAuthCredentialStore {
|
|
|
154
157
|
});
|
|
155
158
|
}
|
|
156
159
|
}
|
|
160
|
+
/** `fetch` with `OAUTH_REQUEST_TIMEOUT_MS` per request, on top of the request's own signal. */
|
|
161
|
+
function timedFetch(fetch = globalThis.fetch) {
|
|
162
|
+
return (input, init) => {
|
|
163
|
+
const timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
|
|
164
|
+
return fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout });
|
|
165
|
+
};
|
|
166
|
+
}
|
|
157
167
|
function registeredRedirectUrls(client) {
|
|
158
168
|
return client && "redirect_uris" in client ? client.redirect_uris : [];
|
|
159
169
|
}
|
|
@@ -226,13 +236,14 @@ export function createMcpAuthProvider(options) {
|
|
|
226
236
|
registeredRedirectUrls(state.clientInformation)[0] ??
|
|
227
237
|
FALLBACK_REDIRECT_URL;
|
|
228
238
|
const provider = createProvider(serverUrl, store, settings, redirectUrl, () => { });
|
|
229
|
-
// Refreshes the tokens, or reports that a new sign-in is needed.
|
|
239
|
+
// Refreshes the tokens, or reports that a new sign-in is needed. Not cancellable: a refresh the
|
|
240
|
+
// server answered may have rotated the refresh token, so its answer must be saved.
|
|
230
241
|
const result = await authorizeMcp(provider, {
|
|
231
242
|
serverUrl,
|
|
232
243
|
resourceMetadataUrl: challenge?.resourceMetadataUrl,
|
|
233
244
|
authorizationServerMetadataUrl: settings.authServerMetadataUrl,
|
|
234
245
|
scope: challenge?.scope,
|
|
235
|
-
fetch: (
|
|
246
|
+
fetch: timedFetch(fetch),
|
|
236
247
|
});
|
|
237
248
|
if (result === "REDIRECT")
|
|
238
249
|
throw new McpOAuthAuthorizationRequiredError();
|
|
@@ -295,11 +306,15 @@ function responseFromRedirectUrl(input, state, redirectUrl) {
|
|
|
295
306
|
throw new Error("The redirect URL does not contain an authorization code");
|
|
296
307
|
return { code, iss: url.searchParams.get("iss") ?? undefined };
|
|
297
308
|
}
|
|
298
|
-
/**
|
|
299
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Wait for the browser callback or a pasted redirect URL, whichever comes first. Aborting `signal`
|
|
311
|
+
* aborts the prompt, which then cancels the sign-in.
|
|
312
|
+
*/
|
|
313
|
+
async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt, signal) {
|
|
300
314
|
const controller = new AbortController();
|
|
315
|
+
const promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
301
316
|
const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
|
|
302
|
-
const fromUser = prompt.promptForRedirectUrl(
|
|
317
|
+
const fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {
|
|
303
318
|
if (!input?.trim())
|
|
304
319
|
throw new McpSignInCancelledError();
|
|
305
320
|
return responseFromRedirectUrl(input, state, redirectUrl);
|
|
@@ -336,10 +351,13 @@ async function listenForCallback(settings, extraPaths, port, required) {
|
|
|
336
351
|
}
|
|
337
352
|
/**
|
|
338
353
|
* Sign in to an MCP server. Uses the stored refresh token when possible; otherwise runs the browser
|
|
339
|
-
* authorization code flow. Tokens are saved to `store`.
|
|
354
|
+
* authorization code flow. Tokens are saved to `store`. Aborting `signal` stops the sign-in at any
|
|
355
|
+
* step with `McpSignInCancelledError`; requests to the authorization server are also time-limited.
|
|
340
356
|
*/
|
|
341
357
|
export async function signInMcpServer(options) {
|
|
342
|
-
const { serverUrl, store, settings } = options;
|
|
358
|
+
const { serverUrl, store, settings, signal } = options;
|
|
359
|
+
if (signal?.aborted)
|
|
360
|
+
throw new McpSignInCancelledError();
|
|
343
361
|
const stored = await store.load();
|
|
344
362
|
const stepUp = options.challenge?.error === "insufficient_scope";
|
|
345
363
|
const callbackOptions = callbackSettings(settings);
|
|
@@ -373,6 +391,8 @@ export async function signInMcpServer(options) {
|
|
|
373
391
|
});
|
|
374
392
|
const flow = {
|
|
375
393
|
serverUrl,
|
|
394
|
+
fetch: timedFetch(),
|
|
395
|
+
signal,
|
|
376
396
|
resourceMetadataUrl: options.challenge?.resourceMetadataUrl,
|
|
377
397
|
authorizationServerMetadataUrl: settings.authServerMetadataUrl,
|
|
378
398
|
// A server asking for more scope gets it on top of the configured scope and, since the challenge
|
|
@@ -389,9 +409,15 @@ export async function signInMcpServer(options) {
|
|
|
389
409
|
// The flow picks the redirect URI, which may be specific to the MCP server.
|
|
390
410
|
const authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl);
|
|
391
411
|
options.prompt.showAuthorizationUrl(authorizationUrl);
|
|
392
|
-
const { code, iss } = await waitForAuthorizationResponse(callback, state, authorizationRedirectUrl, options.prompt);
|
|
412
|
+
const { code, iss } = await waitForAuthorizationResponse(callback, state, authorizationRedirectUrl, options.prompt, signal);
|
|
393
413
|
await authorizeMcp(provider, { ...flow, authorizationCode: code, iss });
|
|
394
414
|
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
// Aborted requests fail with the signal's reason; report them as the cancellation they are.
|
|
417
|
+
if (signal?.aborted)
|
|
418
|
+
throw new McpSignInCancelledError();
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
395
421
|
finally {
|
|
396
422
|
await callback.close();
|
|
397
423
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/extensions/mcp/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAE1F,OAAO,EAEN,YAAY,EACZ,kCAAkC,EAClC,gBAAgB,EAKhB,mBAAmB,EAInB,oBAAoB,EACpB,WAAW,GACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,QAAQ,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,4GAA4G;AAC5G,MAAM,wBAAwB,GAAG,sBAAsB,CAAC;AACxD,2FAA2F;AAC3F,MAAM,qBAAqB,GAAG,UAAU,aAAa,GAAG,aAAa,EAAE,CAAC;AACxE,6EAA6E;AAC7E,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,uGAAuG;AACvG,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAC1C,yGAAyG;AACzG,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,sFAAsF;AACtF,MAAM,oBAAoB,GAAG,MAAM,CAAC;AACpC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AA+BlC,SAAS,gBAAgB,CAAC,QAA0B;IACnD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,UAAU,aAAa,GAAG,aAAa,EAAE,CAAC,CAAC;IACvF,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;IACjE,IAAI,gBAAoC,CAAC;IACzC,iGAAiG;IACjG,IAAI,GAAG,CAAC,IAAI;QAAE,gBAAgB,GAAG,QAAQ,CAAC,WAAW,CAAC;SACjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,gBAAgB,GAAG,GAAG,CAAC,IAAI,CAAC;IAC7B,CAAC;IACD,OAAO;QACN,iFAAiF;QACjF,IAAI,EAAE,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO;QACvD,YAAY,EAAE,OAAO;QACrB,IAAI;QACJ,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,gBAAgB;KAChB,CAAC;AACH,CAAC;AAED,uCAAuC;AACvC,SAAS,WAAW,CAAC,GAAG,MAA8B;IACrD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAClG,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAID,SAAS,WAAW,CAAC,OAA2B;IAC/C,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;AAChH,CAAC;AAED,SAAS,eAAe,CAAC,MAAoB;IAC5C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,IAAY,EAAE,SAAiB;IACjD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7C,OAAO,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC;AAOD,sGAAsG;AACtG,MAAM,OAAO,uBAAuB;IAKnC,YAAY,OAA4B,EAAE,OAAgB;QACzD,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,sBAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;QAC3F,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAClD,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,SAAiB;QACxC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtD,OAAO;YACN,+FAA+F;YAC/F,IAAI,EAAE,GAAG,EAAE,CACV,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;gBACjC,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;gBACpC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;oBAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtE,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;gBAChC,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC;gBACzB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/D,CAAC,CAAC;YACH,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE;gBACrB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACrB,CAAC,CAAC;YACH,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC;SACtD,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,eAAe,CAAI,GAAW,EAAE,EAAoB;QACjE,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,EAAE,CAAC;QAC/B,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,IAAI,EAAE,CAAC,EAAE;YACnF,QAAQ,EAAE,KAAK;YACf,KAAK,EAAE,qBAAqB;YAC5B,OAAO,EAAE;gBACR,OAAO,EAAE,oBAAoB,GAAG,qBAAqB;gBACrD,MAAM,EAAE,CAAC;gBACT,UAAU,EAAE,qBAAqB;gBACjC,UAAU,EAAE,qBAAqB;aACjC;YACD,oFAAoF;YACpF,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC;SACvB,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,OAAO,MAAM,EAAE,EAAE,CAAC;QACnB,CAAC;gBAAS,CAAC;YACV,MAAM,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACxC,CAAC;IACF,CAAC;IAED,qHAAqH;IACrH,MAAM,CAAC,IAAY,EAAE,SAAiB;QACrC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QACtF,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC;IACnD,CAAC;IAED,+GAA+G;IAC/G,MAAM,CAAC,IAAY,EAAE,SAAiB;QACrC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;YACjF,IAAI,CAAC,MAAM;gBAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YACtC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;YACtB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,MAAsC;QACnD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,CAAC,MAAM,CAAC,CAAC;YACf,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7D,CAAC,CAAC,CAAC;IACJ,CAAC;CACD;AAED,SAAS,sBAAsB,CAAC,MAA+C;IAC9E,OAAO,MAAM,IAAI,eAAe,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,+FAA+F;AAC/F,SAAS,UAAU,CAAC,SAAiB;IACpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/B,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;IACd,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC5F,CAAC;AAED;;;;;GAKG;AACH,SAAS,sBAAsB,CAC9B,SAAiB,EACjB,WAAmB,EACnB,QAAiD;IAEjD,IACC,CAAC,QAAQ,EAAE,qCAAqC;QAChD,CAAC,QAAQ,CAAC,qCAAqC,EAAE,QAAQ,CAAC,MAAM,CAAC,EAChE,CAAC;QACF,MAAM,IAAI,KAAK,CACd,mIAAmI,CACnI,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,8CAA8C,EAAE,CAAC;QAC7D,OAAO,EAAE,GAAG,EAAE,GAAG,wBAAwB,cAAc,EAAE,WAAW,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IACtC,QAAQ,CAAC,QAAQ,GAAG,GAAG,aAAa,IAAI,EAAE,EAAE,CAAC;IAC7C,OAAO,EAAE,GAAG,EAAE,GAAG,wBAAwB,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC7F,CAAC;AAED,SAAS,cAAc,CACtB,SAAiB,EACjB,KAAyB,EACzB,QAA0B,EAC1B,WAAmB,EACnB,UAA8B;IAE9B,OAAO,IAAI,gBAAgB,CAAC;QAC3B,SAAS;QACT,WAAW;QACX,cAAc,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,UAAU,IAAI,QAAQ,EAAE;QAChE,sBAAsB,EACrB,QAAQ,CAAC,kBAAkB,KAAK,MAAM;YACrC,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,sBAAsB,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC;YACxE,CAAC,CAAC,SAAS;QACb,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,KAAK;QACL,UAAU;KACV,CAAC,CAAC;AACJ,CAAC;AAOD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAKrC;IACA,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACrC,IAAI,UAAqC,CAAC;IAE1C,2EAA2E;IAC3E,MAAM,OAAO,GAAG,CAAC,UAA8B,EAAE,QAAkB,UAAU,CAAC,KAAK,EAAE,SAA0B,EAAE,EAAE;QAClH,UAAU,KAAK,KAAK;aAClB,eAAe,CAAC,KAAK,IAAI,EAAE;YAC3B,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACjC,IAAI,KAAK,EAAE,MAAM,EAAE,YAAY,KAAK,UAAU;gBAAE,OAAO;YACvD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa;gBAAE,MAAM,IAAI,kCAAkC,EAAE,CAAC;YAClF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,WAAW,GAChB,gBAAgB,CAAC,QAAQ,CAAC,CAAC,gBAAgB;gBAC3C,sBAAsB,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBAClD,qBAAqB,CAAC;YACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACnF,iEAAiE;YACjE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE;gBAC3C,SAAS;gBACT,mBAAmB,EAAE,SAAS,EAAE,mBAAmB;gBACnD,8BAA8B,EAAE,QAAQ,CAAC,qBAAqB;gBAC9D,KAAK,EAAE,SAAS,EAAE,KAAK;gBACvB,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CACtB,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,0BAA0B,CAAC,EAAE,CAAC;aACnF,CAAC,CAAC;YACH,IAAI,MAAM,KAAK,UAAU;gBAAE,MAAM,IAAI,kCAAkC,EAAE,CAAC;QAC3E,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACb,UAAU,GAAG,SAAS,CAAC;QACxB,CAAC,CAAC,CAAC;QACJ,OAAO,UAAU,CAAC;IACnB,CAAC,CAAC;IAEF,OAAO;QACN,KAAK,EAAE,KAAK,IAAI,EAAE;YACjB,MAAM,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC;YAC1C,MAAM,OAAO,GAAG,KAAK,EAAE,cAAc,KAAK,SAAS,IAAI,KAAK,CAAC,cAAc,GAAG,eAAe,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5G,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa;gBAAE,OAAO,KAAK,CAAC;YAC5D,iGAAiG;YACjG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC5C,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC;QACnD,CAAC;QACD,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACjC,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC;YACzF,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC/B,wEAAwE;YACxE,IAAI,SAAS,CAAC,KAAK,KAAK,oBAAoB;gBAAE,MAAM,IAAI,kCAAkC,EAAE,CAAC;YAC7F,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YACnB,MAAM,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;KACD,CAAC;AACH,CAAC;AAaD,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACjD;QACC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACvC,CAAC;CACD;AAID,SAAS,uBAAuB,CAAC,KAAa,EAAE,KAAa,EAAE,WAAgB;IAC9E,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IAChF,CAAC;IACD,8FAA8F;IAC9F,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC,QAAQ,EAAE,CAAC;QAChF,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5C,IAAI,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,KAAK,CAAC,CAAC;IAC/E,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IAChH,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACtF,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;AAChE,CAAC;AAED,qFAAqF;AACrF,KAAK,UAAU,4BAA4B,CAC1C,QAA6B,EAC7B,KAAa,EACb,WAAgB,EAChB,MAAuB;IAEvB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,WAAW,GAAG,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,MAAM,CAAC,oBAAoB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9E,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;QACxD,OAAO,uBAAuB,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACJ,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpD,CAAC;YAAS,CAAC;QACV,oFAAoF;QACpF,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACnC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;AACF,CAAC;AAED,+EAA+E;AAC/E,KAAK,UAAU,iBAAiB,CAC/B,QAA0B,EAC1B,UAAoB,EACpB,IAAwB,EACxB,QAAiB;IAEjB,MAAM,OAAO,GAAG;QACf,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,UAAU;QACV,UAAU,EAAE,CAAC,IAAuB,EAAE,EAAE,CACvC,IAAI,CAAC,EAAE;YACN,CAAC,CAAC,gBAAgB,CAAC,2DAA2D,CAAC;YAC/E,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC;KAC9C,CAAC;IACF,IAAI,CAAC;QACJ,OAAO,MAAM,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,QAAQ,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,KAAK,CAAC;QAChD,OAAO,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAMrC;IACA,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC/C,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,KAAK,oBAAoB,CAAC;IACjE,MAAM,eAAe,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACnD,sFAAsF;IACtF,MAAM,UAAU,GAAG,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,aAAa,GAClB,eAAe,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAClG,MAAM,IAAI,GAAG,QAAQ,CAAC,kBAAkB,KAAK,MAAM,CAAC;IACpD,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CACvC,eAAe;IACf,qEAAqE;IACrE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EACzD,aAAa,EACb,eAAe,CAAC,IAAI,KAAK,SAAS,CAClC,CAAC;IACF,MAAM,WAAW,GAAG,eAAe,CAAC,gBAAgB,IAAI,QAAQ,CAAC,WAAW,CAAC;IAC7E,IAAI,CAAC;QACJ,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,GAAkB,EAAE,GAAG,MAAM,EAAE,CAAC;YAC1C,gDAAgD;YAChD,OAAO,IAAI,CAAC,UAAU,CAAC;YACvB,gGAAgG;YAChG,uGAAuG;YACvG,MAAM,UAAU,GACf,QAAQ,CAAC,QAAQ;gBACjB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;YAC7G,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjB,OAAO,IAAI,CAAC,iBAAiB,CAAC;gBAC9B,OAAO,IAAI,CAAC,MAAM,CAAC;gBACnB,OAAO,IAAI,CAAC,cAAc,CAAC;YAC5B,CAAC;YACD,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,gBAAiC,CAAC;QACtC,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE;YAChF,gBAAgB,GAAG,GAAG,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG;YACZ,SAAS;YACT,mBAAmB,EAAE,OAAO,CAAC,SAAS,EAAE,mBAAmB;YAC3D,8BAA8B,EAAE,QAAQ,CAAC,qBAAqB;YAC9D,iGAAiG;YACjG,wEAAwE;YACxE,KAAK,EAAE,WAAW,CACjB,QAAQ,CAAC,KAAK,EACd,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAChG;SACD,CAAC;QACF,sFAAsF;QACtF,MAAM,WAAW,GAAG,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,YAAY;YAAE,OAAO;QACtF,IAAI,CAAC,gBAAgB;YAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QAE1F,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QACrC,4EAA4E;QAC5E,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,WAAW,CAAC,CAAC;QAC3G,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;QACtD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,4BAA4B,CACvD,QAAQ,EACR,KAAK,EACL,wBAAwB,EACxB,OAAO,CAAC,MAAM,CACd,CAAC;QACF,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACzE,CAAC;YAAS,CAAC;QACV,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACF,CAAC","sourcesContent":["/**\n * OAuth sign-in for remote MCP servers.\n *\n * Connections never start a browser flow on their own. They send the stored access token and, after\n * a 401, try the stored refresh token. When that is not possible they fail with\n * `McpOAuthAuthorizationRequiredError`, and the user signs in through `/mcp`, which runs\n * the authorization code flow (PKCE, a Client ID Metadata Document or dynamic client registration)\n * against a loopback callback.\n *\n * Credentials live in `<agent-dir>/mcp-auth.json`, keyed by server name and URL.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"@earendil-works/pi-ai/utils/oauth-page\";\nimport type { AuthProvider, McpFetch } from \"@earendil-works/pi-mcp\";\nimport {\n\ttype AuthorizationServerMetadata,\n\tauthorizeMcp,\n\tMcpOAuthAuthorizationRequiredError,\n\tMcpOAuthProvider,\n\ttype McpOAuthState,\n\ttype McpOAuthStateStore,\n\ttype OAuthCallback,\n\ttype OAuthCallbackPage,\n\tOAuthCallbackServer,\n\ttype OAuthChallenge,\n\ttype OAuthClientInformationMixed,\n\ttype OAuthClientMetadataDocument,\n\tparseWwwAuthenticate,\n\tstepUpScope,\n} from \"@earendil-works/pi-mcp/oauth\";\nimport lockfile from \"proper-lockfile\";\nimport { APP_NAME, getAgentDir } from \"../../config.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../../core/auth-storage.ts\";\nimport { mcpNamespace } from \"../../core/mcp-servers.ts\";\n\nconst CALLBACK_HOST = \"127.0.0.1\";\nconst CALLBACK_PATH = \"/callback\";\n/** Where pi.dev serves pi's Client ID Metadata Documents: `client.json` and `<callback ID>/client.json`. */\nconst CLIENT_METADATA_BASE_URL = \"https://pi.dev/oauth\";\n/** Redirect URI for refreshes when none is stored. Refreshing never redirects the user. */\nconst FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;\n/** Access tokens this close to expiry are refreshed before they are sent. */\nconst REFRESH_SKEW_MS = 30_000;\n/** Bounds each request of a refresh, so it cannot hold the refresh lock or delay shutdown for long. */\nconst REFRESH_REQUEST_TIMEOUT_MS = 15_000;\n/** A refresh lock that its holder stopped renewing (the process was killed) is taken over after this. */\nconst REFRESH_LOCK_STALE_MS = 20_000;\n/** How long to wait for another process's refresh: longer than a stale lock lives. */\nconst REFRESH_LOCK_WAIT_MS = 25_000;\nconst REFRESH_LOCK_RETRY_MS = 100;\n\nexport interface McpOAuthSettings {\n\tclientId?: string;\n\t/** Already resolved. */\n\tclientSecret?: string;\n\tcallbackPort?: number;\n\t/** Loopback redirect URI; see `McpOAuthConfig.callbackUrl`. */\n\tcallbackUrl?: string;\n\t/** Scopes to request, separated by spaces. */\n\tscope?: string;\n\t/** `client_name` for dynamic client registration. Default: `APP_NAME`. */\n\tclientName?: string;\n\t/** See `McpOAuthConfig.clientRegistration`. */\n\tclientRegistration?: \"dcr\" | \"cimd\";\n\t/** See `McpOAuthConfig.authServerMetadataUrl`. */\n\tauthServerMetadataUrl?: URL;\n}\n\n/** Where the loopback callback server listens and the redirect URI it serves. */\ninterface CallbackSettings {\n\t/** Address to listen on. */\n\thost: string;\n\t/** Host name in the redirect URI. */\n\tredirectHost: string;\n\tport: number | undefined;\n\tpath: string;\n\t/** The exact redirect URI, when the port is fixed. */\n\tfixedRedirectUrl: string | undefined;\n}\n\nfunction callbackSettings(settings: McpOAuthSettings): CallbackSettings {\n\tconst url = new URL(settings.callbackUrl ?? `http://${CALLBACK_HOST}${CALLBACK_PATH}`);\n\tconst address = url.hostname.replace(/^\\[|\\]$/g, \"\");\n\tconst port = url.port ? Number(url.port) : settings.callbackPort;\n\tlet fixedRedirectUrl: string | undefined;\n\t// A configured URI with a port is sent exactly as written, since servers compare it as a string.\n\tif (url.port) fixedRedirectUrl = settings.callbackUrl;\n\telse if (port !== undefined) {\n\t\turl.port = String(port);\n\t\tfixedRedirectUrl = url.href;\n\t}\n\treturn {\n\t\t// `localhost` is served on 127.0.0.1; browsers fall back to it when ::1 refuses.\n\t\thost: address === \"localhost\" ? CALLBACK_HOST : address,\n\t\tredirectHost: address,\n\t\tport,\n\t\tpath: url.pathname,\n\t\tfixedRedirectUrl,\n\t};\n}\n\n/** Scopes of both lists, each once. */\nfunction mergeScopes(...scopes: (string | undefined)[]): string | undefined {\n\tconst merged = [...new Set(scopes.flatMap((scope) => scope?.split(/\\s+/).filter(Boolean) ?? []))];\n\treturn merged.length > 0 ? merged.join(\" \") : undefined;\n}\n\ntype StoredStates = Record<string, McpOAuthState>;\n\nfunction parseStates(content: string | undefined): StoredStates {\n\tif (!content?.trim()) return {};\n\tconst parsed: unknown = JSON.parse(content);\n\treturn typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed) ? (parsed as StoredStates) : {};\n}\n\nfunction serializeStates(states: StoredStates): string {\n\treturn `${JSON.stringify(states, null, 2)}\\n`;\n}\n\n/**\n * Keys of a server's state: by name and URL, so servers sharing a URL keep separate accounts, and the\n * legacy key by URL alone, written by older versions.\n */\nfunction storeKeys(name: string, serverUrl: string): { key: string; legacyKey: string } {\n\tconst legacyKey = String(new URL(serverUrl));\n\treturn { key: `${mcpNamespace(name)}|${legacyKey}`, legacyKey };\n}\n\nexport interface McpOAuthServerStore extends McpOAuthStateStore {\n\t/** Run `fn` while no other process refreshes the server's tokens. */\n\twithRefreshLock<T>(fn: () => Promise<T>): Promise<T>;\n}\n\n/** Per-server OAuth state (client registration, tokens, pending PKCE verifier) in `mcp-auth.json`. */\nexport class McpOAuthCredentialStore {\n\tprivate readonly backend: AuthStorageBackend;\n\t/** Directory for the refresh lock files. Without one, refreshes are only serialized in this process. */\n\tprivate readonly lockDir: string | undefined;\n\n\tconstructor(backend?: AuthStorageBackend, lockDir?: string) {\n\t\tthis.backend = backend ?? new FileAuthStorageBackend(join(getAgentDir(), \"mcp-auth.json\"));\n\t\tthis.lockDir = backend ? lockDir : getAgentDir();\n\t}\n\n\tforServer(name: string, serverUrl: string): McpOAuthServerStore {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn {\n\t\t\t// The first server to load legacy state takes it over; others with the same URL sign in again.\n\t\t\tload: () =>\n\t\t\t\tthis.backend.withLock((current) => {\n\t\t\t\t\tconst states = parseStates(current);\n\t\t\t\t\tif (states[key] || !states[legacyKey]) return { result: states[key] };\n\t\t\t\t\tstates[key] = states[legacyKey];\n\t\t\t\t\tdelete states[legacyKey];\n\t\t\t\t\treturn { result: states[key], next: serializeStates(states) };\n\t\t\t\t}),\n\t\t\tsave: (state) =>\n\t\t\t\tthis.write((states) => {\n\t\t\t\t\tstates[key] = state;\n\t\t\t\t}),\n\t\t\twithRefreshLock: (fn) => this.withRefreshLock(key, fn),\n\t\t};\n\t}\n\n\t/**\n\t * A lock file per server. When the process exits, proper-lockfile removes the locks it holds; when\n\t * it is killed, the lock goes stale because it is no longer renewed, and the next process takes it over.\n\t */\n\tprivate async withRefreshLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n\t\tif (!this.lockDir) return fn();\n\t\tmkdirSync(this.lockDir, { recursive: true, mode: 0o700 });\n\t\tconst hash = createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16);\n\t\tconst release = await lockfile.lock(join(this.lockDir, `mcp-auth-refresh-${hash}`), {\n\t\t\trealpath: false,\n\t\t\tstale: REFRESH_LOCK_STALE_MS,\n\t\t\tretries: {\n\t\t\t\tretries: REFRESH_LOCK_WAIT_MS / REFRESH_LOCK_RETRY_MS,\n\t\t\t\tfactor: 1,\n\t\t\t\tminTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t\tmaxTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t},\n\t\t\t// The default throws from a timer. A lost lock at worst lets two refreshes overlap.\n\t\t\tonCompromised: () => {},\n\t\t});\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait release().catch(() => undefined);\n\t\t}\n\t}\n\n\t/** The stored tokens of a server, for noticing sign-ins done by another process. Does not take over legacy state. */\n\ttokens(name: string, serverUrl: string): McpOAuthState[\"tokens\"] {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\tconst states = this.backend.withLock((current) => ({ result: parseStates(current) }));\n\t\treturn (states[key] ?? states[legacyKey])?.tokens;\n\t}\n\n\t/** Returns whether credentials were stored for the server. Removes legacy state the server would take over. */\n\tremove(name: string, serverUrl: string): boolean {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn this.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tconst stored = key in states ? key : legacyKey in states ? legacyKey : undefined;\n\t\t\tif (!stored) return { result: false };\n\t\t\tdelete states[stored];\n\t\t\treturn { result: true, next: serializeStates(states) };\n\t\t});\n\t}\n\n\tprivate write(update: (states: StoredStates) => void): void {\n\t\tthis.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tupdate(states);\n\t\t\treturn { result: undefined, next: serializeStates(states) };\n\t\t});\n\t}\n}\n\nfunction registeredRedirectUrls(client: OAuthClientInformationMixed | undefined): string[] {\n\treturn client && \"redirect_uris\" in client ? client.redirect_uris : [];\n}\n\n/** 12 characters identifying an MCP server URL in callback paths, computed like Codex does. */\nfunction callbackId(serverUrl: string): string {\n\tconst url = new URL(serverUrl);\n\turl.hash = \"\";\n\treturn createHash(\"sha256\").update(url.href).digest().subarray(0, 9).toString(\"base64url\");\n}\n\n/**\n * pi's Client ID Metadata Document, for `clientRegistration: \"cimd\"`, chosen like Codex chooses its own.\n * The configuration ensures the default callback path. Without the `iss` parameter in authorization\n * responses (RFC 9207), the redirect URI and the document are specific to the MCP server, so a response\n * cannot be mixed up with one from another authorization server (RFC 9700 section 4.4.2.2).\n */\nfunction clientMetadataDocument(\n\tserverUrl: string,\n\tredirectUrl: string,\n\tmetadata: AuthorizationServerMetadata | undefined,\n): OAuthClientMetadataDocument {\n\tif (\n\t\t!metadata?.client_id_metadata_document_supported ||\n\t\t!metadata.token_endpoint_auth_methods_supported?.includes(\"none\")\n\t) {\n\t\tthrow new Error(\n\t\t\t'The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration \"cimd\"',\n\t\t);\n\t}\n\tif (metadata.authorization_response_iss_parameter_supported) {\n\t\treturn { url: `${CLIENT_METADATA_BASE_URL}/client.json`, redirectUrl };\n\t}\n\tconst id = callbackId(serverUrl);\n\tconst redirect = new URL(redirectUrl);\n\tredirect.pathname = `${CALLBACK_PATH}/${id}`;\n\treturn { url: `${CLIENT_METADATA_BASE_URL}/${id}/client.json`, redirectUrl: redirect.href };\n}\n\nfunction createProvider(\n\tserverUrl: string,\n\tstore: McpOAuthStateStore,\n\tsettings: McpOAuthSettings,\n\tredirectUrl: string,\n\tonRedirect: (url: URL) => void,\n): McpOAuthProvider {\n\treturn new McpOAuthProvider({\n\t\tserverUrl,\n\t\tredirectUrl,\n\t\tclientMetadata: { client_name: settings.clientName ?? APP_NAME },\n\t\tclientMetadataDocument:\n\t\t\tsettings.clientRegistration === \"cimd\"\n\t\t\t\t? (metadata) => clientMetadataDocument(serverUrl, redirectUrl, metadata)\n\t\t\t\t: undefined,\n\t\tclientId: settings.clientId,\n\t\tclientSecret: settings.clientSecret,\n\t\tstore,\n\t\tonRedirect,\n\t});\n}\n\nexport interface McpAuthProvider extends AuthProvider {\n\t/** Resolves when no refresh is running, so shutdown does not drop rotated tokens before they are saved. */\n\tsettled(): Promise<void>;\n}\n\n/**\n * Auth provider for MCP connections: sends the stored access token and refreshes it when it is about\n * to expire or after a 401. Throws `McpOAuthAuthorizationRequiredError` when the user has to sign in,\n * including when the server asks for more scope (`insufficient_scope`). `onChallenge` receives the\n * server's `WWW-Authenticate` challenge so sign-in can use its resource metadata URL and scope.\n * `settings` is only called when a refresh is needed, so a secret that fails to resolve fails the\n * refresh instead of the whole connection setup.\n *\n * Many servers rotate refresh tokens, so two refreshes with the same refresh token lose the grant.\n * Requests in this process share one refresh, and other processes are kept out by the store's\n * refresh lock, held from reading the tokens to saving new ones. Tokens that changed meanwhile\n * (another process refreshed them, or the user signed in) are used without refreshing.\n */\nexport function createMcpAuthProvider(options: {\n\tserverUrl: string;\n\tstore: McpOAuthServerStore;\n\tsettings: () => McpOAuthSettings;\n\tonChallenge: (challenge: OAuthChallenge) => void;\n}): McpAuthProvider {\n\tconst { serverUrl, store } = options;\n\tlet refreshing: Promise<void> | undefined;\n\n\t/** Replace `staleToken`, the access token that expired or was rejected. */\n\tconst refresh = (staleToken: string | undefined, fetch: McpFetch = globalThis.fetch, challenge?: OAuthChallenge) => {\n\t\trefreshing ??= store\n\t\t\t.withRefreshLock(async () => {\n\t\t\t\tconst state = await store.load();\n\t\t\t\tif (state?.tokens?.access_token !== staleToken) return;\n\t\t\t\tif (!state?.tokens?.refresh_token) throw new McpOAuthAuthorizationRequiredError();\n\t\t\t\tconst settings = options.settings();\n\t\t\t\tconst redirectUrl =\n\t\t\t\t\tcallbackSettings(settings).fixedRedirectUrl ??\n\t\t\t\t\tregisteredRedirectUrls(state.clientInformation)[0] ??\n\t\t\t\t\tFALLBACK_REDIRECT_URL;\n\t\t\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, () => {});\n\t\t\t\t// Refreshes the tokens, or reports that a new sign-in is needed.\n\t\t\t\tconst result = await authorizeMcp(provider, {\n\t\t\t\t\tserverUrl,\n\t\t\t\t\tresourceMetadataUrl: challenge?.resourceMetadataUrl,\n\t\t\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t\t\tscope: challenge?.scope,\n\t\t\t\t\tfetch: (input, init) =>\n\t\t\t\t\t\tfetch(input, { ...init, signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS) }),\n\t\t\t\t});\n\t\t\t\tif (result === \"REDIRECT\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\trefreshing = undefined;\n\t\t\t});\n\t\treturn refreshing;\n\t};\n\n\treturn {\n\t\ttoken: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t\tconst state = await store.load();\n\t\t\tconst token = state?.tokens?.access_token;\n\t\t\tconst expired = state?.tokensExpireAt !== undefined && state.tokensExpireAt - REFRESH_SKEW_MS <= Date.now();\n\t\t\tif (!expired || !state?.tokens?.refresh_token) return token;\n\t\t\t// Failures fall through: the request goes out with the old token and a 401 decides what happens.\n\t\t\tawait refresh(token).catch(() => undefined);\n\t\t\treturn (await store.load())?.tokens?.access_token;\n\t\t},\n\t\tonUnauthorized: async (context) => {\n\t\t\tconst challenge = parseWwwAuthenticate(context.response.headers.get(\"www-authenticate\"));\n\t\t\toptions.onChallenge(challenge);\n\t\t\t// A refresh keeps the granted scope, so more scope needs a new sign-in.\n\t\t\tif (challenge.error === \"insufficient_scope\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\tawait refresh(context.token, context.fetch, challenge);\n\t\t},\n\t\tsettled: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t},\n\t};\n}\n\nexport interface McpSignInPrompt {\n\t/** Show the authorization URL to the user and open it in a browser. */\n\tshowAuthorizationUrl(url: URL): void;\n\t/**\n\t * Ask for the redirect URL from the browser address bar, for when the browser cannot reach the\n\t * loopback callback (for example over SSH). Aborted once the callback arrives. Resolves to\n\t * `undefined` or an empty string when the user cancels.\n\t */\n\tpromptForRedirectUrl(signal: AbortSignal): Promise<string | undefined>;\n}\n\nexport class McpSignInCancelledError extends Error {\n\tconstructor() {\n\t\tsuper(\"Sign-in cancelled\");\n\t\tthis.name = \"McpSignInCancelledError\";\n\t}\n}\n\ntype AuthorizationResponse = Pick<OAuthCallback, \"code\" | \"iss\">;\n\nfunction responseFromRedirectUrl(input: string, state: string, redirectUrl: URL): AuthorizationResponse {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(input.trim());\n\t} catch {\n\t\tthrow new Error(\"Expected the full redirect URL from the browser address bar\");\n\t}\n\t// A server-specific redirect URI tells authorization servers apart, so it must match exactly.\n\tif (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {\n\t\tthrow new Error(\"The redirect URL does not match this sign-in's redirect URI\");\n\t}\n\tconst error = url.searchParams.get(\"error\");\n\tif (error) throw new Error(url.searchParams.get(\"error_description\") ?? error);\n\tif (url.searchParams.get(\"state\") !== state) throw new Error(\"The redirect URL belongs to a different sign-in\");\n\tconst code = url.searchParams.get(\"code\");\n\tif (!code) throw new Error(\"The redirect URL does not contain an authorization code\");\n\treturn { code, iss: url.searchParams.get(\"iss\") ?? undefined };\n}\n\n/** Wait for the browser callback or a pasted redirect URL, whichever comes first. */\nasync function waitForAuthorizationResponse(\n\tcallback: OAuthCallbackServer,\n\tstate: string,\n\tredirectUrl: URL,\n\tprompt: McpSignInPrompt,\n): Promise<AuthorizationResponse> {\n\tconst controller = new AbortController();\n\tconst fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);\n\tconst fromUser = prompt.promptForRedirectUrl(controller.signal).then((input) => {\n\t\tif (!input?.trim()) throw new McpSignInCancelledError();\n\t\treturn responseFromRedirectUrl(input, state, redirectUrl);\n\t});\n\ttry {\n\t\treturn await Promise.race([fromBrowser, fromUser]);\n\t} finally {\n\t\t// The losing side rejects once the prompt is aborted or the callback server closes.\n\t\tcontroller.abort();\n\t\tfromBrowser.catch(() => undefined);\n\t\tfromUser.catch(() => undefined);\n\t}\n}\n\n/** Listen on `port`, or on a free port when it is taken and not `required`. */\nasync function listenForCallback(\n\tsettings: CallbackSettings,\n\textraPaths: string[],\n\tport: number | undefined,\n\trequired: boolean,\n): Promise<OAuthCallbackServer> {\n\tconst options = {\n\t\thost: settings.host,\n\t\tredirectHost: settings.redirectHost,\n\t\tpath: settings.path,\n\t\textraPaths,\n\t\trenderPage: (page: OAuthCallbackPage) =>\n\t\t\tpage.ok\n\t\t\t\t? oauthSuccessHtml(\"Signed in to the MCP server. You may now close this page.\")\n\t\t\t\t: oauthErrorHtml(page.message, page.details),\n\t};\n\ttry {\n\t\treturn await OAuthCallbackServer.listen({ ...options, port: port ?? 0 });\n\t} catch (error) {\n\t\tif (required || port === undefined) throw error;\n\t\treturn OAuthCallbackServer.listen(options);\n\t}\n}\n\n/**\n * Sign in to an MCP server. Uses the stored refresh token when possible; otherwise runs the browser\n * authorization code flow. Tokens are saved to `store`.\n */\nexport async function signInMcpServer(options: {\n\tserverUrl: string;\n\tstore: McpOAuthStateStore;\n\tsettings: McpOAuthSettings;\n\tchallenge?: OAuthChallenge;\n\tprompt: McpSignInPrompt;\n}): Promise<void> {\n\tconst { serverUrl, store, settings } = options;\n\tconst stored = await store.load();\n\tconst stepUp = options.challenge?.error === \"insufficient_scope\";\n\tconst callbackOptions = callbackSettings(settings);\n\t// Reuse the port of the registered redirect URI so the registered client stays valid.\n\tconst registered = registeredRedirectUrls(stored?.clientInformation)[0];\n\tconst preferredPort =\n\t\tcallbackOptions.port ?? (registered ? Number(new URL(registered).port) || undefined : undefined);\n\tconst cimd = settings.clientRegistration === \"cimd\";\n\tconst callback = await listenForCallback(\n\t\tcallbackOptions,\n\t\t// The redirect URI of a server-specific Client ID Metadata Document.\n\t\tcimd ? [`${CALLBACK_PATH}/${callbackId(serverUrl)}`] : [],\n\t\tpreferredPort,\n\t\tcallbackOptions.port !== undefined,\n\t);\n\tconst redirectUrl = callbackOptions.fixedRedirectUrl ?? callback.redirectUrl;\n\ttry {\n\t\tif (stored) {\n\t\t\tconst next: McpOAuthState = { ...stored };\n\t\t\t// Every sign-in gets a fresh `state` parameter.\n\t\t\tdelete next.oauthState;\n\t\t\t// A registered client cannot use another redirect URI, and its tokens belong to it. A Client ID\n\t\t\t// Metadata Document is not stored, so with one, a stored client was registered before and is replaced.\n\t\t\tconst keepClient =\n\t\t\t\tsettings.clientId ||\n\t\t\t\t(cimd ? !stored.clientInformation : registeredRedirectUrls(stored.clientInformation).includes(redirectUrl));\n\t\t\tif (!keepClient) {\n\t\t\t\tdelete next.clientInformation;\n\t\t\t\tdelete next.tokens;\n\t\t\t\tdelete next.tokensExpireAt;\n\t\t\t}\n\t\t\tawait store.save(next);\n\t\t}\n\n\t\tlet authorizationUrl: URL | undefined;\n\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, (url) => {\n\t\t\tauthorizationUrl = url;\n\t\t});\n\t\tconst flow = {\n\t\t\tserverUrl,\n\t\t\tresourceMetadataUrl: options.challenge?.resourceMetadataUrl,\n\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t// A server asking for more scope gets it on top of the configured scope and, since the challenge\n\t\t\t// may list only the missing scopes, on top of the scope granted so far.\n\t\t\tscope: mergeScopes(\n\t\t\t\tsettings.scope,\n\t\t\t\tstepUp ? stepUpScope(stored?.tokens?.scope, options.challenge?.scope) : options.challenge?.scope,\n\t\t\t),\n\t\t};\n\t\t// A refresh keeps the granted scope; a server asking for more needs the browser flow.\n\t\tconst skipRefresh = stepUp;\n\t\tif ((await authorizeMcp(provider, { ...flow, skipRefresh })) === \"AUTHORIZED\") return;\n\t\tif (!authorizationUrl) throw new Error(\"OAuth flow did not produce an authorization URL\");\n\n\t\tconst state = await provider.state();\n\t\t// The flow picks the redirect URI, which may be specific to the MCP server.\n\t\tconst authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get(\"redirect_uri\") ?? redirectUrl);\n\t\toptions.prompt.showAuthorizationUrl(authorizationUrl);\n\t\tconst { code, iss } = await waitForAuthorizationResponse(\n\t\t\tcallback,\n\t\t\tstate,\n\t\t\tauthorizationRedirectUrl,\n\t\t\toptions.prompt,\n\t\t);\n\t\tawait authorizeMcp(provider, { ...flow, authorizationCode: code, iss });\n\t} finally {\n\t\tawait callback.close();\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/extensions/mcp/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAE1F,OAAO,EAEN,YAAY,EACZ,kCAAkC,EAClC,gBAAgB,EAKhB,mBAAmB,EAInB,oBAAoB,EACpB,WAAW,GACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,QAAQ,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,4GAA4G;AAC5G,MAAM,wBAAwB,GAAG,sBAAsB,CAAC;AACxD,2FAA2F;AAC3F,MAAM,qBAAqB,GAAG,UAAU,aAAa,GAAG,aAAa,EAAE,CAAC;AACxE,6EAA6E;AAC7E,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B;;;GAGG;AACH,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,yGAAyG;AACzG,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,sFAAsF;AACtF,MAAM,oBAAoB,GAAG,MAAM,CAAC;AACpC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AA+BlC,SAAS,gBAAgB,CAAC,QAA0B;IACnD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,UAAU,aAAa,GAAG,aAAa,EAAE,CAAC,CAAC;IACvF,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;IACjE,IAAI,gBAAoC,CAAC;IACzC,iGAAiG;IACjG,IAAI,GAAG,CAAC,IAAI;QAAE,gBAAgB,GAAG,QAAQ,CAAC,WAAW,CAAC;SACjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,gBAAgB,GAAG,GAAG,CAAC,IAAI,CAAC;IAC7B,CAAC;IACD,OAAO;QACN,iFAAiF;QACjF,IAAI,EAAE,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO;QACvD,YAAY,EAAE,OAAO;QACrB,IAAI;QACJ,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,gBAAgB;KAChB,CAAC;AACH,CAAC;AAED,uCAAuC;AACvC,SAAS,WAAW,CAAC,GAAG,MAA8B;IACrD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAClG,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAID,SAAS,WAAW,CAAC,OAA2B;IAC/C,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;AAChH,CAAC;AAED,SAAS,eAAe,CAAC,MAAoB;IAC5C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,IAAY,EAAE,SAAiB;IACjD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7C,OAAO,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC;AAOD,sGAAsG;AACtG,MAAM,OAAO,uBAAuB;IAKnC,YAAY,OAA4B,EAAE,OAAgB;QACzD,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,sBAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;QAC3F,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAClD,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,SAAiB;QACxC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtD,OAAO;YACN,+FAA+F;YAC/F,IAAI,EAAE,GAAG,EAAE,CACV,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;gBACjC,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;gBACpC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;oBAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtE,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;gBAChC,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC;gBACzB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/D,CAAC,CAAC;YACH,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE;gBACrB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACrB,CAAC,CAAC;YACH,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC;SACtD,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,eAAe,CAAI,GAAW,EAAE,EAAoB;QACjE,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,EAAE,CAAC;QAC/B,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,IAAI,EAAE,CAAC,EAAE;YACnF,QAAQ,EAAE,KAAK;YACf,KAAK,EAAE,qBAAqB;YAC5B,OAAO,EAAE;gBACR,OAAO,EAAE,oBAAoB,GAAG,qBAAqB;gBACrD,MAAM,EAAE,CAAC;gBACT,UAAU,EAAE,qBAAqB;gBACjC,UAAU,EAAE,qBAAqB;aACjC;YACD,oFAAoF;YACpF,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC;SACvB,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,OAAO,MAAM,EAAE,EAAE,CAAC;QACnB,CAAC;gBAAS,CAAC;YACV,MAAM,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACxC,CAAC;IACF,CAAC;IAED,qHAAqH;IACrH,MAAM,CAAC,IAAY,EAAE,SAAiB;QACrC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QACtF,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC;IACnD,CAAC;IAED,+GAA+G;IAC/G,MAAM,CAAC,IAAY,EAAE,SAAiB;QACrC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;YACjF,IAAI,CAAC,MAAM;gBAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YACtC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;YACtB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,MAAsC;QACnD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,CAAC,MAAM,CAAC,CAAC;YACf,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7D,CAAC,CAAC,CAAC;IACJ,CAAC;CACD;AAED,+FAA+F;AAC/F,SAAS,UAAU,CAAC,QAAkB,UAAU,CAAC,KAAK;IACrD,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;QAC9D,OAAO,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5G,CAAC,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,MAA+C;IAC9E,OAAO,MAAM,IAAI,eAAe,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,+FAA+F;AAC/F,SAAS,UAAU,CAAC,SAAiB;IACpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/B,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;IACd,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC5F,CAAC;AAED;;;;;GAKG;AACH,SAAS,sBAAsB,CAC9B,SAAiB,EACjB,WAAmB,EACnB,QAAiD;IAEjD,IACC,CAAC,QAAQ,EAAE,qCAAqC;QAChD,CAAC,QAAQ,CAAC,qCAAqC,EAAE,QAAQ,CAAC,MAAM,CAAC,EAChE,CAAC;QACF,MAAM,IAAI,KAAK,CACd,mIAAmI,CACnI,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,8CAA8C,EAAE,CAAC;QAC7D,OAAO,EAAE,GAAG,EAAE,GAAG,wBAAwB,cAAc,EAAE,WAAW,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IACtC,QAAQ,CAAC,QAAQ,GAAG,GAAG,aAAa,IAAI,EAAE,EAAE,CAAC;IAC7C,OAAO,EAAE,GAAG,EAAE,GAAG,wBAAwB,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC7F,CAAC;AAED,SAAS,cAAc,CACtB,SAAiB,EACjB,KAAyB,EACzB,QAA0B,EAC1B,WAAmB,EACnB,UAA8B;IAE9B,OAAO,IAAI,gBAAgB,CAAC;QAC3B,SAAS;QACT,WAAW;QACX,cAAc,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,UAAU,IAAI,QAAQ,EAAE;QAChE,sBAAsB,EACrB,QAAQ,CAAC,kBAAkB,KAAK,MAAM;YACrC,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,sBAAsB,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC;YACxE,CAAC,CAAC,SAAS;QACb,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,KAAK;QACL,UAAU;KACV,CAAC,CAAC;AACJ,CAAC;AAOD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAKrC;IACA,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACrC,IAAI,UAAqC,CAAC;IAE1C,2EAA2E;IAC3E,MAAM,OAAO,GAAG,CAAC,UAA8B,EAAE,QAAkB,UAAU,CAAC,KAAK,EAAE,SAA0B,EAAE,EAAE;QAClH,UAAU,KAAK,KAAK;aAClB,eAAe,CAAC,KAAK,IAAI,EAAE;YAC3B,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACjC,IAAI,KAAK,EAAE,MAAM,EAAE,YAAY,KAAK,UAAU;gBAAE,OAAO;YACvD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa;gBAAE,MAAM,IAAI,kCAAkC,EAAE,CAAC;YAClF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,WAAW,GAChB,gBAAgB,CAAC,QAAQ,CAAC,CAAC,gBAAgB;gBAC3C,sBAAsB,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBAClD,qBAAqB,CAAC;YACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACnF,gGAAgG;YAChG,mFAAmF;YACnF,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE;gBAC3C,SAAS;gBACT,mBAAmB,EAAE,SAAS,EAAE,mBAAmB;gBACnD,8BAA8B,EAAE,QAAQ,CAAC,qBAAqB;gBAC9D,KAAK,EAAE,SAAS,EAAE,KAAK;gBACvB,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC;aACxB,CAAC,CAAC;YACH,IAAI,MAAM,KAAK,UAAU;gBAAE,MAAM,IAAI,kCAAkC,EAAE,CAAC;QAC3E,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACb,UAAU,GAAG,SAAS,CAAC;QACxB,CAAC,CAAC,CAAC;QACJ,OAAO,UAAU,CAAC;IACnB,CAAC,CAAC;IAEF,OAAO;QACN,KAAK,EAAE,KAAK,IAAI,EAAE;YACjB,MAAM,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC;YAC1C,MAAM,OAAO,GAAG,KAAK,EAAE,cAAc,KAAK,SAAS,IAAI,KAAK,CAAC,cAAc,GAAG,eAAe,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5G,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa;gBAAE,OAAO,KAAK,CAAC;YAC5D,iGAAiG;YACjG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC5C,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC;QACnD,CAAC;QACD,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACjC,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC;YACzF,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC/B,wEAAwE;YACxE,IAAI,SAAS,CAAC,KAAK,KAAK,oBAAoB;gBAAE,MAAM,IAAI,kCAAkC,EAAE,CAAC;YAC7F,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YACnB,MAAM,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;KACD,CAAC;AACH,CAAC;AAaD,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACjD;QACC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACvC,CAAC;CACD;AAID,SAAS,uBAAuB,CAAC,KAAa,EAAE,KAAa,EAAE,WAAgB;IAC9E,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IAChF,CAAC;IACD,8FAA8F;IAC9F,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC,QAAQ,EAAE,CAAC;QAChF,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5C,IAAI,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,KAAK,CAAC,CAAC;IAC/E,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IAChH,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACtF,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,4BAA4B,CAC1C,QAA6B,EAC7B,KAAa,EACb,WAAgB,EAChB,MAAuB,EACvB,MAA+B;IAE/B,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;IAC/F,MAAM,WAAW,GAAG,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,MAAM,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACzE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;QACxD,OAAO,uBAAuB,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACJ,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpD,CAAC;YAAS,CAAC;QACV,oFAAoF;QACpF,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACnC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;AACF,CAAC;AAED,+EAA+E;AAC/E,KAAK,UAAU,iBAAiB,CAC/B,QAA0B,EAC1B,UAAoB,EACpB,IAAwB,EACxB,QAAiB;IAEjB,MAAM,OAAO,GAAG;QACf,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,UAAU;QACV,UAAU,EAAE,CAAC,IAAuB,EAAE,EAAE,CACvC,IAAI,CAAC,EAAE;YACN,CAAC,CAAC,gBAAgB,CAAC,2DAA2D,CAAC;YAC/E,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC;KAC9C,CAAC;IACF,IAAI,CAAC;QACJ,OAAO,MAAM,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,QAAQ,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,KAAK,CAAC;QAChD,OAAO,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAOrC;IACA,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvD,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;IACzD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,KAAK,oBAAoB,CAAC;IACjE,MAAM,eAAe,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACnD,sFAAsF;IACtF,MAAM,UAAU,GAAG,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,aAAa,GAClB,eAAe,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAClG,MAAM,IAAI,GAAG,QAAQ,CAAC,kBAAkB,KAAK,MAAM,CAAC;IACpD,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CACvC,eAAe;IACf,qEAAqE;IACrE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EACzD,aAAa,EACb,eAAe,CAAC,IAAI,KAAK,SAAS,CAClC,CAAC;IACF,MAAM,WAAW,GAAG,eAAe,CAAC,gBAAgB,IAAI,QAAQ,CAAC,WAAW,CAAC;IAC7E,IAAI,CAAC;QACJ,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,GAAkB,EAAE,GAAG,MAAM,EAAE,CAAC;YAC1C,gDAAgD;YAChD,OAAO,IAAI,CAAC,UAAU,CAAC;YACvB,gGAAgG;YAChG,uGAAuG;YACvG,MAAM,UAAU,GACf,QAAQ,CAAC,QAAQ;gBACjB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;YAC7G,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjB,OAAO,IAAI,CAAC,iBAAiB,CAAC;gBAC9B,OAAO,IAAI,CAAC,MAAM,CAAC;gBACnB,OAAO,IAAI,CAAC,cAAc,CAAC;YAC5B,CAAC;YACD,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,gBAAiC,CAAC;QACtC,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE;YAChF,gBAAgB,GAAG,GAAG,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG;YACZ,SAAS;YACT,KAAK,EAAE,UAAU,EAAE;YACnB,MAAM;YACN,mBAAmB,EAAE,OAAO,CAAC,SAAS,EAAE,mBAAmB;YAC3D,8BAA8B,EAAE,QAAQ,CAAC,qBAAqB;YAC9D,iGAAiG;YACjG,wEAAwE;YACxE,KAAK,EAAE,WAAW,CACjB,QAAQ,CAAC,KAAK,EACd,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAChG;SACD,CAAC;QACF,sFAAsF;QACtF,MAAM,WAAW,GAAG,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,YAAY;YAAE,OAAO;QACtF,IAAI,CAAC,gBAAgB;YAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QAE1F,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QACrC,4EAA4E;QAC5E,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,WAAW,CAAC,CAAC;QAC3G,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;QACtD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,4BAA4B,CACvD,QAAQ,EACR,KAAK,EACL,wBAAwB,EACxB,OAAO,CAAC,MAAM,EACd,MAAM,CACN,CAAC;QACF,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,4FAA4F;QAC5F,IAAI,MAAM,EAAE,OAAO;YAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;QACzD,MAAM,KAAK,CAAC;IACb,CAAC;YAAS,CAAC;QACV,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACF,CAAC","sourcesContent":["/**\n * OAuth sign-in for remote MCP servers.\n *\n * Connections never start a browser flow on their own. They send the stored access token and, after\n * a 401, try the stored refresh token. When that is not possible they fail with\n * `McpOAuthAuthorizationRequiredError`, and the user signs in through `/mcp`, which runs\n * the authorization code flow (PKCE, a Client ID Metadata Document or dynamic client registration)\n * against a loopback callback.\n *\n * Credentials live in `<agent-dir>/mcp-auth.json`, keyed by server name and URL.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"@earendil-works/pi-ai/utils/oauth-page\";\nimport type { AuthProvider, McpFetch } from \"@earendil-works/pi-mcp\";\nimport {\n\ttype AuthorizationServerMetadata,\n\tauthorizeMcp,\n\tMcpOAuthAuthorizationRequiredError,\n\tMcpOAuthProvider,\n\ttype McpOAuthState,\n\ttype McpOAuthStateStore,\n\ttype OAuthCallback,\n\ttype OAuthCallbackPage,\n\tOAuthCallbackServer,\n\ttype OAuthChallenge,\n\ttype OAuthClientInformationMixed,\n\ttype OAuthClientMetadataDocument,\n\tparseWwwAuthenticate,\n\tstepUpScope,\n} from \"@earendil-works/pi-mcp/oauth\";\nimport lockfile from \"proper-lockfile\";\nimport { APP_NAME, getAgentDir } from \"../../config.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../../core/auth-storage.ts\";\nimport { mcpNamespace } from \"../../core/mcp-servers.ts\";\n\nconst CALLBACK_HOST = \"127.0.0.1\";\nconst CALLBACK_PATH = \"/callback\";\n/** Where pi.dev serves pi's Client ID Metadata Documents: `client.json` and `<callback ID>/client.json`. */\nconst CLIENT_METADATA_BASE_URL = \"https://pi.dev/oauth\";\n/** Redirect URI for refreshes when none is stored. Refreshing never redirects the user. */\nconst FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;\n/** Access tokens this close to expiry are refreshed before they are sent. */\nconst REFRESH_SKEW_MS = 30_000;\n/**\n * Bounds each request to the authorization server, so an unresponsive one cannot hold the refresh lock,\n * delay shutdown, or keep a sign-in waiting for long.\n */\nconst OAUTH_REQUEST_TIMEOUT_MS = 15_000;\n/** A refresh lock that its holder stopped renewing (the process was killed) is taken over after this. */\nconst REFRESH_LOCK_STALE_MS = 20_000;\n/** How long to wait for another process's refresh: longer than a stale lock lives. */\nconst REFRESH_LOCK_WAIT_MS = 25_000;\nconst REFRESH_LOCK_RETRY_MS = 100;\n\nexport interface McpOAuthSettings {\n\tclientId?: string;\n\t/** Already resolved. */\n\tclientSecret?: string;\n\tcallbackPort?: number;\n\t/** Loopback redirect URI; see `McpOAuthConfig.callbackUrl`. */\n\tcallbackUrl?: string;\n\t/** Scopes to request, separated by spaces. */\n\tscope?: string;\n\t/** `client_name` for dynamic client registration. Default: `APP_NAME`. */\n\tclientName?: string;\n\t/** See `McpOAuthConfig.clientRegistration`. */\n\tclientRegistration?: \"dcr\" | \"cimd\";\n\t/** See `McpOAuthConfig.authServerMetadataUrl`. */\n\tauthServerMetadataUrl?: URL;\n}\n\n/** Where the loopback callback server listens and the redirect URI it serves. */\ninterface CallbackSettings {\n\t/** Address to listen on. */\n\thost: string;\n\t/** Host name in the redirect URI. */\n\tredirectHost: string;\n\tport: number | undefined;\n\tpath: string;\n\t/** The exact redirect URI, when the port is fixed. */\n\tfixedRedirectUrl: string | undefined;\n}\n\nfunction callbackSettings(settings: McpOAuthSettings): CallbackSettings {\n\tconst url = new URL(settings.callbackUrl ?? `http://${CALLBACK_HOST}${CALLBACK_PATH}`);\n\tconst address = url.hostname.replace(/^\\[|\\]$/g, \"\");\n\tconst port = url.port ? Number(url.port) : settings.callbackPort;\n\tlet fixedRedirectUrl: string | undefined;\n\t// A configured URI with a port is sent exactly as written, since servers compare it as a string.\n\tif (url.port) fixedRedirectUrl = settings.callbackUrl;\n\telse if (port !== undefined) {\n\t\turl.port = String(port);\n\t\tfixedRedirectUrl = url.href;\n\t}\n\treturn {\n\t\t// `localhost` is served on 127.0.0.1; browsers fall back to it when ::1 refuses.\n\t\thost: address === \"localhost\" ? CALLBACK_HOST : address,\n\t\tredirectHost: address,\n\t\tport,\n\t\tpath: url.pathname,\n\t\tfixedRedirectUrl,\n\t};\n}\n\n/** Scopes of both lists, each once. */\nfunction mergeScopes(...scopes: (string | undefined)[]): string | undefined {\n\tconst merged = [...new Set(scopes.flatMap((scope) => scope?.split(/\\s+/).filter(Boolean) ?? []))];\n\treturn merged.length > 0 ? merged.join(\" \") : undefined;\n}\n\ntype StoredStates = Record<string, McpOAuthState>;\n\nfunction parseStates(content: string | undefined): StoredStates {\n\tif (!content?.trim()) return {};\n\tconst parsed: unknown = JSON.parse(content);\n\treturn typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed) ? (parsed as StoredStates) : {};\n}\n\nfunction serializeStates(states: StoredStates): string {\n\treturn `${JSON.stringify(states, null, 2)}\\n`;\n}\n\n/**\n * Keys of a server's state: by name and URL, so servers sharing a URL keep separate accounts, and the\n * legacy key by URL alone, written by older versions.\n */\nfunction storeKeys(name: string, serverUrl: string): { key: string; legacyKey: string } {\n\tconst legacyKey = String(new URL(serverUrl));\n\treturn { key: `${mcpNamespace(name)}|${legacyKey}`, legacyKey };\n}\n\nexport interface McpOAuthServerStore extends McpOAuthStateStore {\n\t/** Run `fn` while no other process refreshes the server's tokens. */\n\twithRefreshLock<T>(fn: () => Promise<T>): Promise<T>;\n}\n\n/** Per-server OAuth state (client registration, tokens, pending PKCE verifier) in `mcp-auth.json`. */\nexport class McpOAuthCredentialStore {\n\tprivate readonly backend: AuthStorageBackend;\n\t/** Directory for the refresh lock files. Without one, refreshes are only serialized in this process. */\n\tprivate readonly lockDir: string | undefined;\n\n\tconstructor(backend?: AuthStorageBackend, lockDir?: string) {\n\t\tthis.backend = backend ?? new FileAuthStorageBackend(join(getAgentDir(), \"mcp-auth.json\"));\n\t\tthis.lockDir = backend ? lockDir : getAgentDir();\n\t}\n\n\tforServer(name: string, serverUrl: string): McpOAuthServerStore {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn {\n\t\t\t// The first server to load legacy state takes it over; others with the same URL sign in again.\n\t\t\tload: () =>\n\t\t\t\tthis.backend.withLock((current) => {\n\t\t\t\t\tconst states = parseStates(current);\n\t\t\t\t\tif (states[key] || !states[legacyKey]) return { result: states[key] };\n\t\t\t\t\tstates[key] = states[legacyKey];\n\t\t\t\t\tdelete states[legacyKey];\n\t\t\t\t\treturn { result: states[key], next: serializeStates(states) };\n\t\t\t\t}),\n\t\t\tsave: (state) =>\n\t\t\t\tthis.write((states) => {\n\t\t\t\t\tstates[key] = state;\n\t\t\t\t}),\n\t\t\twithRefreshLock: (fn) => this.withRefreshLock(key, fn),\n\t\t};\n\t}\n\n\t/**\n\t * A lock file per server. When the process exits, proper-lockfile removes the locks it holds; when\n\t * it is killed, the lock goes stale because it is no longer renewed, and the next process takes it over.\n\t */\n\tprivate async withRefreshLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n\t\tif (!this.lockDir) return fn();\n\t\tmkdirSync(this.lockDir, { recursive: true, mode: 0o700 });\n\t\tconst hash = createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16);\n\t\tconst release = await lockfile.lock(join(this.lockDir, `mcp-auth-refresh-${hash}`), {\n\t\t\trealpath: false,\n\t\t\tstale: REFRESH_LOCK_STALE_MS,\n\t\t\tretries: {\n\t\t\t\tretries: REFRESH_LOCK_WAIT_MS / REFRESH_LOCK_RETRY_MS,\n\t\t\t\tfactor: 1,\n\t\t\t\tminTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t\tmaxTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t},\n\t\t\t// The default throws from a timer. A lost lock at worst lets two refreshes overlap.\n\t\t\tonCompromised: () => {},\n\t\t});\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait release().catch(() => undefined);\n\t\t}\n\t}\n\n\t/** The stored tokens of a server, for noticing sign-ins done by another process. Does not take over legacy state. */\n\ttokens(name: string, serverUrl: string): McpOAuthState[\"tokens\"] {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\tconst states = this.backend.withLock((current) => ({ result: parseStates(current) }));\n\t\treturn (states[key] ?? states[legacyKey])?.tokens;\n\t}\n\n\t/** Returns whether credentials were stored for the server. Removes legacy state the server would take over. */\n\tremove(name: string, serverUrl: string): boolean {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn this.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tconst stored = key in states ? key : legacyKey in states ? legacyKey : undefined;\n\t\t\tif (!stored) return { result: false };\n\t\t\tdelete states[stored];\n\t\t\treturn { result: true, next: serializeStates(states) };\n\t\t});\n\t}\n\n\tprivate write(update: (states: StoredStates) => void): void {\n\t\tthis.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tupdate(states);\n\t\t\treturn { result: undefined, next: serializeStates(states) };\n\t\t});\n\t}\n}\n\n/** `fetch` with `OAUTH_REQUEST_TIMEOUT_MS` per request, on top of the request's own signal. */\nfunction timedFetch(fetch: McpFetch = globalThis.fetch): McpFetch {\n\treturn (input, init) => {\n\t\tconst timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);\n\t\treturn fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout });\n\t};\n}\n\nfunction registeredRedirectUrls(client: OAuthClientInformationMixed | undefined): string[] {\n\treturn client && \"redirect_uris\" in client ? client.redirect_uris : [];\n}\n\n/** 12 characters identifying an MCP server URL in callback paths, computed like Codex does. */\nfunction callbackId(serverUrl: string): string {\n\tconst url = new URL(serverUrl);\n\turl.hash = \"\";\n\treturn createHash(\"sha256\").update(url.href).digest().subarray(0, 9).toString(\"base64url\");\n}\n\n/**\n * pi's Client ID Metadata Document, for `clientRegistration: \"cimd\"`, chosen like Codex chooses its own.\n * The configuration ensures the default callback path. Without the `iss` parameter in authorization\n * responses (RFC 9207), the redirect URI and the document are specific to the MCP server, so a response\n * cannot be mixed up with one from another authorization server (RFC 9700 section 4.4.2.2).\n */\nfunction clientMetadataDocument(\n\tserverUrl: string,\n\tredirectUrl: string,\n\tmetadata: AuthorizationServerMetadata | undefined,\n): OAuthClientMetadataDocument {\n\tif (\n\t\t!metadata?.client_id_metadata_document_supported ||\n\t\t!metadata.token_endpoint_auth_methods_supported?.includes(\"none\")\n\t) {\n\t\tthrow new Error(\n\t\t\t'The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration \"cimd\"',\n\t\t);\n\t}\n\tif (metadata.authorization_response_iss_parameter_supported) {\n\t\treturn { url: `${CLIENT_METADATA_BASE_URL}/client.json`, redirectUrl };\n\t}\n\tconst id = callbackId(serverUrl);\n\tconst redirect = new URL(redirectUrl);\n\tredirect.pathname = `${CALLBACK_PATH}/${id}`;\n\treturn { url: `${CLIENT_METADATA_BASE_URL}/${id}/client.json`, redirectUrl: redirect.href };\n}\n\nfunction createProvider(\n\tserverUrl: string,\n\tstore: McpOAuthStateStore,\n\tsettings: McpOAuthSettings,\n\tredirectUrl: string,\n\tonRedirect: (url: URL) => void,\n): McpOAuthProvider {\n\treturn new McpOAuthProvider({\n\t\tserverUrl,\n\t\tredirectUrl,\n\t\tclientMetadata: { client_name: settings.clientName ?? APP_NAME },\n\t\tclientMetadataDocument:\n\t\t\tsettings.clientRegistration === \"cimd\"\n\t\t\t\t? (metadata) => clientMetadataDocument(serverUrl, redirectUrl, metadata)\n\t\t\t\t: undefined,\n\t\tclientId: settings.clientId,\n\t\tclientSecret: settings.clientSecret,\n\t\tstore,\n\t\tonRedirect,\n\t});\n}\n\nexport interface McpAuthProvider extends AuthProvider {\n\t/** Resolves when no refresh is running, so shutdown does not drop rotated tokens before they are saved. */\n\tsettled(): Promise<void>;\n}\n\n/**\n * Auth provider for MCP connections: sends the stored access token and refreshes it when it is about\n * to expire or after a 401. Throws `McpOAuthAuthorizationRequiredError` when the user has to sign in,\n * including when the server asks for more scope (`insufficient_scope`). `onChallenge` receives the\n * server's `WWW-Authenticate` challenge so sign-in can use its resource metadata URL and scope.\n * `settings` is only called when a refresh is needed, so a secret that fails to resolve fails the\n * refresh instead of the whole connection setup.\n *\n * Many servers rotate refresh tokens, so two refreshes with the same refresh token lose the grant.\n * Requests in this process share one refresh, and other processes are kept out by the store's\n * refresh lock, held from reading the tokens to saving new ones. Tokens that changed meanwhile\n * (another process refreshed them, or the user signed in) are used without refreshing.\n */\nexport function createMcpAuthProvider(options: {\n\tserverUrl: string;\n\tstore: McpOAuthServerStore;\n\tsettings: () => McpOAuthSettings;\n\tonChallenge: (challenge: OAuthChallenge) => void;\n}): McpAuthProvider {\n\tconst { serverUrl, store } = options;\n\tlet refreshing: Promise<void> | undefined;\n\n\t/** Replace `staleToken`, the access token that expired or was rejected. */\n\tconst refresh = (staleToken: string | undefined, fetch: McpFetch = globalThis.fetch, challenge?: OAuthChallenge) => {\n\t\trefreshing ??= store\n\t\t\t.withRefreshLock(async () => {\n\t\t\t\tconst state = await store.load();\n\t\t\t\tif (state?.tokens?.access_token !== staleToken) return;\n\t\t\t\tif (!state?.tokens?.refresh_token) throw new McpOAuthAuthorizationRequiredError();\n\t\t\t\tconst settings = options.settings();\n\t\t\t\tconst redirectUrl =\n\t\t\t\t\tcallbackSettings(settings).fixedRedirectUrl ??\n\t\t\t\t\tregisteredRedirectUrls(state.clientInformation)[0] ??\n\t\t\t\t\tFALLBACK_REDIRECT_URL;\n\t\t\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, () => {});\n\t\t\t\t// Refreshes the tokens, or reports that a new sign-in is needed. Not cancellable: a refresh the\n\t\t\t\t// server answered may have rotated the refresh token, so its answer must be saved.\n\t\t\t\tconst result = await authorizeMcp(provider, {\n\t\t\t\t\tserverUrl,\n\t\t\t\t\tresourceMetadataUrl: challenge?.resourceMetadataUrl,\n\t\t\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t\t\tscope: challenge?.scope,\n\t\t\t\t\tfetch: timedFetch(fetch),\n\t\t\t\t});\n\t\t\t\tif (result === \"REDIRECT\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\trefreshing = undefined;\n\t\t\t});\n\t\treturn refreshing;\n\t};\n\n\treturn {\n\t\ttoken: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t\tconst state = await store.load();\n\t\t\tconst token = state?.tokens?.access_token;\n\t\t\tconst expired = state?.tokensExpireAt !== undefined && state.tokensExpireAt - REFRESH_SKEW_MS <= Date.now();\n\t\t\tif (!expired || !state?.tokens?.refresh_token) return token;\n\t\t\t// Failures fall through: the request goes out with the old token and a 401 decides what happens.\n\t\t\tawait refresh(token).catch(() => undefined);\n\t\t\treturn (await store.load())?.tokens?.access_token;\n\t\t},\n\t\tonUnauthorized: async (context) => {\n\t\t\tconst challenge = parseWwwAuthenticate(context.response.headers.get(\"www-authenticate\"));\n\t\t\toptions.onChallenge(challenge);\n\t\t\t// A refresh keeps the granted scope, so more scope needs a new sign-in.\n\t\t\tif (challenge.error === \"insufficient_scope\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\tawait refresh(context.token, context.fetch, challenge);\n\t\t},\n\t\tsettled: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t},\n\t};\n}\n\nexport interface McpSignInPrompt {\n\t/** Show the authorization URL to the user and open it in a browser. */\n\tshowAuthorizationUrl(url: URL): void;\n\t/**\n\t * Ask for the redirect URL from the browser address bar, for when the browser cannot reach the\n\t * loopback callback (for example over SSH). Aborted once the callback arrives or the sign-in is\n\t * cancelled. Resolves to `undefined` or an empty string when the user cancels.\n\t */\n\tpromptForRedirectUrl(signal: AbortSignal): Promise<string | undefined>;\n}\n\nexport class McpSignInCancelledError extends Error {\n\tconstructor() {\n\t\tsuper(\"Sign-in cancelled\");\n\t\tthis.name = \"McpSignInCancelledError\";\n\t}\n}\n\ntype AuthorizationResponse = Pick<OAuthCallback, \"code\" | \"iss\">;\n\nfunction responseFromRedirectUrl(input: string, state: string, redirectUrl: URL): AuthorizationResponse {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(input.trim());\n\t} catch {\n\t\tthrow new Error(\"Expected the full redirect URL from the browser address bar\");\n\t}\n\t// A server-specific redirect URI tells authorization servers apart, so it must match exactly.\n\tif (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {\n\t\tthrow new Error(\"The redirect URL does not match this sign-in's redirect URI\");\n\t}\n\tconst error = url.searchParams.get(\"error\");\n\tif (error) throw new Error(url.searchParams.get(\"error_description\") ?? error);\n\tif (url.searchParams.get(\"state\") !== state) throw new Error(\"The redirect URL belongs to a different sign-in\");\n\tconst code = url.searchParams.get(\"code\");\n\tif (!code) throw new Error(\"The redirect URL does not contain an authorization code\");\n\treturn { code, iss: url.searchParams.get(\"iss\") ?? undefined };\n}\n\n/**\n * Wait for the browser callback or a pasted redirect URL, whichever comes first. Aborting `signal`\n * aborts the prompt, which then cancels the sign-in.\n */\nasync function waitForAuthorizationResponse(\n\tcallback: OAuthCallbackServer,\n\tstate: string,\n\tredirectUrl: URL,\n\tprompt: McpSignInPrompt,\n\tsignal: AbortSignal | undefined,\n): Promise<AuthorizationResponse> {\n\tconst controller = new AbortController();\n\tconst promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;\n\tconst fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);\n\tconst fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {\n\t\tif (!input?.trim()) throw new McpSignInCancelledError();\n\t\treturn responseFromRedirectUrl(input, state, redirectUrl);\n\t});\n\ttry {\n\t\treturn await Promise.race([fromBrowser, fromUser]);\n\t} finally {\n\t\t// The losing side rejects once the prompt is aborted or the callback server closes.\n\t\tcontroller.abort();\n\t\tfromBrowser.catch(() => undefined);\n\t\tfromUser.catch(() => undefined);\n\t}\n}\n\n/** Listen on `port`, or on a free port when it is taken and not `required`. */\nasync function listenForCallback(\n\tsettings: CallbackSettings,\n\textraPaths: string[],\n\tport: number | undefined,\n\trequired: boolean,\n): Promise<OAuthCallbackServer> {\n\tconst options = {\n\t\thost: settings.host,\n\t\tredirectHost: settings.redirectHost,\n\t\tpath: settings.path,\n\t\textraPaths,\n\t\trenderPage: (page: OAuthCallbackPage) =>\n\t\t\tpage.ok\n\t\t\t\t? oauthSuccessHtml(\"Signed in to the MCP server. You may now close this page.\")\n\t\t\t\t: oauthErrorHtml(page.message, page.details),\n\t};\n\ttry {\n\t\treturn await OAuthCallbackServer.listen({ ...options, port: port ?? 0 });\n\t} catch (error) {\n\t\tif (required || port === undefined) throw error;\n\t\treturn OAuthCallbackServer.listen(options);\n\t}\n}\n\n/**\n * Sign in to an MCP server. Uses the stored refresh token when possible; otherwise runs the browser\n * authorization code flow. Tokens are saved to `store`. Aborting `signal` stops the sign-in at any\n * step with `McpSignInCancelledError`; requests to the authorization server are also time-limited.\n */\nexport async function signInMcpServer(options: {\n\tserverUrl: string;\n\tstore: McpOAuthStateStore;\n\tsettings: McpOAuthSettings;\n\tchallenge?: OAuthChallenge;\n\tprompt: McpSignInPrompt;\n\tsignal?: AbortSignal;\n}): Promise<void> {\n\tconst { serverUrl, store, settings, signal } = options;\n\tif (signal?.aborted) throw new McpSignInCancelledError();\n\tconst stored = await store.load();\n\tconst stepUp = options.challenge?.error === \"insufficient_scope\";\n\tconst callbackOptions = callbackSettings(settings);\n\t// Reuse the port of the registered redirect URI so the registered client stays valid.\n\tconst registered = registeredRedirectUrls(stored?.clientInformation)[0];\n\tconst preferredPort =\n\t\tcallbackOptions.port ?? (registered ? Number(new URL(registered).port) || undefined : undefined);\n\tconst cimd = settings.clientRegistration === \"cimd\";\n\tconst callback = await listenForCallback(\n\t\tcallbackOptions,\n\t\t// The redirect URI of a server-specific Client ID Metadata Document.\n\t\tcimd ? [`${CALLBACK_PATH}/${callbackId(serverUrl)}`] : [],\n\t\tpreferredPort,\n\t\tcallbackOptions.port !== undefined,\n\t);\n\tconst redirectUrl = callbackOptions.fixedRedirectUrl ?? callback.redirectUrl;\n\ttry {\n\t\tif (stored) {\n\t\t\tconst next: McpOAuthState = { ...stored };\n\t\t\t// Every sign-in gets a fresh `state` parameter.\n\t\t\tdelete next.oauthState;\n\t\t\t// A registered client cannot use another redirect URI, and its tokens belong to it. A Client ID\n\t\t\t// Metadata Document is not stored, so with one, a stored client was registered before and is replaced.\n\t\t\tconst keepClient =\n\t\t\t\tsettings.clientId ||\n\t\t\t\t(cimd ? !stored.clientInformation : registeredRedirectUrls(stored.clientInformation).includes(redirectUrl));\n\t\t\tif (!keepClient) {\n\t\t\t\tdelete next.clientInformation;\n\t\t\t\tdelete next.tokens;\n\t\t\t\tdelete next.tokensExpireAt;\n\t\t\t}\n\t\t\tawait store.save(next);\n\t\t}\n\n\t\tlet authorizationUrl: URL | undefined;\n\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, (url) => {\n\t\t\tauthorizationUrl = url;\n\t\t});\n\t\tconst flow = {\n\t\t\tserverUrl,\n\t\t\tfetch: timedFetch(),\n\t\t\tsignal,\n\t\t\tresourceMetadataUrl: options.challenge?.resourceMetadataUrl,\n\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t// A server asking for more scope gets it on top of the configured scope and, since the challenge\n\t\t\t// may list only the missing scopes, on top of the scope granted so far.\n\t\t\tscope: mergeScopes(\n\t\t\t\tsettings.scope,\n\t\t\t\tstepUp ? stepUpScope(stored?.tokens?.scope, options.challenge?.scope) : options.challenge?.scope,\n\t\t\t),\n\t\t};\n\t\t// A refresh keeps the granted scope; a server asking for more needs the browser flow.\n\t\tconst skipRefresh = stepUp;\n\t\tif ((await authorizeMcp(provider, { ...flow, skipRefresh })) === \"AUTHORIZED\") return;\n\t\tif (!authorizationUrl) throw new Error(\"OAuth flow did not produce an authorization URL\");\n\n\t\tconst state = await provider.state();\n\t\t// The flow picks the redirect URI, which may be specific to the MCP server.\n\t\tconst authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get(\"redirect_uri\") ?? redirectUrl);\n\t\toptions.prompt.showAuthorizationUrl(authorizationUrl);\n\t\tconst { code, iss } = await waitForAuthorizationResponse(\n\t\t\tcallback,\n\t\t\tstate,\n\t\t\tauthorizationRedirectUrl,\n\t\t\toptions.prompt,\n\t\t\tsignal,\n\t\t);\n\t\tawait authorizeMcp(provider, { ...flow, authorizationCode: code, iss });\n\t} catch (error) {\n\t\t// Aborted requests fail with the signal's reason; report them as the cancellation they are.\n\t\tif (signal?.aborted) throw new McpSignInCancelledError();\n\t\tthrow error;\n\t} finally {\n\t\tawait callback.close();\n\t}\n}\n"]}
|
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
import type { ListResourcesResult, ListResourceTemplatesResult, McpRequestOptions, ReadResourceResult, Resource, ResourceTemplate } from "@earendil-works/pi-mcp";
|
|
13
13
|
import type { TSchema } from "typebox";
|
|
14
14
|
import type { ToolDefinition } from "../../core/extensions/types.ts";
|
|
15
|
+
import { LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL } from "../../core/mcp-servers.ts";
|
|
15
16
|
import type { McpExposure } from "./config.ts";
|
|
16
|
-
import { type McpToolDetails
|
|
17
|
-
export
|
|
18
|
-
export declare const LIST_MCP_RESOURCE_TEMPLATES_TOOL = "list_mcp_resource_templates";
|
|
19
|
-
export { READ_MCP_RESOURCE_TOOL };
|
|
17
|
+
import { type McpToolDetails } from "./tools.ts";
|
|
18
|
+
export { LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL };
|
|
20
19
|
/** A connected server that offers resources. */
|
|
21
20
|
export interface McpResourceServer {
|
|
22
21
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAEX,mBAAmB,EACnB,2BAA2B,EAC3B,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,EACR,gBAAgB,EAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAEX,mBAAmB,EACnB,2BAA2B,EAC3B,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,EACR,gBAAgB,EAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACtF,OAAO,EACN,gCAAgC,EAChC,uBAAuB,EACvB,sBAAsB,EACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAmB,KAAK,cAAc,EAAkC,MAAM,YAAY,CAAC;AAElG,OAAO,EAAE,gCAAgC,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,CAAC;AAE7F,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACpG,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACpH,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9D,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC9E,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnF;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAGzG;AA6ID;;GAEG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE;IACzD,QAAQ,EAAE,WAAW,CAAC;IACtB,OAAO,EAAE,MAAM,SAAS,iBAAiB,EAAE,CAAC;CAC5C,GAAG,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,CA8I5C"}
|
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
* interfaces for hosts that render them, and so are icons. Read resources become text and images for
|
|
10
10
|
* the model; binary resources are saved to temp files. Scripts get the JSON payloads.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
export { READ_MCP_RESOURCE_TOOL };
|
|
12
|
+
import { LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL, } from "../../core/mcp-servers.js";
|
|
13
|
+
import { limitMcpContent, toModelContent, toToolExposure } from "./tools.js";
|
|
14
|
+
export { LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL };
|
|
16
15
|
/** MCP App user interfaces, which only hosts that render them can use. */
|
|
17
16
|
export function isMcpAppResource(item) {
|
|
18
17
|
const uri = item.uri ?? item.uriTemplate ?? "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../../src/extensions/mcp/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,OAAO,EACN,eAAe,EAEf,sBAAsB,EACtB,cAAc,EACd,cAAc,GACd,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAC5D,MAAM,CAAC,MAAM,gCAAgC,GAAG,6BAA6B,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAalC,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,IAA+D;IAC/F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IAC/C,OAAO,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AAC9F,CAAC;AAED,uFAAuF;AACvF,SAAS,MAAM,CAAgC,MAAc,EAAE,IAAO;IACrE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,IAA+B,CAAC;IAC1E,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,cAAc,GAAG,CAAC,WAAmB,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;AAClF,MAAM,aAAa,GAAG,cAAc,CAAC,4DAA4D,CAAC,CAAC;AACnG,MAAM,MAAM,GAAG,cAAc,CAAC,mFAAmF,CAAC,CAAC;AACnH,MAAM,eAAe,GAAG;IACvB,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE;IACrD,oBAAoB,EAAE,KAAK;CAC3B,CAAC;AACF,MAAM,eAAe,GAAG;IACvB,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACX,MAAM,EAAE,cAAc,CACrB,sGAAsG,CACtG;QACD,GAAG,EAAE,cAAc,CAAC,+EAA+E,CAAC;KACpG;IACD,QAAQ,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;IAC3B,oBAAoB,EAAE,KAAK;CAC3B,CAAC;AAEF,MAAM,cAAc,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC1C,MAAM,cAAc,GAAG;IACtB,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,kCAAkC;IAC/C,KAAK,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QACrE,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;KAC7B;CACD,CAAC;AACF,MAAM,kBAAkB,GAAG;IAC1B,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACX,MAAM,EAAE,cAAc;QACtB,SAAS,EAAE;YACV,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACX,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBAC1B,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACvB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACxB,KAAK,EAAE,cAAc;oBACrB,WAAW,EAAE,cAAc;oBAC3B,QAAQ,EAAE,cAAc;oBACxB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;iBACxB;gBACD,QAAQ,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC;aACnC;SACD;QACD,UAAU,EAAE,cAAc;QAC1B,MAAM,EAAE,cAAc;KACtB;IACD,QAAQ,EAAE,CAAC,WAAW,CAAC;CACvB,CAAC;AACF,MAAM,4BAA4B,GAAG;IACpC,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACX,MAAM,EAAE,cAAc;QACtB,iBAAiB,EAAE;YAClB,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACX,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBAC1B,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,uBAAuB,EAAE;oBACrE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACxB,KAAK,EAAE,cAAc;oBACrB,WAAW,EAAE,cAAc;oBAC3B,QAAQ,EAAE,cAAc;iBACxB;gBACD,QAAQ,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,CAAC;aAC3C;SACD;QACD,UAAU,EAAE,cAAc;QAC1B,MAAM,EAAE,cAAc;KACtB;IACD,QAAQ,EAAE,CAAC,mBAAmB,CAAC;CAC/B,CAAC;AACF,MAAM,kBAAkB,GAAG;IAC1B,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACX,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC1B,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACvB,QAAQ,EAAE;YACT,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACN,KAAK,EAAE;oBACN;wBACC,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;wBAC3F,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;qBACzB;oBACD;wBACC,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACX,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACvB,QAAQ,EAAE,cAAc;4BACxB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE;yBAC/C;wBACD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;qBACzB;iBACD;aACD;SACD;KACD;IACD,QAAQ,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC;CACvC,CAAC;AAEF,SAAS,cAAc,CAAC,MAAe,EAAE,GAAW;IACnD,MAAM,KAAK,GAAI,MAA8C,EAAE,CAAC,GAAG,CAAC,CAAC;IACrE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,mBAAmB,CAAC,CAAC;IAC1E,OAAO,KAAK,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;AAClC,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IACnC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED,KAAK,UAAU,UAAU,CACxB,IAAY,EACZ,MAA0B,EAC1B,OAAgC;IAEhC,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7G,OAAO;QACN,OAAO;QACP,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;QACtF,iBAAiB,EAAE,OAA+B;KAClD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gCAAgC,CAAC,OAGhD;IACA,MAAM,QAAQ,GAAoB,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IAEzD,MAAM,UAAU,GAAG,CAAC,IAAY,EAAqB,EAAE;QACtD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACpE,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,MAAM,IAAI,KAAK,CACd,eAAe,IAAI,qBAAqB,SAAS,CAAC,CAAC,CAAC,6BAA6B,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACnG,CAAC;IACH,CAAC,CAAC;IAEF,6DAA6D;IAC7D,MAAM,IAAI,GAAG,KAAK,EACjB,MAAe,EACf,MAA+B,EAC/B,GAAsC,EACtC,IAOE,EACF,GAA4E,EACzC,EAAE;QACrC,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,CAAC,IAAO,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,IAAiE,CAAC,CAAC;QAClH,IAAI,UAAU,EAAE,CAAC;YAChB,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;YACnF,OAAO;gBACN,MAAM,EAAE,MAAM,CAAC,IAAI;gBACnB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC5E,GAAG,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;aAC7E,CAAC;QACH,CAAC;QACD,IAAI,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QAClF,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACpF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACvC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAC7E,CAAC;QACF,MAAM,KAAK,GAA8B,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAwC,EAAE,CAAC;QACvD,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;YACnC,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW;gBAChC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;;gBAC5E,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACnE,CAAC,CAAC;IAEF,MAAM,aAAa,GAA4C;QAC9D,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACV,gPAAgP;QACjP,UAAU,EAAE,eAAqC;QACjD,YAAY,EAAE,kBAAwC;QACtD,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC1C,WAAW,EAAE,QAAQ;QACrB,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM;YACxC,MAAM,OAAO,GAAG,MAAM,IAAI,CACzB,MAAM,EACN,MAAM,EACN,WAAW,EACX,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE;gBACxC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;gBAClE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;YACnE,CAAC,EACD,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,cAAc,CAAC,CAC/D,CAAC;YACF,OAAO,UAAU,CAAC,uBAAuB,EAAE,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QACvF,CAAC;KACD,CAAC;IAEF,MAAM,aAAa,GAA4C;QAC9D,IAAI,EAAE,gCAAgC;QACtC,KAAK,EAAE,gCAAgC;QACvC,WAAW,EACV,8SAA8S;QAC/S,UAAU,EAAE,eAAqC;QACjD,YAAY,EAAE,4BAAkD;QAChE,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC1C,WAAW,EAAE,QAAQ;QACrB,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM;YACxC,MAAM,OAAO,GAAG,MAAM,IAAI,CACzB,MAAM,EACN,MAAM,EACN,mBAAmB,EACnB,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE;gBACxC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;gBAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,iBAAiB,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;YAC3E,CAAC,EACD,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,cAAc,CAAC,CACvE,CAAC;YACF,OAAO,UAAU,CAAC,gCAAgC,EAAE,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAChG,CAAC;KACD,CAAC;IAEF,MAAM,YAAY,GAA4C;QAC7D,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EAAE,qFAAqF;QAClG,UAAU,EAAE,eAAqC;QACjD,YAAY,EAAE,kBAAwC;QACtD,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC1C,WAAW,EAAE,QAAQ;QACrB,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM;YACxC,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YACpD,MAAM,GAAG,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC1C,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;YAC5D,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;YACvF,0EAA0E;YAC1E,MAAM,MAAM,GAAmB,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;gBACpE,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5F,EAAE,IAAI,EAAE,UAAmB,EAAE,QAAQ,EAAE,QAAQ,EAAE;aACjD,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC5D,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,eAAe,CACxD,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC,CACxF,CAAC;YACF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YAC7E,OAAO;gBACN,OAAO;gBACP,OAAO,EAAE;oBACR,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,IAAI,EAAE,sBAAsB;oBAC5B,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC7C;gBACD,iBAAiB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAA0B;aACjF,CAAC;QACH,CAAC;KACD,CAAC;IAEF,OAAO,CAAC,aAAa,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;AACrD,CAAC","sourcesContent":["/**\n * MCP resources, through the tools Codex and opencode use: `list_mcp_resources`,\n * `list_mcp_resource_templates`, and `read_mcp_resource`. They take a `server` argument and cover\n * every connected server with resources, so models trained on those tools use them unchanged.\n *\n * Listings are JSON, as in Codex: `{ server?, resources: [{ server, ...resource }], nextCursor? }`.\n * With a `server`, one page is listed and `cursor` continues it; without, every page of every server.\n * MCP App resources (`ui://` URIs and `profile=mcp-app` HTML) are left out, since they are user\n * interfaces for hosts that render them, and so are icons. Read resources become text and images for\n * the model; binary resources are saved to temp files. Scripts get the JSON payloads.\n */\n\nimport type { ImageContent, JsonValue, TextContent } from \"@earendil-works/pi-ai\";\nimport type {\n\tContentBlock,\n\tListResourcesResult,\n\tListResourceTemplatesResult,\n\tMcpRequestOptions,\n\tReadResourceResult,\n\tResource,\n\tResourceTemplate,\n} from \"@earendil-works/pi-mcp\";\nimport type { TSchema } from \"typebox\";\nimport type { ToolAnnotations, ToolDefinition } from \"../../core/extensions/types.ts\";\nimport type { McpExposure } from \"./config.ts\";\nimport {\n\tlimitMcpContent,\n\ttype McpToolDetails,\n\tREAD_MCP_RESOURCE_TOOL,\n\ttoModelContent,\n\ttoToolExposure,\n} from \"./tools.ts\";\n\nexport const LIST_MCP_RESOURCES_TOOL = \"list_mcp_resources\";\nexport const LIST_MCP_RESOURCE_TEMPLATES_TOOL = \"list_mcp_resource_templates\";\nexport { READ_MCP_RESOURCE_TOOL };\n\n/** A connected server that offers resources. */\nexport interface McpResourceServer {\n\tname: string;\n\ttimeoutMs: number;\n\tresourcesPage(cursor: string | undefined, options: McpRequestOptions): Promise<ListResourcesResult>;\n\tresourceTemplatesPage(cursor: string | undefined, options: McpRequestOptions): Promise<ListResourceTemplatesResult>;\n\tallResources(options: McpRequestOptions): Promise<Resource[]>;\n\tallResourceTemplates(options: McpRequestOptions): Promise<ResourceTemplate[]>;\n\treadResource(uri: string, options: McpRequestOptions): Promise<ReadResourceResult>;\n}\n\n/** MCP App user interfaces, which only hosts that render them can use. */\nexport function isMcpAppResource(item: { uri?: string; uriTemplate?: string; mimeType?: string }): boolean {\n\tconst uri = item.uri ?? item.uriTemplate ?? \"\";\n\treturn uri.startsWith(\"ui://\") || /;\\s*profile\\s*=\\s*\"?mcp-app\"?/i.test(item.mimeType ?? \"\");\n}\n\n/** A listed resource or template without `_meta` and icons, tagged with its server. */\nfunction listed<T extends { _meta?: unknown }>(server: string, item: T): Record<string, unknown> {\n\tconst { _meta, icons: _icons, ...rest } = item as T & { icons?: unknown };\n\treturn { server, ...rest };\n}\n\nconst stringProperty = (description: string) => ({ type: \"string\", description });\nconst SERVER_FILTER = stringProperty(\"MCP server name. Omit to list every server with resources.\");\nconst CURSOR = stringProperty(\"Opaque cursor from a previous call with the same server; omit for the first page.\");\nconst LIST_PARAMETERS = {\n\ttype: \"object\",\n\tproperties: { server: SERVER_FILTER, cursor: CURSOR },\n\tadditionalProperties: false,\n};\nconst READ_PARAMETERS = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: stringProperty(\n\t\t\t\"MCP server name exactly as configured. Must match the 'server' field returned by list_mcp_resources.\",\n\t\t),\n\t\turi: stringProperty(\"Resource URI to read. Must be one of the URIs returned by list_mcp_resources.\"),\n\t},\n\trequired: [\"server\", \"uri\"],\n\tadditionalProperties: false,\n};\n\nconst optionalString = { type: \"string\" };\nconst LISTING_ERRORS = {\n\ttype: \"array\",\n\tdescription: \"Servers that could not be listed\",\n\titems: {\n\t\ttype: \"object\",\n\t\tproperties: { server: { type: \"string\" }, error: { type: \"string\" } },\n\t\trequired: [\"server\", \"error\"],\n\t},\n};\nconst LIST_OUTPUT_SCHEMA = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: optionalString,\n\t\tresources: {\n\t\t\ttype: \"array\",\n\t\t\titems: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tserver: { type: \"string\" },\n\t\t\t\t\turi: { type: \"string\" },\n\t\t\t\t\tname: { type: \"string\" },\n\t\t\t\t\ttitle: optionalString,\n\t\t\t\t\tdescription: optionalString,\n\t\t\t\t\tmimeType: optionalString,\n\t\t\t\t\tsize: { type: \"number\" },\n\t\t\t\t},\n\t\t\t\trequired: [\"server\", \"uri\", \"name\"],\n\t\t\t},\n\t\t},\n\t\tnextCursor: optionalString,\n\t\terrors: LISTING_ERRORS,\n\t},\n\trequired: [\"resources\"],\n};\nconst LIST_TEMPLATES_OUTPUT_SCHEMA = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: optionalString,\n\t\tresourceTemplates: {\n\t\t\ttype: \"array\",\n\t\t\titems: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tserver: { type: \"string\" },\n\t\t\t\t\turiTemplate: { type: \"string\", description: \"RFC 6570 URI template\" },\n\t\t\t\t\tname: { type: \"string\" },\n\t\t\t\t\ttitle: optionalString,\n\t\t\t\t\tdescription: optionalString,\n\t\t\t\t\tmimeType: optionalString,\n\t\t\t\t},\n\t\t\t\trequired: [\"server\", \"uriTemplate\", \"name\"],\n\t\t\t},\n\t\t},\n\t\tnextCursor: optionalString,\n\t\terrors: LISTING_ERRORS,\n\t},\n\trequired: [\"resourceTemplates\"],\n};\nconst READ_OUTPUT_SCHEMA = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: { type: \"string\" },\n\t\turi: { type: \"string\" },\n\t\tcontents: {\n\t\t\ttype: \"array\",\n\t\t\titems: {\n\t\t\t\tanyOf: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\tproperties: { uri: { type: \"string\" }, mimeType: optionalString, text: { type: \"string\" } },\n\t\t\t\t\t\trequired: [\"uri\", \"text\"],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\turi: { type: \"string\" },\n\t\t\t\t\t\t\tmimeType: optionalString,\n\t\t\t\t\t\t\tblob: { type: \"string\", description: \"base64\" },\n\t\t\t\t\t\t},\n\t\t\t\t\t\trequired: [\"uri\", \"blob\"],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t},\n\trequired: [\"server\", \"uri\", \"contents\"],\n};\n\nfunction stringArgument(params: unknown, key: string): string | undefined {\n\tconst value = (params as Record<string, unknown> | undefined)?.[key];\n\tif (value === undefined || value === null) return undefined;\n\tif (typeof value !== \"string\") throw new Error(`${key} must be a string`);\n\treturn value.trim() || undefined;\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nasync function jsonResult(\n\ttool: string,\n\tserver: string | undefined,\n\tpayload: Record<string, unknown>,\n): Promise<{ content: (TextContent | ImageContent)[]; details: McpToolDetails; structuredContent: JsonValue }> {\n\tconst { content, fullOutputPath } = await limitMcpContent([{ type: \"text\", text: JSON.stringify(payload) }]);\n\treturn {\n\t\tcontent,\n\t\tdetails: { server: server ?? \"\", tool, ...(fullOutputPath ? { fullOutputPath } : {}) },\n\t\tstructuredContent: payload as unknown as JsonValue,\n\t};\n}\n\n/**\n * The three resource tools. `servers` returns the servers whose resources they reach, at call time.\n */\nexport function createMcpResourceToolDefinitions(options: {\n\texposure: McpExposure;\n\tservers: () => readonly McpResourceServer[];\n}): ToolDefinition<TSchema, McpToolDetails>[] {\n\tconst readOnly: ToolAnnotations = { readOnlyHint: true };\n\n\tconst findServer = (name: string): McpResourceServer => {\n\t\tconst servers = options.servers();\n\t\tconst server = servers.find((candidate) => candidate.name === name);\n\t\tif (server) return server;\n\t\tconst available = servers.map((candidate) => candidate.name).join(\", \");\n\t\tthrow new Error(\n\t\t\t`MCP server \"${name}\" has no resources${available ? `. Servers with resources: ${available}` : \"\"}`,\n\t\t);\n\t};\n\n\t/** One page of one server, or every page of every server. */\n\tconst list = async <T extends { _meta?: unknown }>(\n\t\tparams: unknown,\n\t\tsignal: AbortSignal | undefined,\n\t\tkey: \"resources\" | \"resourceTemplates\",\n\t\tpage: (\n\t\t\tserver: McpResourceServer,\n\t\t\tcursor: string | undefined,\n\t\t\toptions: McpRequestOptions,\n\t\t) => Promise<{\n\t\t\titems: T[];\n\t\t\tnextCursor?: string;\n\t\t}>,\n\t\tall: (server: McpResourceServer, options: McpRequestOptions) => Promise<T[]>,\n\t): Promise<Record<string, unknown>> => {\n\t\tconst serverName = stringArgument(params, \"server\");\n\t\tconst cursor = stringArgument(params, \"cursor\");\n\t\tconst visible = (item: T) => !isMcpAppResource(item as { uri?: string; uriTemplate?: string; mimeType?: string });\n\t\tif (serverName) {\n\t\t\tconst server = findServer(serverName);\n\t\t\tconst result = await page(server, cursor, { signal, timeoutMs: server.timeoutMs });\n\t\t\treturn {\n\t\t\t\tserver: server.name,\n\t\t\t\t[key]: result.items.filter(visible).map((item) => listed(server.name, item)),\n\t\t\t\t...(result.nextCursor === undefined ? {} : { nextCursor: result.nextCursor }),\n\t\t\t};\n\t\t}\n\t\tif (cursor) throw new Error(\"cursor can only be used when a server is specified\");\n\t\tconst servers = [...options.servers()].sort((a, b) => a.name.localeCompare(b.name));\n\t\tconst results = await Promise.allSettled(\n\t\t\tservers.map((server) => all(server, { signal, timeoutMs: server.timeoutMs })),\n\t\t);\n\t\tconst items: Record<string, unknown>[] = [];\n\t\tconst errors: { server: string; error: string }[] = [];\n\t\tresults.forEach((result, index) => {\n\t\t\tconst server = servers[index].name;\n\t\t\tif (result.status === \"fulfilled\")\n\t\t\t\titems.push(...result.value.filter(visible).map((item) => listed(server, item)));\n\t\t\telse errors.push({ server, error: errorMessage(result.reason) });\n\t\t});\n\t\treturn { [key]: items, ...(errors.length > 0 ? { errors } : {}) };\n\t};\n\n\tconst listResources: ToolDefinition<TSchema, McpToolDetails> = {\n\t\tname: LIST_MCP_RESOURCES_TOOL,\n\t\tlabel: LIST_MCP_RESOURCES_TOOL,\n\t\tdescription:\n\t\t\t\"Lists resources provided by MCP servers. Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information. Prefer resources over web search when possible.\",\n\t\tparameters: LIST_PARAMETERS as unknown as TSchema,\n\t\toutputSchema: LIST_OUTPUT_SCHEMA as unknown as TSchema,\n\t\texposure: toToolExposure(options.exposure),\n\t\tannotations: readOnly,\n\t\tasync execute(_toolCallId, params, signal) {\n\t\t\tconst payload = await list(\n\t\t\t\tparams,\n\t\t\t\tsignal,\n\t\t\t\t\"resources\",\n\t\t\t\tasync (server, cursor, requestOptions) => {\n\t\t\t\t\tconst result = await server.resourcesPage(cursor, requestOptions);\n\t\t\t\t\treturn { items: result.resources, nextCursor: result.nextCursor };\n\t\t\t\t},\n\t\t\t\t(server, requestOptions) => server.allResources(requestOptions),\n\t\t\t);\n\t\t\treturn jsonResult(LIST_MCP_RESOURCES_TOOL, stringArgument(params, \"server\"), payload);\n\t\t},\n\t};\n\n\tconst listTemplates: ToolDefinition<TSchema, McpToolDetails> = {\n\t\tname: LIST_MCP_RESOURCE_TEMPLATES_TOOL,\n\t\tlabel: LIST_MCP_RESOURCE_TEMPLATES_TOOL,\n\t\tdescription:\n\t\t\t\"Lists resource templates provided by MCP servers. Parameterized resource templates allow servers to share data that takes parameters and provides context to language models, such as files, database schemas, or application-specific information. Prefer resource templates over web search when possible.\",\n\t\tparameters: LIST_PARAMETERS as unknown as TSchema,\n\t\toutputSchema: LIST_TEMPLATES_OUTPUT_SCHEMA as unknown as TSchema,\n\t\texposure: toToolExposure(options.exposure),\n\t\tannotations: readOnly,\n\t\tasync execute(_toolCallId, params, signal) {\n\t\t\tconst payload = await list(\n\t\t\t\tparams,\n\t\t\t\tsignal,\n\t\t\t\t\"resourceTemplates\",\n\t\t\t\tasync (server, cursor, requestOptions) => {\n\t\t\t\t\tconst result = await server.resourceTemplatesPage(cursor, requestOptions);\n\t\t\t\t\treturn { items: result.resourceTemplates, nextCursor: result.nextCursor };\n\t\t\t\t},\n\t\t\t\t(server, requestOptions) => server.allResourceTemplates(requestOptions),\n\t\t\t);\n\t\t\treturn jsonResult(LIST_MCP_RESOURCE_TEMPLATES_TOOL, stringArgument(params, \"server\"), payload);\n\t\t},\n\t};\n\n\tconst readResource: ToolDefinition<TSchema, McpToolDetails> = {\n\t\tname: READ_MCP_RESOURCE_TOOL,\n\t\tlabel: READ_MCP_RESOURCE_TOOL,\n\t\tdescription: \"Read a specific resource from an MCP server given the server name and resource URI.\",\n\t\tparameters: READ_PARAMETERS as unknown as TSchema,\n\t\toutputSchema: READ_OUTPUT_SCHEMA as unknown as TSchema,\n\t\texposure: toToolExposure(options.exposure),\n\t\tannotations: readOnly,\n\t\tasync execute(_toolCallId, params, signal) {\n\t\t\tconst serverName = stringArgument(params, \"server\");\n\t\t\tconst uri = stringArgument(params, \"uri\");\n\t\t\tif (!serverName) throw new Error(\"server must be provided\");\n\t\t\tif (!uri) throw new Error(\"uri must be provided\");\n\t\t\tconst server = findServer(serverName);\n\t\t\tconst result = await server.readResource(uri, { signal, timeoutMs: server.timeoutMs });\n\t\t\t// Several contents (for example a directory) are labeled with their URIs.\n\t\t\tconst blocks: ContentBlock[] = result.contents.flatMap((contents) => [\n\t\t\t\t...(result.contents.length > 1 ? [{ type: \"text\" as const, text: `${contents.uri}:` }] : []),\n\t\t\t\t{ type: \"resource\" as const, resource: contents },\n\t\t\t]);\n\t\t\tconst converted = await toModelContent(server.name, blocks);\n\t\t\tconst { content, fullOutputPath } = await limitMcpContent(\n\t\t\t\tconverted.length > 0 ? converted : [{ type: \"text\", text: `Resource ${uri} is empty.` }],\n\t\t\t);\n\t\t\tconst contents = result.contents.map(({ _meta: _ignored, ...rest }) => rest);\n\t\t\treturn {\n\t\t\t\tcontent,\n\t\t\t\tdetails: {\n\t\t\t\t\tserver: server.name,\n\t\t\t\t\ttool: READ_MCP_RESOURCE_TOOL,\n\t\t\t\t\t...(fullOutputPath ? { fullOutputPath } : {}),\n\t\t\t\t},\n\t\t\t\tstructuredContent: { server: server.name, uri, contents } as unknown as JsonValue,\n\t\t\t};\n\t\t},\n\t};\n\n\treturn [listResources, listTemplates, readResource];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../../src/extensions/mcp/resources.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP resources, through the tools Codex and opencode use: `list_mcp_resources`,\n * `list_mcp_resource_templates`, and `read_mcp_resource`. They take a `server` argument and cover\n * every connected server with resources, so models trained on those tools use them unchanged.\n *\n * Listings are JSON, as in Codex: `{ server?, resources: [{ server, ...resource }], nextCursor? }`.\n * With a `server`, one page is listed and `cursor` continues it; without, every page of every server.\n * MCP App resources (`ui://` URIs and `profile=mcp-app` HTML) are left out, since they are user\n * interfaces for hosts that render them, and so are icons. Read resources become text and images for\n * the model; binary resources are saved to temp files. Scripts get the JSON payloads.\n */\n\nimport type { ImageContent, JsonValue, TextContent } from \"@earendil-works/pi-ai\";\nimport type {\n\tContentBlock,\n\tListResourcesResult,\n\tListResourceTemplatesResult,\n\tMcpRequestOptions,\n\tReadResourceResult,\n\tResource,\n\tResourceTemplate,\n} from \"@earendil-works/pi-mcp\";\nimport type { TSchema } from \"typebox\";\nimport type { ToolAnnotations, ToolDefinition } from \"../../core/extensions/types.ts\";\nimport {\n\tLIST_MCP_RESOURCE_TEMPLATES_TOOL,\n\tLIST_MCP_RESOURCES_TOOL,\n\tREAD_MCP_RESOURCE_TOOL,\n} from \"../../core/mcp-servers.ts\";\nimport type { McpExposure } from \"./config.ts\";\nimport { limitMcpContent, type McpToolDetails, toModelContent, toToolExposure } from \"./tools.ts\";\n\nexport { LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL };\n\n/** A connected server that offers resources. */\nexport interface McpResourceServer {\n\tname: string;\n\ttimeoutMs: number;\n\tresourcesPage(cursor: string | undefined, options: McpRequestOptions): Promise<ListResourcesResult>;\n\tresourceTemplatesPage(cursor: string | undefined, options: McpRequestOptions): Promise<ListResourceTemplatesResult>;\n\tallResources(options: McpRequestOptions): Promise<Resource[]>;\n\tallResourceTemplates(options: McpRequestOptions): Promise<ResourceTemplate[]>;\n\treadResource(uri: string, options: McpRequestOptions): Promise<ReadResourceResult>;\n}\n\n/** MCP App user interfaces, which only hosts that render them can use. */\nexport function isMcpAppResource(item: { uri?: string; uriTemplate?: string; mimeType?: string }): boolean {\n\tconst uri = item.uri ?? item.uriTemplate ?? \"\";\n\treturn uri.startsWith(\"ui://\") || /;\\s*profile\\s*=\\s*\"?mcp-app\"?/i.test(item.mimeType ?? \"\");\n}\n\n/** A listed resource or template without `_meta` and icons, tagged with its server. */\nfunction listed<T extends { _meta?: unknown }>(server: string, item: T): Record<string, unknown> {\n\tconst { _meta, icons: _icons, ...rest } = item as T & { icons?: unknown };\n\treturn { server, ...rest };\n}\n\nconst stringProperty = (description: string) => ({ type: \"string\", description });\nconst SERVER_FILTER = stringProperty(\"MCP server name. Omit to list every server with resources.\");\nconst CURSOR = stringProperty(\"Opaque cursor from a previous call with the same server; omit for the first page.\");\nconst LIST_PARAMETERS = {\n\ttype: \"object\",\n\tproperties: { server: SERVER_FILTER, cursor: CURSOR },\n\tadditionalProperties: false,\n};\nconst READ_PARAMETERS = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: stringProperty(\n\t\t\t\"MCP server name exactly as configured. Must match the 'server' field returned by list_mcp_resources.\",\n\t\t),\n\t\turi: stringProperty(\"Resource URI to read. Must be one of the URIs returned by list_mcp_resources.\"),\n\t},\n\trequired: [\"server\", \"uri\"],\n\tadditionalProperties: false,\n};\n\nconst optionalString = { type: \"string\" };\nconst LISTING_ERRORS = {\n\ttype: \"array\",\n\tdescription: \"Servers that could not be listed\",\n\titems: {\n\t\ttype: \"object\",\n\t\tproperties: { server: { type: \"string\" }, error: { type: \"string\" } },\n\t\trequired: [\"server\", \"error\"],\n\t},\n};\nconst LIST_OUTPUT_SCHEMA = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: optionalString,\n\t\tresources: {\n\t\t\ttype: \"array\",\n\t\t\titems: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tserver: { type: \"string\" },\n\t\t\t\t\turi: { type: \"string\" },\n\t\t\t\t\tname: { type: \"string\" },\n\t\t\t\t\ttitle: optionalString,\n\t\t\t\t\tdescription: optionalString,\n\t\t\t\t\tmimeType: optionalString,\n\t\t\t\t\tsize: { type: \"number\" },\n\t\t\t\t},\n\t\t\t\trequired: [\"server\", \"uri\", \"name\"],\n\t\t\t},\n\t\t},\n\t\tnextCursor: optionalString,\n\t\terrors: LISTING_ERRORS,\n\t},\n\trequired: [\"resources\"],\n};\nconst LIST_TEMPLATES_OUTPUT_SCHEMA = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: optionalString,\n\t\tresourceTemplates: {\n\t\t\ttype: \"array\",\n\t\t\titems: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tserver: { type: \"string\" },\n\t\t\t\t\turiTemplate: { type: \"string\", description: \"RFC 6570 URI template\" },\n\t\t\t\t\tname: { type: \"string\" },\n\t\t\t\t\ttitle: optionalString,\n\t\t\t\t\tdescription: optionalString,\n\t\t\t\t\tmimeType: optionalString,\n\t\t\t\t},\n\t\t\t\trequired: [\"server\", \"uriTemplate\", \"name\"],\n\t\t\t},\n\t\t},\n\t\tnextCursor: optionalString,\n\t\terrors: LISTING_ERRORS,\n\t},\n\trequired: [\"resourceTemplates\"],\n};\nconst READ_OUTPUT_SCHEMA = {\n\ttype: \"object\",\n\tproperties: {\n\t\tserver: { type: \"string\" },\n\t\turi: { type: \"string\" },\n\t\tcontents: {\n\t\t\ttype: \"array\",\n\t\t\titems: {\n\t\t\t\tanyOf: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\tproperties: { uri: { type: \"string\" }, mimeType: optionalString, text: { type: \"string\" } },\n\t\t\t\t\t\trequired: [\"uri\", \"text\"],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\turi: { type: \"string\" },\n\t\t\t\t\t\t\tmimeType: optionalString,\n\t\t\t\t\t\t\tblob: { type: \"string\", description: \"base64\" },\n\t\t\t\t\t\t},\n\t\t\t\t\t\trequired: [\"uri\", \"blob\"],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t},\n\trequired: [\"server\", \"uri\", \"contents\"],\n};\n\nfunction stringArgument(params: unknown, key: string): string | undefined {\n\tconst value = (params as Record<string, unknown> | undefined)?.[key];\n\tif (value === undefined || value === null) return undefined;\n\tif (typeof value !== \"string\") throw new Error(`${key} must be a string`);\n\treturn value.trim() || undefined;\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nasync function jsonResult(\n\ttool: string,\n\tserver: string | undefined,\n\tpayload: Record<string, unknown>,\n): Promise<{ content: (TextContent | ImageContent)[]; details: McpToolDetails; structuredContent: JsonValue }> {\n\tconst { content, fullOutputPath } = await limitMcpContent([{ type: \"text\", text: JSON.stringify(payload) }]);\n\treturn {\n\t\tcontent,\n\t\tdetails: { server: server ?? \"\", tool, ...(fullOutputPath ? { fullOutputPath } : {}) },\n\t\tstructuredContent: payload as unknown as JsonValue,\n\t};\n}\n\n/**\n * The three resource tools. `servers` returns the servers whose resources they reach, at call time.\n */\nexport function createMcpResourceToolDefinitions(options: {\n\texposure: McpExposure;\n\tservers: () => readonly McpResourceServer[];\n}): ToolDefinition<TSchema, McpToolDetails>[] {\n\tconst readOnly: ToolAnnotations = { readOnlyHint: true };\n\n\tconst findServer = (name: string): McpResourceServer => {\n\t\tconst servers = options.servers();\n\t\tconst server = servers.find((candidate) => candidate.name === name);\n\t\tif (server) return server;\n\t\tconst available = servers.map((candidate) => candidate.name).join(\", \");\n\t\tthrow new Error(\n\t\t\t`MCP server \"${name}\" has no resources${available ? `. Servers with resources: ${available}` : \"\"}`,\n\t\t);\n\t};\n\n\t/** One page of one server, or every page of every server. */\n\tconst list = async <T extends { _meta?: unknown }>(\n\t\tparams: unknown,\n\t\tsignal: AbortSignal | undefined,\n\t\tkey: \"resources\" | \"resourceTemplates\",\n\t\tpage: (\n\t\t\tserver: McpResourceServer,\n\t\t\tcursor: string | undefined,\n\t\t\toptions: McpRequestOptions,\n\t\t) => Promise<{\n\t\t\titems: T[];\n\t\t\tnextCursor?: string;\n\t\t}>,\n\t\tall: (server: McpResourceServer, options: McpRequestOptions) => Promise<T[]>,\n\t): Promise<Record<string, unknown>> => {\n\t\tconst serverName = stringArgument(params, \"server\");\n\t\tconst cursor = stringArgument(params, \"cursor\");\n\t\tconst visible = (item: T) => !isMcpAppResource(item as { uri?: string; uriTemplate?: string; mimeType?: string });\n\t\tif (serverName) {\n\t\t\tconst server = findServer(serverName);\n\t\t\tconst result = await page(server, cursor, { signal, timeoutMs: server.timeoutMs });\n\t\t\treturn {\n\t\t\t\tserver: server.name,\n\t\t\t\t[key]: result.items.filter(visible).map((item) => listed(server.name, item)),\n\t\t\t\t...(result.nextCursor === undefined ? {} : { nextCursor: result.nextCursor }),\n\t\t\t};\n\t\t}\n\t\tif (cursor) throw new Error(\"cursor can only be used when a server is specified\");\n\t\tconst servers = [...options.servers()].sort((a, b) => a.name.localeCompare(b.name));\n\t\tconst results = await Promise.allSettled(\n\t\t\tservers.map((server) => all(server, { signal, timeoutMs: server.timeoutMs })),\n\t\t);\n\t\tconst items: Record<string, unknown>[] = [];\n\t\tconst errors: { server: string; error: string }[] = [];\n\t\tresults.forEach((result, index) => {\n\t\t\tconst server = servers[index].name;\n\t\t\tif (result.status === \"fulfilled\")\n\t\t\t\titems.push(...result.value.filter(visible).map((item) => listed(server, item)));\n\t\t\telse errors.push({ server, error: errorMessage(result.reason) });\n\t\t});\n\t\treturn { [key]: items, ...(errors.length > 0 ? { errors } : {}) };\n\t};\n\n\tconst listResources: ToolDefinition<TSchema, McpToolDetails> = {\n\t\tname: LIST_MCP_RESOURCES_TOOL,\n\t\tlabel: LIST_MCP_RESOURCES_TOOL,\n\t\tdescription:\n\t\t\t\"Lists resources provided by MCP servers. Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information. Prefer resources over web search when possible.\",\n\t\tparameters: LIST_PARAMETERS as unknown as TSchema,\n\t\toutputSchema: LIST_OUTPUT_SCHEMA as unknown as TSchema,\n\t\texposure: toToolExposure(options.exposure),\n\t\tannotations: readOnly,\n\t\tasync execute(_toolCallId, params, signal) {\n\t\t\tconst payload = await list(\n\t\t\t\tparams,\n\t\t\t\tsignal,\n\t\t\t\t\"resources\",\n\t\t\t\tasync (server, cursor, requestOptions) => {\n\t\t\t\t\tconst result = await server.resourcesPage(cursor, requestOptions);\n\t\t\t\t\treturn { items: result.resources, nextCursor: result.nextCursor };\n\t\t\t\t},\n\t\t\t\t(server, requestOptions) => server.allResources(requestOptions),\n\t\t\t);\n\t\t\treturn jsonResult(LIST_MCP_RESOURCES_TOOL, stringArgument(params, \"server\"), payload);\n\t\t},\n\t};\n\n\tconst listTemplates: ToolDefinition<TSchema, McpToolDetails> = {\n\t\tname: LIST_MCP_RESOURCE_TEMPLATES_TOOL,\n\t\tlabel: LIST_MCP_RESOURCE_TEMPLATES_TOOL,\n\t\tdescription:\n\t\t\t\"Lists resource templates provided by MCP servers. Parameterized resource templates allow servers to share data that takes parameters and provides context to language models, such as files, database schemas, or application-specific information. Prefer resource templates over web search when possible.\",\n\t\tparameters: LIST_PARAMETERS as unknown as TSchema,\n\t\toutputSchema: LIST_TEMPLATES_OUTPUT_SCHEMA as unknown as TSchema,\n\t\texposure: toToolExposure(options.exposure),\n\t\tannotations: readOnly,\n\t\tasync execute(_toolCallId, params, signal) {\n\t\t\tconst payload = await list(\n\t\t\t\tparams,\n\t\t\t\tsignal,\n\t\t\t\t\"resourceTemplates\",\n\t\t\t\tasync (server, cursor, requestOptions) => {\n\t\t\t\t\tconst result = await server.resourceTemplatesPage(cursor, requestOptions);\n\t\t\t\t\treturn { items: result.resourceTemplates, nextCursor: result.nextCursor };\n\t\t\t\t},\n\t\t\t\t(server, requestOptions) => server.allResourceTemplates(requestOptions),\n\t\t\t);\n\t\t\treturn jsonResult(LIST_MCP_RESOURCE_TEMPLATES_TOOL, stringArgument(params, \"server\"), payload);\n\t\t},\n\t};\n\n\tconst readResource: ToolDefinition<TSchema, McpToolDetails> = {\n\t\tname: READ_MCP_RESOURCE_TOOL,\n\t\tlabel: READ_MCP_RESOURCE_TOOL,\n\t\tdescription: \"Read a specific resource from an MCP server given the server name and resource URI.\",\n\t\tparameters: READ_PARAMETERS as unknown as TSchema,\n\t\toutputSchema: READ_OUTPUT_SCHEMA as unknown as TSchema,\n\t\texposure: toToolExposure(options.exposure),\n\t\tannotations: readOnly,\n\t\tasync execute(_toolCallId, params, signal) {\n\t\t\tconst serverName = stringArgument(params, \"server\");\n\t\t\tconst uri = stringArgument(params, \"uri\");\n\t\t\tif (!serverName) throw new Error(\"server must be provided\");\n\t\t\tif (!uri) throw new Error(\"uri must be provided\");\n\t\t\tconst server = findServer(serverName);\n\t\t\tconst result = await server.readResource(uri, { signal, timeoutMs: server.timeoutMs });\n\t\t\t// Several contents (for example a directory) are labeled with their URIs.\n\t\t\tconst blocks: ContentBlock[] = result.contents.flatMap((contents) => [\n\t\t\t\t...(result.contents.length > 1 ? [{ type: \"text\" as const, text: `${contents.uri}:` }] : []),\n\t\t\t\t{ type: \"resource\" as const, resource: contents },\n\t\t\t]);\n\t\t\tconst converted = await toModelContent(server.name, blocks);\n\t\t\tconst { content, fullOutputPath } = await limitMcpContent(\n\t\t\t\tconverted.length > 0 ? converted : [{ type: \"text\", text: `Resource ${uri} is empty.` }],\n\t\t\t);\n\t\t\tconst contents = result.contents.map(({ _meta: _ignored, ...rest }) => rest);\n\t\t\treturn {\n\t\t\t\tcontent,\n\t\t\t\tdetails: {\n\t\t\t\t\tserver: server.name,\n\t\t\t\t\ttool: READ_MCP_RESOURCE_TOOL,\n\t\t\t\t\t...(fullOutputPath ? { fullOutputPath } : {}),\n\t\t\t\t},\n\t\t\t\tstructuredContent: { server: server.name, uri, contents } as unknown as JsonValue,\n\t\t\t};\n\t\t},\n\t};\n\n\treturn [listResources, listTemplates, readResource];\n}\n"]}
|
|
@@ -38,7 +38,8 @@ export declare class McpServerConnection implements McpToolCaller, McpResourceSe
|
|
|
38
38
|
challenge: OAuthChallenge | undefined;
|
|
39
39
|
private client;
|
|
40
40
|
private opening;
|
|
41
|
-
|
|
41
|
+
/** Aborted by `close()`; cancels a connect in progress, including the wait between retries. */
|
|
42
|
+
private readonly shutdown;
|
|
42
43
|
/** Stderr of the last stdio server that failed to connect. */
|
|
43
44
|
private stderrTail;
|
|
44
45
|
private readonly cwd;
|
|
@@ -61,6 +62,7 @@ export declare class McpServerConnection implements McpToolCaller, McpResourceSe
|
|
|
61
62
|
log?: McpServerLog;
|
|
62
63
|
});
|
|
63
64
|
get name(): string;
|
|
65
|
+
private get closed();
|
|
64
66
|
get timeoutMs(): number;
|
|
65
67
|
/** Server URL when the server authenticates with OAuth. */
|
|
66
68
|
get oauthUrl(): string | undefined;
|
|
@@ -93,6 +95,7 @@ export declare class McpServerConnection implements McpToolCaller, McpResourceSe
|
|
|
93
95
|
private handleClientClose;
|
|
94
96
|
private refreshTools;
|
|
95
97
|
private refreshResources;
|
|
98
|
+
/** Resolves once no transport of this server is open, including one that was still connecting. */
|
|
96
99
|
close(): Promise<void>;
|
|
97
100
|
}
|
|
98
101
|
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/runtime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { basename, join, resolve } from "node:path";
|
|
8
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
8
9
|
import { pathToFileURL } from "node:url";
|
|
9
10
|
import { JSON_RPC_ERROR_CODES, McpAuthRequiredError, McpClient, McpError, McpHttpError, McpSessionExpiredError, StdioTransport, StreamableHttpTransport, } from "@earendil-works/pi-mcp";
|
|
10
11
|
import { McpOAuthAuthorizationRequiredError } from "@earendil-works/pi-mcp/oauth";
|
|
@@ -109,7 +110,8 @@ export class McpServerConnection {
|
|
|
109
110
|
this.hasResources = false;
|
|
110
111
|
this.resources = [];
|
|
111
112
|
this.resourceTemplates = [];
|
|
112
|
-
|
|
113
|
+
/** Aborted by `close()`; cancels a connect in progress, including the wait between retries. */
|
|
114
|
+
this.shutdown = new AbortController();
|
|
113
115
|
this.entry = options.entry;
|
|
114
116
|
this.cwd = options.cwd;
|
|
115
117
|
this.createTransport = options.createTransport;
|
|
@@ -135,6 +137,9 @@ export class McpServerConnection {
|
|
|
135
137
|
get name() {
|
|
136
138
|
return this.entry.name;
|
|
137
139
|
}
|
|
140
|
+
get closed() {
|
|
141
|
+
return this.shutdown.signal.aborted;
|
|
142
|
+
}
|
|
138
143
|
get timeoutMs() {
|
|
139
144
|
return (this.entry.config.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000;
|
|
140
145
|
}
|
|
@@ -265,7 +270,7 @@ export class McpServerConnection {
|
|
|
265
270
|
if (this.closed || delay === undefined || !isTransientError(error)) {
|
|
266
271
|
throw this.connectFailed(error);
|
|
267
272
|
}
|
|
268
|
-
await
|
|
273
|
+
await sleep(delay, undefined, { signal: this.shutdown.signal }).catch(() => undefined);
|
|
269
274
|
if (this.closed)
|
|
270
275
|
throw this.connectFailed(error);
|
|
271
276
|
}
|
|
@@ -281,6 +286,14 @@ export class McpServerConnection {
|
|
|
281
286
|
const log = this.log;
|
|
282
287
|
if (log)
|
|
283
288
|
client.onNotification("notifications/message", (params) => log.write(this.entry.name, params));
|
|
289
|
+
// Closing the client fails its pending initialize or discovery requests and closes the transport.
|
|
290
|
+
// Memoized: a second `client.close()` returns before the first finished closing the transport.
|
|
291
|
+
let closing;
|
|
292
|
+
const closeClient = () => {
|
|
293
|
+
closing ??= client.close().catch(() => undefined);
|
|
294
|
+
return closing;
|
|
295
|
+
};
|
|
296
|
+
this.shutdown.signal.addEventListener("abort", closeClient, { once: true });
|
|
284
297
|
let transport;
|
|
285
298
|
try {
|
|
286
299
|
transport = this.createTransport(this.entry, this.cwd, this.authProvider);
|
|
@@ -316,12 +329,15 @@ export class McpServerConnection {
|
|
|
316
329
|
return client;
|
|
317
330
|
}
|
|
318
331
|
catch (error) {
|
|
319
|
-
await
|
|
332
|
+
await closeClient();
|
|
320
333
|
if (transport instanceof StdioTransport) {
|
|
321
334
|
this.stderrTail = transport.stderr.trim().slice(-STDERR_TAIL_CHARS) || undefined;
|
|
322
335
|
}
|
|
323
336
|
throw error;
|
|
324
337
|
}
|
|
338
|
+
finally {
|
|
339
|
+
this.shutdown.signal.removeEventListener("abort", closeClient);
|
|
340
|
+
}
|
|
325
341
|
}
|
|
326
342
|
connectFailed(error) {
|
|
327
343
|
if (this.needsSignIn(error) && !this.closed) {
|
|
@@ -365,13 +381,14 @@ export class McpServerConnection {
|
|
|
365
381
|
this.onTools(this);
|
|
366
382
|
this.changed();
|
|
367
383
|
}
|
|
384
|
+
/** Resolves once no transport of this server is open, including one that was still connecting. */
|
|
368
385
|
async close() {
|
|
369
|
-
this.
|
|
386
|
+
this.shutdown.abort();
|
|
370
387
|
this.state = "closed";
|
|
371
388
|
this.changed();
|
|
372
389
|
const client = this.client;
|
|
373
390
|
this.client = undefined;
|
|
374
|
-
await client?.close().catch(() => undefined);
|
|
391
|
+
await Promise.all([client?.close().catch(() => undefined), this.opening?.catch(() => undefined)]);
|
|
375
392
|
// A refresh the server already answered may have rotated the refresh token; exiting before the
|
|
376
393
|
// new tokens are saved would lose the grant.
|
|
377
394
|
await this.authProvider?.settled();
|