@earendil-works/pi-coding-agent 0.99.2 → 1.0.1
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 +90 -0
- package/README.md +32 -7
- package/dist/bundle/chunks/{anthropic-messages-UID2PALB.js → anthropic-messages-GXHZP2HL.js} +3 -3
- package/dist/bundle/chunks/anthropic.js +2 -2
- package/dist/bundle/chunks/{azure-openai-responses-TLHNIYZK.js → azure-openai-responses-AFMVM6NP.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/chunk-2KAZSACP.js +2 -0
- package/dist/bundle/chunks/{chunk-R5J3MOBF.js → chunk-2VNGUWBO.js} +1 -1
- package/dist/bundle/chunks/chunk-5OEJBNHG.js +1203 -0
- package/dist/bundle/chunks/{chunk-7QSPBJKW.js → chunk-6FX7UEPL.js} +16 -30
- package/dist/bundle/chunks/chunk-F5ZABL6Z.js +20 -0
- package/dist/bundle/chunks/chunk-FDW3AYMM.js +2 -0
- package/dist/bundle/chunks/chunk-OYBSLV7Y.js +16 -0
- package/dist/bundle/chunks/{chunk-45SXDISG.js → chunk-PDFMCAOZ.js} +5 -5
- package/dist/bundle/chunks/{chunk-G3UZ5FWD.js → chunk-SYF2TF6W.js} +11 -60
- package/dist/bundle/chunks/chunk-WCQZGZ6I.js +82 -0
- package/dist/bundle/chunks/chunk-ZSI3BL5Q.js +4 -0
- package/dist/bundle/chunks/{cli-ALNBTL3U.js → cli-KILDDGW3.js} +4 -4
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-NPQN263E.js → cloudflare-workers-ai-system-one-X5PVWWYJ.js} +1 -1
- package/dist/bundle/chunks/codemode-worker.js +68 -8
- package/dist/bundle/chunks/easter-egg-3d-T5FFWIBR.js +2 -0
- package/dist/bundle/chunks/execute-RYVK27GT.js +388 -0
- package/dist/bundle/chunks/node-N5BSDOA5.js +16 -0
- package/dist/bundle/chunks/openai-chatgpt.js +2 -2
- package/dist/bundle/chunks/{openai-codex-responses-3GKGNXC4.js → openai-codex-responses-GK67DQQJ.js} +1 -1
- package/dist/bundle/chunks/openai-codex.js +1 -1
- package/dist/bundle/chunks/{openai-responses-S7GKCCDG.js → openai-responses-CQDDLDQF.js} +1 -1
- package/dist/bundle/chunks/openrouter.js +1 -1
- package/dist/bundle/chunks/radius.js +1 -1
- package/dist/bundle/chunks/runtime-D5I2NTIF.js +2 -0
- package/dist/bundle/chunks/virtual-modules-OO3V5ZPE.js +2 -0
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +6 -3
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +8 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +38 -9
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts +3 -3
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +3 -3
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +5 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +6 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -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 +12 -0
- package/dist/core/mcp-servers.d.ts.map +1 -1
- package/dist/core/mcp-servers.js +18 -0
- package/dist/core/mcp-servers.js.map +1 -1
- package/dist/core/model-registry.d.ts +3 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/radius.d.ts +2 -0
- package/dist/core/radius.d.ts.map +1 -1
- package/dist/core/radius.js +2 -0
- package/dist/core/radius.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -2
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/renderers/index.d.ts +2 -2
- package/dist/core/tools/renderers/index.d.ts.map +1 -1
- package/dist/core/tools/renderers/index.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +154 -47
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts +7 -7
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +55 -102
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/mcp/cli.d.ts.map +1 -1
- package/dist/extensions/mcp/cli.js +5 -2
- package/dist/extensions/mcp/cli.js.map +1 -1
- package/dist/extensions/mcp/config.d.ts +15 -3
- package/dist/extensions/mcp/config.d.ts.map +1 -1
- package/dist/extensions/mcp/config.js +46 -10
- package/dist/extensions/mcp/config.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts +4 -1
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +97 -51
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/oauth.d.ts +12 -8
- package/dist/extensions/mcp/oauth.d.ts.map +1 -1
- package/dist/extensions/mcp/oauth.js +104 -35
- package/dist/extensions/mcp/oauth.js.map +1 -1
- package/dist/extensions/mcp/runtime.d.ts.map +1 -1
- package/dist/extensions/mcp/runtime.js +3 -1
- package/dist/extensions/mcp/runtime.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts +3 -1
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +19 -13
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/extensions/mcp/ui.d.ts.map +1 -1
- package/dist/extensions/mcp/ui.js +8 -4
- package/dist/extensions/mcp/ui.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts +3 -0
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js +7 -5
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/auth-url.d.ts +14 -0
- package/dist/modes/interactive/components/auth-url.d.ts.map +1 -0
- package/dist/modes/interactive/components/auth-url.js +37 -0
- package/dist/modes/interactive/components/auth-url.js.map +1 -0
- package/dist/modes/interactive/components/easter-egg-3d.d.ts +104 -0
- package/dist/modes/interactive/components/easter-egg-3d.d.ts.map +1 -0
- package/dist/modes/interactive/components/easter-egg-3d.js +1139 -0
- package/dist/modes/interactive/components/easter-egg-3d.js.map +1 -0
- package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts +6 -0
- package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts.map +1 -0
- package/dist/modes/interactive/components/easter-egg-3d.lazy.js +23 -0
- package/dist/modes/interactive/components/easter-egg-3d.lazy.js.map +1 -0
- package/dist/modes/interactive/components/index.d.ts +0 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +0 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +2 -0
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +11 -5
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +8 -2
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +24 -21
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/pi-logo.d.ts +7 -0
- package/dist/modes/interactive/components/pi-logo.d.ts.map +1 -1
- package/dist/modes/interactive/components/pi-logo.js +13 -1
- package/dist/modes/interactive/components/pi-logo.js.map +1 -1
- package/dist/modes/interactive/components/radius-login-selector.d.ts +15 -0
- package/dist/modes/interactive/components/radius-login-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/radius-login-selector.js +85 -0
- package/dist/modes/interactive/components/radius-login-selector.js.map +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -3
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +5 -5
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +6 -18
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +22 -45
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +5 -4
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +12 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +166 -55
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/system-theme.d.ts +2 -1
- package/dist/modes/interactive/theme/system-theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/system-theme.js +16 -5
- package/dist/modes/interactive/theme/system-theme.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +12 -0
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/image-convert.d.ts +12 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +45 -6
- package/dist/utils/image-convert.js.map +1 -1
- package/docs/cli.md +8 -16
- package/docs/codemode.md +207 -0
- package/docs/docs.json +5 -1
- package/docs/environment-variables.md +2 -2
- package/docs/extensions.md +4 -0
- package/docs/index.md +1 -1
- package/docs/keybindings.md +1 -1
- package/docs/mcp.md +40 -1
- package/docs/models.md +24 -3
- package/docs/providers.md +16 -6
- package/docs/quickstart.md +16 -2
- package/docs/sdk.md +1 -1
- package/docs/settings.md +3 -3
- package/docs/usage.md +1 -1
- 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/package.json +13 -14
- package/dist/bundle/chunks/chunk-3KYEVQKX.js +0 -82
- package/dist/bundle/chunks/chunk-3NVH2HQH.js +0 -4
- package/dist/bundle/chunks/chunk-3YAHQSW6.js +0 -1438
- package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +0 -42
- package/dist/bundle/chunks/chunk-ECRK44UV.js +0 -2
- package/dist/bundle/chunks/execute-RC7W4ML3.js +0 -328
- package/dist/bundle/chunks/node-O3RIBJRH.js +0 -16
- package/dist/bundle/chunks/runtime-NJPX4ID2.js +0 -2
- package/dist/bundle/chunks/virtual-modules-5UGH4HRZ.js +0 -2
- package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
- package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
- package/dist/modes/interactive/components/daxnuts.js +0 -140
- package/dist/modes/interactive/components/daxnuts.js.map +0 -1
- package/npm-shrinkwrap.json +0 -1957
|
@@ -4,20 +4,24 @@
|
|
|
4
4
|
* Connections never start a browser flow on their own. They send the stored access token and, after
|
|
5
5
|
* a 401, try the stored refresh token. When that is not possible they fail with
|
|
6
6
|
* `McpOAuthAuthorizationRequiredError`, and the user signs in through `/mcp`, which runs
|
|
7
|
-
* the authorization code flow (PKCE, dynamic client registration)
|
|
7
|
+
* the authorization code flow (PKCE, a Client ID Metadata Document or dynamic client registration)
|
|
8
|
+
* against a loopback callback.
|
|
8
9
|
*
|
|
9
|
-
* Credentials live in `<agent-dir>/mcp-auth.json`, keyed by server URL.
|
|
10
|
+
* Credentials live in `<agent-dir>/mcp-auth.json`, keyed by server name and URL.
|
|
10
11
|
*/
|
|
11
12
|
import { createHash } from "node:crypto";
|
|
12
13
|
import { mkdirSync } from "node:fs";
|
|
13
14
|
import { join } from "node:path";
|
|
14
15
|
import { oauthErrorHtml, oauthSuccessHtml } from "@earendil-works/pi-ai/utils/oauth-page";
|
|
15
|
-
import { authorizeMcp, McpOAuthAuthorizationRequiredError, McpOAuthProvider, OAuthCallbackServer, parseWwwAuthenticate, } from "@earendil-works/pi-mcp/oauth";
|
|
16
|
+
import { authorizeMcp, McpOAuthAuthorizationRequiredError, McpOAuthProvider, OAuthCallbackServer, parseWwwAuthenticate, stepUpScope, } from "@earendil-works/pi-mcp/oauth";
|
|
16
17
|
import lockfile from "proper-lockfile";
|
|
17
18
|
import { APP_NAME, getAgentDir } from "../../config.js";
|
|
18
19
|
import { FileAuthStorageBackend } from "../../core/auth-storage.js";
|
|
20
|
+
import { mcpNamespace } from "../../core/mcp-servers.js";
|
|
19
21
|
const CALLBACK_HOST = "127.0.0.1";
|
|
20
22
|
const CALLBACK_PATH = "/callback";
|
|
23
|
+
/** Where pi.dev serves pi's Client ID Metadata Documents: `client.json` and `<callback ID>/client.json`. */
|
|
24
|
+
const CLIENT_METADATA_BASE_URL = "https://pi.dev/oauth";
|
|
21
25
|
/** Redirect URI for refreshes when none is stored. Refreshing never redirects the user. */
|
|
22
26
|
const FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
23
27
|
/** Access tokens this close to expiry are refreshed before they are sent. */
|
|
@@ -61,6 +65,17 @@ function parseStates(content) {
|
|
|
61
65
|
const parsed = JSON.parse(content);
|
|
62
66
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
63
67
|
}
|
|
68
|
+
function serializeStates(states) {
|
|
69
|
+
return `${JSON.stringify(states, null, 2)}\n`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Keys of a server's state: by name and URL, so servers sharing a URL keep separate accounts, and the
|
|
73
|
+
* legacy key by URL alone, written by older versions.
|
|
74
|
+
*/
|
|
75
|
+
function storeKeys(name, serverUrl) {
|
|
76
|
+
const legacyKey = String(new URL(serverUrl));
|
|
77
|
+
return { key: `${mcpNamespace(name)}|${legacyKey}`, legacyKey };
|
|
78
|
+
}
|
|
64
79
|
/** Per-server OAuth state (client registration, tokens, pending PKCE verifier) in `mcp-auth.json`. */
|
|
65
80
|
export class McpOAuthCredentialStore {
|
|
66
81
|
backend;
|
|
@@ -70,10 +85,18 @@ export class McpOAuthCredentialStore {
|
|
|
70
85
|
this.backend = backend ?? new FileAuthStorageBackend(join(getAgentDir(), "mcp-auth.json"));
|
|
71
86
|
this.lockDir = backend ? lockDir : getAgentDir();
|
|
72
87
|
}
|
|
73
|
-
forServer(serverUrl) {
|
|
74
|
-
const key =
|
|
88
|
+
forServer(name, serverUrl) {
|
|
89
|
+
const { key, legacyKey } = storeKeys(name, serverUrl);
|
|
75
90
|
return {
|
|
76
|
-
load
|
|
91
|
+
// The first server to load legacy state takes it over; others with the same URL sign in again.
|
|
92
|
+
load: () => this.backend.withLock((current) => {
|
|
93
|
+
const states = parseStates(current);
|
|
94
|
+
if (states[key] || !states[legacyKey])
|
|
95
|
+
return { result: states[key] };
|
|
96
|
+
states[key] = states[legacyKey];
|
|
97
|
+
delete states[legacyKey];
|
|
98
|
+
return { result: states[key], next: serializeStates(states) };
|
|
99
|
+
}),
|
|
77
100
|
save: (state) => this.write((states) => {
|
|
78
101
|
states[key] = state;
|
|
79
102
|
}),
|
|
@@ -108,39 +131,68 @@ export class McpOAuthCredentialStore {
|
|
|
108
131
|
await release().catch(() => undefined);
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
|
-
/** The stored tokens of a server, for noticing sign-ins done by another process. */
|
|
112
|
-
tokens(serverUrl) {
|
|
113
|
-
|
|
134
|
+
/** The stored tokens of a server, for noticing sign-ins done by another process. Does not take over legacy state. */
|
|
135
|
+
tokens(name, serverUrl) {
|
|
136
|
+
const { key, legacyKey } = storeKeys(name, serverUrl);
|
|
137
|
+
const states = this.backend.withLock((current) => ({ result: parseStates(current) }));
|
|
138
|
+
return (states[key] ?? states[legacyKey])?.tokens;
|
|
114
139
|
}
|
|
115
|
-
/** Returns whether credentials were stored for the server. */
|
|
116
|
-
remove(serverUrl) {
|
|
117
|
-
const key =
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
140
|
+
/** Returns whether credentials were stored for the server. Removes legacy state the server would take over. */
|
|
141
|
+
remove(name, serverUrl) {
|
|
142
|
+
const { key, legacyKey } = storeKeys(name, serverUrl);
|
|
143
|
+
return this.backend.withLock((current) => {
|
|
144
|
+
const states = parseStates(current);
|
|
145
|
+
const stored = key in states ? key : legacyKey in states ? legacyKey : undefined;
|
|
146
|
+
if (!stored)
|
|
147
|
+
return { result: false };
|
|
148
|
+
delete states[stored];
|
|
149
|
+
return { result: true, next: serializeStates(states) };
|
|
122
150
|
});
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
read() {
|
|
126
|
-
return this.backend.withLock((current) => ({ result: parseStates(current) }));
|
|
127
151
|
}
|
|
128
152
|
write(update) {
|
|
129
153
|
this.backend.withLock((current) => {
|
|
130
154
|
const states = parseStates(current);
|
|
131
155
|
update(states);
|
|
132
|
-
return { result: undefined, next:
|
|
156
|
+
return { result: undefined, next: serializeStates(states) };
|
|
133
157
|
});
|
|
134
158
|
}
|
|
135
159
|
}
|
|
136
160
|
function registeredRedirectUrls(client) {
|
|
137
161
|
return client && "redirect_uris" in client ? client.redirect_uris : [];
|
|
138
162
|
}
|
|
163
|
+
/** 12 characters identifying an MCP server URL in callback paths, computed like Codex does. */
|
|
164
|
+
function callbackId(serverUrl) {
|
|
165
|
+
const url = new URL(serverUrl);
|
|
166
|
+
url.hash = "";
|
|
167
|
+
return createHash("sha256").update(url.href).digest().subarray(0, 9).toString("base64url");
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* pi's Client ID Metadata Document, for `clientRegistration: "cimd"`, chosen like Codex chooses its own.
|
|
171
|
+
* The configuration ensures the default callback path. Without the `iss` parameter in authorization
|
|
172
|
+
* responses (RFC 9207), the redirect URI and the document are specific to the MCP server, so a response
|
|
173
|
+
* cannot be mixed up with one from another authorization server (RFC 9700 section 4.4.2.2).
|
|
174
|
+
*/
|
|
175
|
+
function clientMetadataDocument(serverUrl, redirectUrl, metadata) {
|
|
176
|
+
if (!metadata?.client_id_metadata_document_supported ||
|
|
177
|
+
!metadata.token_endpoint_auth_methods_supported?.includes("none")) {
|
|
178
|
+
throw new Error('The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration "cimd"');
|
|
179
|
+
}
|
|
180
|
+
if (metadata.authorization_response_iss_parameter_supported) {
|
|
181
|
+
return { url: `${CLIENT_METADATA_BASE_URL}/client.json`, redirectUrl };
|
|
182
|
+
}
|
|
183
|
+
const id = callbackId(serverUrl);
|
|
184
|
+
const redirect = new URL(redirectUrl);
|
|
185
|
+
redirect.pathname = `${CALLBACK_PATH}/${id}`;
|
|
186
|
+
return { url: `${CLIENT_METADATA_BASE_URL}/${id}/client.json`, redirectUrl: redirect.href };
|
|
187
|
+
}
|
|
139
188
|
function createProvider(serverUrl, store, settings, redirectUrl, onRedirect) {
|
|
140
189
|
return new McpOAuthProvider({
|
|
141
190
|
serverUrl,
|
|
142
191
|
redirectUrl,
|
|
143
192
|
clientMetadata: { client_name: settings.clientName ?? APP_NAME },
|
|
193
|
+
clientMetadataDocument: settings.clientRegistration === "cimd"
|
|
194
|
+
? (metadata) => clientMetadataDocument(serverUrl, redirectUrl, metadata)
|
|
195
|
+
: undefined,
|
|
144
196
|
clientId: settings.clientId,
|
|
145
197
|
clientSecret: settings.clientSecret,
|
|
146
198
|
store,
|
|
@@ -181,6 +233,7 @@ export function createMcpAuthProvider(options) {
|
|
|
181
233
|
const result = await authorizeMcp(provider, {
|
|
182
234
|
serverUrl,
|
|
183
235
|
resourceMetadataUrl: challenge?.resourceMetadataUrl,
|
|
236
|
+
authorizationServerMetadataUrl: settings.authServerMetadataUrl,
|
|
184
237
|
scope: challenge?.scope,
|
|
185
238
|
fetch: (input, init) => fetch(input, { ...init, signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS) }),
|
|
186
239
|
});
|
|
@@ -223,7 +276,7 @@ export class McpSignInCancelledError extends Error {
|
|
|
223
276
|
this.name = "McpSignInCancelledError";
|
|
224
277
|
}
|
|
225
278
|
}
|
|
226
|
-
function
|
|
279
|
+
function responseFromRedirectUrl(input, state, redirectUrl) {
|
|
227
280
|
let url;
|
|
228
281
|
try {
|
|
229
282
|
url = new URL(input.trim());
|
|
@@ -231,6 +284,10 @@ function codeFromRedirectUrl(input, state) {
|
|
|
231
284
|
catch {
|
|
232
285
|
throw new Error("Expected the full redirect URL from the browser address bar");
|
|
233
286
|
}
|
|
287
|
+
// A server-specific redirect URI tells authorization servers apart, so it must match exactly.
|
|
288
|
+
if (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {
|
|
289
|
+
throw new Error("The redirect URL does not match this sign-in's redirect URI");
|
|
290
|
+
}
|
|
234
291
|
const error = url.searchParams.get("error");
|
|
235
292
|
if (error)
|
|
236
293
|
throw new Error(url.searchParams.get("error_description") ?? error);
|
|
@@ -239,16 +296,16 @@ function codeFromRedirectUrl(input, state) {
|
|
|
239
296
|
const code = url.searchParams.get("code");
|
|
240
297
|
if (!code)
|
|
241
298
|
throw new Error("The redirect URL does not contain an authorization code");
|
|
242
|
-
return code;
|
|
299
|
+
return { code, iss: url.searchParams.get("iss") ?? undefined };
|
|
243
300
|
}
|
|
244
301
|
/** Wait for the browser callback or a pasted redirect URL, whichever comes first. */
|
|
245
|
-
async function
|
|
302
|
+
async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt) {
|
|
246
303
|
const controller = new AbortController();
|
|
247
|
-
const fromBrowser = callback.waitForCallback(state
|
|
304
|
+
const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
|
|
248
305
|
const fromUser = prompt.promptForRedirectUrl(controller.signal).then((input) => {
|
|
249
306
|
if (!input?.trim())
|
|
250
307
|
throw new McpSignInCancelledError();
|
|
251
|
-
return
|
|
308
|
+
return responseFromRedirectUrl(input, state, redirectUrl);
|
|
252
309
|
});
|
|
253
310
|
try {
|
|
254
311
|
return await Promise.race([fromBrowser, fromUser]);
|
|
@@ -261,11 +318,12 @@ async function waitForAuthorizationCode(callback, state, prompt) {
|
|
|
261
318
|
}
|
|
262
319
|
}
|
|
263
320
|
/** Listen on `port`, or on a free port when it is taken and not `required`. */
|
|
264
|
-
async function listenForCallback(settings, port, required) {
|
|
321
|
+
async function listenForCallback(settings, extraPaths, port, required) {
|
|
265
322
|
const options = {
|
|
266
323
|
host: settings.host,
|
|
267
324
|
redirectHost: settings.redirectHost,
|
|
268
325
|
path: settings.path,
|
|
326
|
+
extraPaths,
|
|
269
327
|
renderPage: (page) => page.ok
|
|
270
328
|
? oauthSuccessHtml("Signed in to the MCP server. You may now close this page.")
|
|
271
329
|
: oauthErrorHtml(page.message, page.details),
|
|
@@ -286,19 +344,26 @@ async function listenForCallback(settings, port, required) {
|
|
|
286
344
|
export async function signInMcpServer(options) {
|
|
287
345
|
const { serverUrl, store, settings } = options;
|
|
288
346
|
const stored = await store.load();
|
|
347
|
+
const stepUp = options.challenge?.error === "insufficient_scope";
|
|
289
348
|
const callbackOptions = callbackSettings(settings);
|
|
290
349
|
// Reuse the port of the registered redirect URI so the registered client stays valid.
|
|
291
350
|
const registered = registeredRedirectUrls(stored?.clientInformation)[0];
|
|
292
351
|
const preferredPort = callbackOptions.port ?? (registered ? Number(new URL(registered).port) || undefined : undefined);
|
|
293
|
-
const
|
|
352
|
+
const cimd = settings.clientRegistration === "cimd";
|
|
353
|
+
const callback = await listenForCallback(callbackOptions,
|
|
354
|
+
// The redirect URI of a server-specific Client ID Metadata Document.
|
|
355
|
+
cimd ? [`${CALLBACK_PATH}/${callbackId(serverUrl)}`] : [], preferredPort, callbackOptions.port !== undefined);
|
|
294
356
|
const redirectUrl = callbackOptions.fixedRedirectUrl ?? callback.redirectUrl;
|
|
295
357
|
try {
|
|
296
358
|
if (stored) {
|
|
297
359
|
const next = { ...stored };
|
|
298
360
|
// Every sign-in gets a fresh `state` parameter.
|
|
299
361
|
delete next.oauthState;
|
|
300
|
-
// A registered client cannot use another redirect URI, and its tokens belong to it.
|
|
301
|
-
|
|
362
|
+
// A registered client cannot use another redirect URI, and its tokens belong to it. A Client ID
|
|
363
|
+
// Metadata Document is not stored, so with one, a stored client was registered before and is replaced.
|
|
364
|
+
const keepClient = settings.clientId ||
|
|
365
|
+
(cimd ? !stored.clientInformation : registeredRedirectUrls(stored.clientInformation).includes(redirectUrl));
|
|
366
|
+
if (!keepClient) {
|
|
302
367
|
delete next.clientInformation;
|
|
303
368
|
delete next.tokens;
|
|
304
369
|
delete next.tokensExpireAt;
|
|
@@ -312,19 +377,23 @@ export async function signInMcpServer(options) {
|
|
|
312
377
|
const flow = {
|
|
313
378
|
serverUrl,
|
|
314
379
|
resourceMetadataUrl: options.challenge?.resourceMetadataUrl,
|
|
315
|
-
|
|
316
|
-
scope
|
|
380
|
+
authorizationServerMetadataUrl: settings.authServerMetadataUrl,
|
|
381
|
+
// A server asking for more scope gets it on top of the configured scope and, since the challenge
|
|
382
|
+
// may list only the missing scopes, on top of the scope granted so far.
|
|
383
|
+
scope: mergeScopes(settings.scope, stepUp ? stepUpScope(stored?.tokens?.scope, options.challenge?.scope) : options.challenge?.scope),
|
|
317
384
|
};
|
|
318
385
|
// A refresh keeps the granted scope; a server asking for more needs the browser flow.
|
|
319
|
-
const skipRefresh =
|
|
386
|
+
const skipRefresh = stepUp;
|
|
320
387
|
if ((await authorizeMcp(provider, { ...flow, skipRefresh })) === "AUTHORIZED")
|
|
321
388
|
return;
|
|
322
389
|
if (!authorizationUrl)
|
|
323
390
|
throw new Error("OAuth flow did not produce an authorization URL");
|
|
324
391
|
const state = await provider.state();
|
|
392
|
+
// The flow picks the redirect URI, which may be specific to the MCP server.
|
|
393
|
+
const authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl);
|
|
325
394
|
options.prompt.showAuthorizationUrl(authorizationUrl);
|
|
326
|
-
const code = await
|
|
327
|
-
await authorizeMcp(provider, { ...flow, authorizationCode: code });
|
|
395
|
+
const { code, iss } = await waitForAuthorizationResponse(callback, state, authorizationRedirectUrl, options.prompt);
|
|
396
|
+
await authorizeMcp(provider, { ...flow, authorizationCode: code, iss });
|
|
328
397
|
}
|
|
329
398
|
finally {
|
|
330
399
|
await callback.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/extensions/mcp/oauth.ts"],"names":[],"mappings":"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","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, dynamic client registration) against a loopback callback.\n *\n * Credentials live in `<agent-dir>/mcp-auth.json`, keyed by server 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\tauthorizeMcp,\n\tMcpOAuthAuthorizationRequiredError,\n\tMcpOAuthProvider,\n\ttype McpOAuthState,\n\ttype McpOAuthStateStore,\n\ttype OAuthCallbackPage,\n\tOAuthCallbackServer,\n\ttype OAuthChallenge,\n\ttype OAuthClientInformationMixed,\n\tparseWwwAuthenticate,\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\";\n\nconst CALLBACK_HOST = \"127.0.0.1\";\nconst CALLBACK_PATH = \"/callback\";\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}\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\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(serverUrl: string): McpOAuthServerStore {\n\t\tconst key = String(new URL(serverUrl));\n\t\treturn {\n\t\t\tload: () => this.read()[key],\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. */\n\ttokens(serverUrl: string): McpOAuthState[\"tokens\"] {\n\t\treturn this.read()[String(new URL(serverUrl))]?.tokens;\n\t}\n\n\t/** Returns whether credentials were stored for the server. */\n\tremove(serverUrl: string): boolean {\n\t\tconst key = String(new URL(serverUrl));\n\t\tif (!(key in this.read())) return false;\n\t\tthis.write((states) => {\n\t\t\tdelete states[key];\n\t\t});\n\t\treturn true;\n\t}\n\n\tprivate read(): StoredStates {\n\t\treturn this.backend.withLock((current) => ({ result: parseStates(current) }));\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: `${JSON.stringify(states, null, 2)}\\n` };\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\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\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\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\nfunction codeFromRedirectUrl(input: string, state: string): string {\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\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;\n}\n\n/** Wait for the browser callback or a pasted redirect URL, whichever comes first. */\nasync function waitForAuthorizationCode(\n\tcallback: OAuthCallbackServer,\n\tstate: string,\n\tprompt: McpSignInPrompt,\n): Promise<string> {\n\tconst controller = new AbortController();\n\tconst fromBrowser = callback.waitForCallback(state).then((result) => result.code);\n\tconst fromUser = prompt.promptForRedirectUrl(controller.signal).then((input) => {\n\t\tif (!input?.trim()) throw new McpSignInCancelledError();\n\t\treturn codeFromRedirectUrl(input, state);\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\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\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 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 callback = await listenForCallback(callbackOptions, preferredPort, callbackOptions.port !== undefined);\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.\n\t\t\tif (!settings.clientId && !registeredRedirectUrls(stored.clientInformation).includes(redirectUrl)) {\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\t// A server asking for more scope gets it on top of the configured scope.\n\t\t\tscope: mergeScopes(settings.scope, options.challenge?.scope),\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 = options.challenge?.error === \"insufficient_scope\";\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\toptions.prompt.showAuthorizationUrl(authorizationUrl);\n\t\tconst code = await waitForAuthorizationCode(callback, state, options.prompt);\n\t\tawait authorizeMcp(provider, { ...flow, authorizationCode: code });\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":"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","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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/runtime.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -137,7 +137,7 @@ export class McpServerConnection {
|
|
|
137
137
|
this.authProvider = url
|
|
138
138
|
? createMcpAuthProvider({
|
|
139
139
|
serverUrl: url,
|
|
140
|
-
store: options.credentials.forServer(url),
|
|
140
|
+
store: options.credentials.forServer(this.entry.name, url),
|
|
141
141
|
settings: () => this.oauthSettings(),
|
|
142
142
|
onChallenge: (challenge) => {
|
|
143
143
|
this.challenge = challenge;
|
|
@@ -171,6 +171,8 @@ export class McpServerConnection {
|
|
|
171
171
|
callbackUrl: oauth.callbackUrl,
|
|
172
172
|
scope: oauth.scope,
|
|
173
173
|
clientName: oauth.clientName,
|
|
174
|
+
clientRegistration: oauth.clientRegistration,
|
|
175
|
+
authServerMetadataUrl: oauth.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : undefined,
|
|
174
176
|
};
|
|
175
177
|
}
|
|
176
178
|
getClient() {
|