pi-mcp-client 0.9.0 → 0.10.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/dist/index.js +831 -85
- package/docs/authentication.md +75 -0
- package/docs/commands.md +2 -1
- package/docs/configuration.md +2 -1
- package/docs/troubleshooting.md +10 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getAgentDir
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { Type } from "typebox";
|
|
9
|
-
import { Text as
|
|
9
|
+
import { Text as Text5 } from "@earendil-works/pi-tui";
|
|
10
10
|
|
|
11
11
|
// src/prompt-command.ts
|
|
12
12
|
import { BorderedLoader } from "@earendil-works/pi-coding-agent";
|
|
@@ -1770,9 +1770,9 @@ var defaultOptions = {
|
|
|
1770
1770
|
fields: void 0,
|
|
1771
1771
|
searchOptions: void 0,
|
|
1772
1772
|
storeFields: [],
|
|
1773
|
-
logger: (level,
|
|
1773
|
+
logger: (level, message3) => {
|
|
1774
1774
|
if (typeof (console === null || console === void 0 ? void 0 : console[level]) === "function")
|
|
1775
|
-
console[level](
|
|
1775
|
+
console[level](message3);
|
|
1776
1776
|
},
|
|
1777
1777
|
autoVacuum: true
|
|
1778
1778
|
};
|
|
@@ -1845,6 +1845,7 @@ import {
|
|
|
1845
1845
|
OAuthError,
|
|
1846
1846
|
RegistrationRejectedError,
|
|
1847
1847
|
InsecureTokenEndpointError,
|
|
1848
|
+
InsufficientScopeError,
|
|
1848
1849
|
ProtocolError,
|
|
1849
1850
|
ProtocolErrorCode,
|
|
1850
1851
|
ResourceNotFoundError,
|
|
@@ -1887,10 +1888,12 @@ var messages = {
|
|
|
1887
1888
|
oauth_client_rejected: "The authorization server rejected the OAuth client or its authentication method.",
|
|
1888
1889
|
oauth_pkce_unsupported: "The authorization server does not support the required S256 PKCE method.",
|
|
1889
1890
|
oauth_scope_rejected: "The authorization server rejected the requested OAuth scopes.",
|
|
1891
|
+
oauth_scope_required: "The server requires additional OAuth permissions.",
|
|
1890
1892
|
oauth_grant_rejected: "The OAuth authorization code or refresh token was rejected.",
|
|
1891
1893
|
oauth_redirect_rejected: "The authorization server rejected the OAuth callback URL.",
|
|
1892
1894
|
oauth_endpoint_insecure: "The OAuth token endpoint is not secure.",
|
|
1893
1895
|
oauth_issuer_changed: "The OAuth authorization server changed.",
|
|
1896
|
+
oauth_dpop_unavailable: "The OAuth DPoP signing key is unavailable or DPoP is disabled.",
|
|
1894
1897
|
callback_unavailable: "The local OAuth callback port is unavailable.",
|
|
1895
1898
|
busy: "Discovery is still running.",
|
|
1896
1899
|
operation_failed: "The operation failed."
|
|
@@ -1932,11 +1935,13 @@ function diagnostic(code, context) {
|
|
|
1932
1935
|
oauth_registration_rejected: `Check whether the service allows public/native client registration and the callback URL shown by /mcp get ${target}. If registration is restricted, configure an approved public client with oauthClientId, run /mcp reload, then retry /mcp login ${target}.`,
|
|
1933
1936
|
oauth_client_rejected: `Verify oauthClientId, the app's approval, and support for public-client authentication (token endpoint method none). Clients requiring a client secret are not supported. After configuration changes, run /mcp reload and /mcp login ${target}.`,
|
|
1934
1937
|
oauth_pkce_unsupported: "Use an authorization server or app configuration that supports authorization-code login with S256 PKCE. This client cannot fall back to login without PKCE.",
|
|
1938
|
+
oauth_scope_required: `Run /mcp login ${target} to review the server's requested scopes. If no review appears, check oauthScopes against the service's requirements. No authorization or replay was attempted; retry only after approval.`,
|
|
1935
1939
|
oauth_scope_rejected: `Check oauthScopes against the service's allowed scopes and app permissions. Run /mcp reload after changes, then /mcp login ${target}.`,
|
|
1936
1940
|
oauth_grant_rejected: `Run /mcp login ${target} for a fresh authorization code. If it fails again, verify the registered client and exact callback URL shown by /mcp get ${target}.`,
|
|
1937
1941
|
oauth_redirect_rejected: `Register the exact callback URL shown by /mcp get ${target}, including its host, port, and /callback path. --no-browser uses the same callback URL and does not bypass redirect validation.`,
|
|
1938
1942
|
oauth_endpoint_insecure: "Use an authorization server with an HTTPS token endpoint. HTTP is only allowed for loopback endpoints; do not disable TLS verification.",
|
|
1939
1943
|
oauth_issuer_changed: `Verify the server configuration before running /mcp logout ${target} and /mcp login ${target} to trust the new authorization server.`,
|
|
1944
|
+
oauth_dpop_unavailable: `Enable oauthDpop and run /mcp reload, then /mcp login ${target}. If you intentionally disabled DPoP, or the stored key is corrupt or mismatched, run /mcp logout ${target} before signing in again. Bound access or refresh tokens cannot be used without their signing key.`,
|
|
1940
1945
|
callback_unavailable: `Free the configured callback port, change oauthCallbackPort, or run /mcp login ${target} --no-browser.`,
|
|
1941
1946
|
busy: "Wait for the current MCP operation to finish, then retry.",
|
|
1942
1947
|
operation_failed: "Check /mcp for server status and verify the server configuration."
|
|
@@ -1988,6 +1993,8 @@ function diagnose(error, context) {
|
|
|
1988
1993
|
for (let depth = 0; depth < 4 && current instanceof Error; depth++) {
|
|
1989
1994
|
if (current instanceof DiagnosticError)
|
|
1990
1995
|
return diagnostic(current.diagnostic.code, context);
|
|
1996
|
+
if (current instanceof InsufficientScopeError)
|
|
1997
|
+
return diagnostic("oauth_scope_required", context);
|
|
1991
1998
|
if (current instanceof UnauthorizedError)
|
|
1992
1999
|
return diagnostic("authentication_required", context);
|
|
1993
2000
|
if (current instanceof SdkHttpError) {
|
|
@@ -2198,6 +2205,7 @@ var OPTION_FIELDS = [
|
|
|
2198
2205
|
"oauthClientId",
|
|
2199
2206
|
"oauthScopes",
|
|
2200
2207
|
"oauthCallbackPort",
|
|
2208
|
+
"oauthDpop",
|
|
2201
2209
|
"disabled",
|
|
2202
2210
|
"includeTools",
|
|
2203
2211
|
"excludeTools",
|
|
@@ -2219,6 +2227,7 @@ function validateOptions(entry, fail) {
|
|
|
2219
2227
|
fail("oauthScopes (requires OAuth and 1\u2013100 unique OAuth scope tokens)");
|
|
2220
2228
|
if (entry.oauthCallbackPort !== void 0 && (!Number.isInteger(entry.oauthCallbackPort) || Number(entry.oauthCallbackPort) < 1 || Number(entry.oauthCallbackPort) > 65535))
|
|
2221
2229
|
fail("oauthCallbackPort (requires OAuth and a port from 1\u201365535)");
|
|
2230
|
+
if (entry.oauthDpop !== void 0 && typeof entry.oauthDpop !== "boolean") fail("oauthDpop (requires a boolean)");
|
|
2222
2231
|
if (entry.disabled !== void 0 && typeof entry.disabled !== "boolean") fail("disabled");
|
|
2223
2232
|
for (const field of ["timeoutMs", "startupTimeoutMs", "toolTimeoutMs"]) {
|
|
2224
2233
|
if (entry[field] !== void 0 && (!Number.isInteger(entry[field]) || Number(entry[field]) < 100 || Number(entry[field]) > 6e5))
|
|
@@ -2273,7 +2282,7 @@ function parseConnections(value, source) {
|
|
|
2273
2282
|
fail("transport (provide exactly one of command or url)");
|
|
2274
2283
|
if (entry.type === "stdio" && !entry.command || entry.type === "http" && !entry.url)
|
|
2275
2284
|
fail("type (must match command or url)");
|
|
2276
|
-
if (entry.command && (entry.headers || entry.oauthClientId !== void 0 || entry.oauthScopes !== void 0 || entry.oauthCallbackPort !== void 0))
|
|
2285
|
+
if (entry.command && (entry.headers || entry.oauthClientId !== void 0 || entry.oauthScopes !== void 0 || entry.oauthCallbackPort !== void 0 || entry.oauthDpop !== void 0))
|
|
2277
2286
|
fail("HTTP options on stdio transport");
|
|
2278
2287
|
if (entry.url && (entry.args || entry.cwd || entry.env))
|
|
2279
2288
|
fail("stdio options on HTTP transport");
|
|
@@ -2444,7 +2453,7 @@ function resolveServer(config, cwd) {
|
|
|
2444
2453
|
throw new Error("MCP URLs must use HTTP(S), without credentials or fragments.");
|
|
2445
2454
|
result.url = url.href;
|
|
2446
2455
|
}
|
|
2447
|
-
if ((result.oauthClientId !== void 0 || result.oauthScopes !== void 0 || result.oauthCallbackPort !== void 0) && Object.keys(result.headers ?? {}).some(
|
|
2456
|
+
if ((result.oauthClientId !== void 0 || result.oauthScopes !== void 0 || result.oauthCallbackPort !== void 0 || result.oauthDpop !== void 0) && Object.keys(result.headers ?? {}).some(
|
|
2448
2457
|
(key) => key.toLowerCase() === "authorization"
|
|
2449
2458
|
))
|
|
2450
2459
|
throw new Error("Use OAuth or an Authorization header, not both.");
|
|
@@ -2581,7 +2590,7 @@ function searchTools(tools, query, server, limit = DEFAULT_SEARCH_LIMIT) {
|
|
|
2581
2590
|
}
|
|
2582
2591
|
|
|
2583
2592
|
// src/config-commands.ts
|
|
2584
|
-
var ADD_USAGE = "Usage: /mcp add --scope global|project [--replace] [--transport http|stdio] [--header 'Name: value'] [--env KEY=value] [--oauth-client-id ID] [--oauth-scope SCOPE] [--oauth-callback-port PORT] <server> <url> OR <server> -- <command> [args...]. Put options before the server name.";
|
|
2593
|
+
var ADD_USAGE = "Usage: /mcp add --scope global|project [--replace] [--transport http|stdio] [--header 'Name: value'] [--env KEY=value] [--oauth-client-id ID] [--oauth-scope SCOPE] [--oauth-callback-port PORT] [--oauth-dpop] <server> <url> OR <server> -- <command> [args...]. Put options before the server name.";
|
|
2585
2594
|
var REMOVE_USAGE = "Usage: /mcp remove --scope global|project <server>. Credentials are retained; log out first if you want to remove them.";
|
|
2586
2595
|
function commandWords(input) {
|
|
2587
2596
|
const words = [];
|
|
@@ -2642,6 +2651,10 @@ function parseConfigCommand(input) {
|
|
|
2642
2651
|
replace = true;
|
|
2643
2652
|
continue;
|
|
2644
2653
|
}
|
|
2654
|
+
if (flag === "--oauth-dpop" && action === "add") {
|
|
2655
|
+
definition.oauthDpop = true;
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2645
2658
|
if (flag === "--oauth")
|
|
2646
2659
|
throw new ConfigMutationError("Remove --oauth; HTTP authentication is automatic.");
|
|
2647
2660
|
const value = words[index++];
|
|
@@ -2680,7 +2693,7 @@ function parseConfigCommand(input) {
|
|
|
2680
2693
|
if (transport === "http") fail();
|
|
2681
2694
|
definition.command = words[++index];
|
|
2682
2695
|
definition.args = words.slice(index + 1);
|
|
2683
|
-
if (!definition.command || definition.oauthClientId || definition.oauthScopes || definition.oauthCallbackPort || definition.headers) fail();
|
|
2696
|
+
if (!definition.command || definition.oauthClientId || definition.oauthScopes || definition.oauthCallbackPort || definition.oauthDpop || definition.headers) fail();
|
|
2684
2697
|
} else {
|
|
2685
2698
|
if (transport === "stdio" || index + 1 !== words.length || definition.env) fail();
|
|
2686
2699
|
definition.url = words[index];
|
|
@@ -2702,7 +2715,7 @@ function configCommandCompletions(input, servers) {
|
|
|
2702
2715
|
const args = /^--scope\s+(global|project)\s+([^\s]*)$/u.exec(rest);
|
|
2703
2716
|
if (!args) return [];
|
|
2704
2717
|
const [, scope, partial] = args;
|
|
2705
|
-
const values = action === "import" ? [] : action === "remove" ? servers.sort() : ["--replace", "--transport", "--header", "--env", "--oauth-client-id", "--oauth-scope", "--oauth-callback-port"];
|
|
2718
|
+
const values = action === "import" ? [] : action === "remove" ? servers.sort() : ["--replace", "--transport", "--header", "--env", "--oauth-client-id", "--oauth-scope", "--oauth-callback-port", "--oauth-dpop"];
|
|
2706
2719
|
return values.filter((value) => value.startsWith(partial)).map((value) => ({
|
|
2707
2720
|
value: `${action} --scope ${scope} ${value}`,
|
|
2708
2721
|
label: value
|
|
@@ -2714,13 +2727,13 @@ function identifier(value) {
|
|
|
2714
2727
|
return typeof value === "string" && !!value && value.length <= 512 && line(value) === value;
|
|
2715
2728
|
}
|
|
2716
2729
|
function preparePrompt(server, identity, value) {
|
|
2717
|
-
const
|
|
2718
|
-
if (!object(value) || !identifier(value.name) || value.description !== void 0 && typeof value.description !== "string" || value.title !== void 0 && typeof value.title !== "string" || value.arguments !== void 0 && !Array.isArray(value.arguments)) throw
|
|
2730
|
+
const invalid4 = () => failure("protocol_error", { server, operation: "search" });
|
|
2731
|
+
if (!object(value) || !identifier(value.name) || value.description !== void 0 && typeof value.description !== "string" || value.title !== void 0 && typeof value.title !== "string" || value.arguments !== void 0 && !Array.isArray(value.arguments)) throw invalid4();
|
|
2719
2732
|
const args = value.arguments ?? [];
|
|
2720
|
-
if (args.length > 100) throw
|
|
2733
|
+
if (args.length > 100) throw invalid4();
|
|
2721
2734
|
const seen = /* @__PURE__ */ new Set();
|
|
2722
2735
|
const arguments_ = args.map((argument) => {
|
|
2723
|
-
if (!object(argument) || !identifier(argument.name) || seen.has(argument.name) || argument.description !== void 0 && typeof argument.description !== "string" || argument.required !== void 0 && typeof argument.required !== "boolean") throw
|
|
2736
|
+
if (!object(argument) || !identifier(argument.name) || seen.has(argument.name) || argument.description !== void 0 && typeof argument.description !== "string" || argument.required !== void 0 && typeof argument.required !== "boolean") throw invalid4();
|
|
2724
2737
|
seen.add(argument.name);
|
|
2725
2738
|
return { name: argument.name, description: plain(argument.description ?? "").slice(0, 8e3), required: argument.required === true };
|
|
2726
2739
|
});
|
|
@@ -2742,14 +2755,14 @@ function promptCommand(prompt) {
|
|
|
2742
2755
|
}
|
|
2743
2756
|
var promptText = (value) => plain(value).replace(/\r\n?/g, "\n");
|
|
2744
2757
|
function preparePromptSnapshot(result, server) {
|
|
2745
|
-
const
|
|
2746
|
-
if (!object(result) || !Array.isArray(result.messages) || !result.messages.length) throw
|
|
2758
|
+
const invalid4 = () => failure("protocol_error", { server, operation: "prompt" });
|
|
2759
|
+
if (!object(result) || !Array.isArray(result.messages) || !result.messages.length) throw invalid4();
|
|
2747
2760
|
if (Buffer.byteLength(JSON.stringify(result)) > 50 * 1024) throw failure("prompt_too_large", { server, operation: "prompt" });
|
|
2748
2761
|
let usable = true;
|
|
2749
2762
|
const previews = [];
|
|
2750
|
-
const messages2 = result.messages.map((
|
|
2751
|
-
if (!["user", "assistant"].includes(
|
|
2752
|
-
const content =
|
|
2763
|
+
const messages2 = result.messages.map((message3, index) => {
|
|
2764
|
+
if (!["user", "assistant"].includes(message3.role) || !object(message3.content)) throw invalid4();
|
|
2765
|
+
const content = message3.content;
|
|
2753
2766
|
let block;
|
|
2754
2767
|
let preview2;
|
|
2755
2768
|
if (content.type === "text" && typeof content.text === "string") {
|
|
@@ -2769,9 +2782,9 @@ ${promptText(content.resource.text)}`;
|
|
|
2769
2782
|
block = { type: line(String(content.type)), unsupported: true, bytes: Buffer.byteLength(JSON.stringify(content)) };
|
|
2770
2783
|
preview2 = `Unsupported ${line(String(content.type))} content \xB7 ${Buffer.byteLength(JSON.stringify(content))} bytes`;
|
|
2771
2784
|
}
|
|
2772
|
-
previews.push(`Message ${index + 1} \xB7 ${
|
|
2785
|
+
previews.push(`Message ${index + 1} \xB7 ${message3.role} (server supplied)
|
|
2773
2786
|
${preview2}`);
|
|
2774
|
-
return { role:
|
|
2787
|
+
return { role: message3.role, content: block };
|
|
2775
2788
|
});
|
|
2776
2789
|
const description = typeof result.description === "string" ? promptText(result.description) : void 0;
|
|
2777
2790
|
const body = JSON.stringify({ server, ...description === void 0 ? {} : { description }, messages: messages2 }, null, 2);
|
|
@@ -3192,10 +3205,10 @@ var TomlError = class extends Error {
|
|
|
3192
3205
|
line;
|
|
3193
3206
|
column;
|
|
3194
3207
|
codeblock;
|
|
3195
|
-
constructor(
|
|
3208
|
+
constructor(message3, options) {
|
|
3196
3209
|
const [line2, column] = getLineColFromPtr(options.toml, options.ptr);
|
|
3197
3210
|
const codeblock = makeCodeBlock(options.toml, line2, column);
|
|
3198
|
-
super(`Invalid TOML document: ${
|
|
3211
|
+
super(`Invalid TOML document: ${message3}
|
|
3199
3212
|
|
|
3200
3213
|
${codeblock}`, options);
|
|
3201
3214
|
this.line = line2;
|
|
@@ -3591,8 +3604,8 @@ function parseArray(ctx, integersAsBigInt) {
|
|
|
3591
3604
|
}
|
|
3592
3605
|
|
|
3593
3606
|
// node_modules/smol-toml/dist/parse.js
|
|
3594
|
-
function peekTable(key,
|
|
3595
|
-
let t =
|
|
3607
|
+
function peekTable(key, table2, meta, type) {
|
|
3608
|
+
let t = table2;
|
|
3596
3609
|
let m = meta;
|
|
3597
3610
|
let k;
|
|
3598
3611
|
let hasOwn = false;
|
|
@@ -4128,7 +4141,7 @@ import {
|
|
|
4128
4141
|
// src/oauth-page.ts
|
|
4129
4142
|
function oauthCallbackHtml(status) {
|
|
4130
4143
|
const heading = status === "received" ? "Authorization response received" : status === "denied" ? "Authorization not granted" : "Invalid OAuth callback";
|
|
4131
|
-
const
|
|
4144
|
+
const message3 = status === "received" ? "Return to Pi to finish signing in. You can close this tab." : "Return to Pi and try signing in again. You can close this tab.";
|
|
4132
4145
|
return `<!doctype html>
|
|
4133
4146
|
<html lang="en">
|
|
4134
4147
|
<head>
|
|
@@ -4191,28 +4204,509 @@ function oauthCallbackHtml(status) {
|
|
|
4191
4204
|
<div class="logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg></div>
|
|
4192
4205
|
<p class="brand">Pi MCP Client</p>
|
|
4193
4206
|
<h1>${heading}</h1>
|
|
4194
|
-
<p>${
|
|
4207
|
+
<p>${message3}</p>
|
|
4195
4208
|
</main>
|
|
4196
4209
|
</body>
|
|
4197
4210
|
</html>`;
|
|
4198
4211
|
}
|
|
4199
4212
|
|
|
4213
|
+
// src/oauth-fetch.ts
|
|
4214
|
+
import { OAuthErrorCode } from "@modelcontextprotocol/client";
|
|
4215
|
+
var codes = new Set(Object.values(OAuthErrorCode));
|
|
4216
|
+
async function privateOAuthFetch(input, init) {
|
|
4217
|
+
const tokenRequest = init?.body instanceof URLSearchParams && init.body.has("grant_type");
|
|
4218
|
+
if (!tokenRequest) return fetch(input, init);
|
|
4219
|
+
try {
|
|
4220
|
+
const response = await fetch(input, init);
|
|
4221
|
+
const reader = response.body?.getReader();
|
|
4222
|
+
const chunks = [];
|
|
4223
|
+
let size = 0;
|
|
4224
|
+
try {
|
|
4225
|
+
while (reader) {
|
|
4226
|
+
const { done, value } = await reader.read();
|
|
4227
|
+
if (done) break;
|
|
4228
|
+
size += value.byteLength;
|
|
4229
|
+
if (size > 65536) throw new Error("Token response too large");
|
|
4230
|
+
chunks.push(value);
|
|
4231
|
+
}
|
|
4232
|
+
} finally {
|
|
4233
|
+
await reader?.cancel();
|
|
4234
|
+
}
|
|
4235
|
+
const text = new TextDecoder().decode(Buffer.concat(chunks));
|
|
4236
|
+
let body;
|
|
4237
|
+
try {
|
|
4238
|
+
body = JSON.parse(text);
|
|
4239
|
+
} catch {
|
|
4240
|
+
body = void 0;
|
|
4241
|
+
}
|
|
4242
|
+
if (!response.ok || body && typeof body === "object" && "error" in body) {
|
|
4243
|
+
const code = body && typeof body === "object" && "error" in body ? body.error : void 0;
|
|
4244
|
+
const payload = response.ok && body && typeof body === "object" ? { ...body } : {};
|
|
4245
|
+
return Response.json({
|
|
4246
|
+
...payload,
|
|
4247
|
+
error: typeof code === "string" && codes.has(code) ? code : "server_error",
|
|
4248
|
+
error_description: void 0,
|
|
4249
|
+
error_uri: void 0
|
|
4250
|
+
}, {
|
|
4251
|
+
status: response.status,
|
|
4252
|
+
headers: response.headers
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
if (!body || typeof body !== "object") throw new Error("Invalid token response");
|
|
4256
|
+
return new Response(text, { status: response.status, headers: response.headers });
|
|
4257
|
+
} catch {
|
|
4258
|
+
throw failure("oauth_failed", { operation: "auth" });
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
// src/dpop.ts
|
|
4263
|
+
import { DpopSession, generateDpopKeyPair } from "@modelcontextprotocol/client";
|
|
4264
|
+
|
|
4265
|
+
// node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
4266
|
+
var encoder = new TextEncoder();
|
|
4267
|
+
var decoder = new TextDecoder();
|
|
4268
|
+
var strictDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
4269
|
+
var MAX_INT32 = 2 ** 32;
|
|
4270
|
+
var NON_ASCII = /[^\x00-\x7f]/;
|
|
4271
|
+
function encode(string) {
|
|
4272
|
+
if (typeof string == "string" && string.length >= 128) {
|
|
4273
|
+
if (NON_ASCII.test(string))
|
|
4274
|
+
throw new TypeError("non-ASCII string encountered in encode()");
|
|
4275
|
+
return encoder.encode(string);
|
|
4276
|
+
}
|
|
4277
|
+
const bytes = new Uint8Array(string.length);
|
|
4278
|
+
for (let i = 0; i < string.length; i++) {
|
|
4279
|
+
const code = string.charCodeAt(i);
|
|
4280
|
+
if (code > 127)
|
|
4281
|
+
throw new TypeError("non-ASCII string encountered in encode()");
|
|
4282
|
+
bytes[i] = code;
|
|
4283
|
+
}
|
|
4284
|
+
return bytes;
|
|
4285
|
+
}
|
|
4286
|
+
function encodeBase64(input, url = false) {
|
|
4287
|
+
if (Uint8Array.prototype.toBase64)
|
|
4288
|
+
return input.toBase64({ alphabet: url ? "base64url" : "base64", omitPadding: url });
|
|
4289
|
+
const CHUNK_SIZE = 32768, arr = [];
|
|
4290
|
+
for (let i = 0; i < input.length; i += CHUNK_SIZE)
|
|
4291
|
+
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
|
4292
|
+
const encoded = btoa(arr.join(""));
|
|
4293
|
+
return url ? encoded.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") : encoded;
|
|
4294
|
+
}
|
|
4295
|
+
function decodeBase64(encoded, url = false) {
|
|
4296
|
+
if (Uint8Array.fromBase64)
|
|
4297
|
+
return Uint8Array.fromBase64(encoded, { alphabet: url ? "base64url" : "base64" });
|
|
4298
|
+
if (url) {
|
|
4299
|
+
if (encoded.includes("+") || encoded.includes("/"))
|
|
4300
|
+
throw new TypeError("Invalid base64url");
|
|
4301
|
+
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
4302
|
+
}
|
|
4303
|
+
const binary = atob(encoded), bytes = new Uint8Array(binary.length);
|
|
4304
|
+
for (let i = 0; i < binary.length; i++)
|
|
4305
|
+
bytes[i] = binary.charCodeAt(i);
|
|
4306
|
+
return bytes;
|
|
4307
|
+
}
|
|
4308
|
+
async function digest(algorithm, data) {
|
|
4309
|
+
const subtleDigest = `SHA-${algorithm.slice(-3)}`;
|
|
4310
|
+
return new Uint8Array(await crypto.subtle.digest(subtleDigest, data));
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
// node_modules/jose/dist/webapi/util/errors.js
|
|
4314
|
+
var JOSEError = class extends Error {
|
|
4315
|
+
static code = "ERR_JOSE_GENERIC";
|
|
4316
|
+
code = "ERR_JOSE_GENERIC";
|
|
4317
|
+
constructor(message3, options) {
|
|
4318
|
+
super(message3, options), this.name = this.constructor.name, Error.captureStackTrace?.(this, this.constructor);
|
|
4319
|
+
}
|
|
4320
|
+
};
|
|
4321
|
+
var JOSENotSupported = class extends JOSEError {
|
|
4322
|
+
static code = "ERR_JOSE_NOT_SUPPORTED";
|
|
4323
|
+
code = "ERR_JOSE_NOT_SUPPORTED";
|
|
4324
|
+
};
|
|
4325
|
+
var JWKInvalid = class extends JOSEError {
|
|
4326
|
+
static code = "ERR_JWK_INVALID";
|
|
4327
|
+
code = "ERR_JWK_INVALID";
|
|
4328
|
+
};
|
|
4329
|
+
|
|
4330
|
+
// node_modules/jose/dist/webapi/util/base64url.js
|
|
4331
|
+
var invalid2 = "The input to be decoded is not correctly encoded.";
|
|
4332
|
+
function decode(input) {
|
|
4333
|
+
try {
|
|
4334
|
+
return decodeBase64(typeof input == "string" ? input : decoder.decode(input), true);
|
|
4335
|
+
} catch (cause) {
|
|
4336
|
+
throw new TypeError(invalid2, { cause });
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
function encode2(input) {
|
|
4340
|
+
return encodeBase64(typeof input == "string" ? encoder.encode(input) : input, true);
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
// node_modules/jose/dist/webapi/lib/validate.js
|
|
4344
|
+
function isObject(input) {
|
|
4345
|
+
if (typeof input != "object" || input === null || Object.prototype.toString.call(input) !== "[object Object]")
|
|
4346
|
+
return false;
|
|
4347
|
+
const prototype = Object.getPrototypeOf(input);
|
|
4348
|
+
return prototype === null || Object.getPrototypeOf(prototype) === null;
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
// node_modules/jose/dist/webapi/lib/key.js
|
|
4352
|
+
var isCryptoKey = (key) => {
|
|
4353
|
+
if (key?.[Symbol.toStringTag] === "CryptoKey")
|
|
4354
|
+
return true;
|
|
4355
|
+
try {
|
|
4356
|
+
return key instanceof CryptoKey;
|
|
4357
|
+
} catch {
|
|
4358
|
+
return false;
|
|
4359
|
+
}
|
|
4360
|
+
};
|
|
4361
|
+
var isKeyObject = (key) => key?.[Symbol.toStringTag] === "KeyObject";
|
|
4362
|
+
var isKeyLike = (key) => isCryptoKey(key) || isKeyObject(key);
|
|
4363
|
+
function message(msg, actual, ...types) {
|
|
4364
|
+
if (types.length > 2) {
|
|
4365
|
+
const last2 = types.pop();
|
|
4366
|
+
msg += `one of type ${types.join(", ")}, or ${last2}.`;
|
|
4367
|
+
} else types.length === 2 ? msg += `one of type ${types[0]} or ${types[1]}.` : msg += `of type ${types[0]}.`;
|
|
4368
|
+
return actual == null ? msg += ` Received ${actual}` : typeof actual == "function" && actual.name ? msg += ` Received function ${actual.name}` : typeof actual == "object" && actual != null && actual.constructor?.name && (msg += ` Received an instance of ${actual.constructor.name}`), msg;
|
|
4369
|
+
}
|
|
4370
|
+
var invalidKeyInput = (actual, ...types) => message("Key must be ", actual, ...types);
|
|
4371
|
+
function snapshotJwk(jwk) {
|
|
4372
|
+
return { __proto__: null, ...jwk };
|
|
4373
|
+
}
|
|
4374
|
+
function normalizeJwk(jwk) {
|
|
4375
|
+
const normalized = snapshotJwk(jwk);
|
|
4376
|
+
if (normalized.ext !== void 0 && typeof normalized.ext != "boolean")
|
|
4377
|
+
throw new TypeError('"ext" (Extractable) Parameter must be a boolean');
|
|
4378
|
+
if (normalized.key_ops !== void 0) {
|
|
4379
|
+
const value = normalized.key_ops, keyOps = Array.isArray(value) ? [...value] : void 0;
|
|
4380
|
+
if (!keyOps || keyOps.some((operation) => typeof operation != "string") || new Set(keyOps).size !== keyOps.length)
|
|
4381
|
+
throw new TypeError('"key_ops" (Key Operations) Parameter must be an array of unique strings');
|
|
4382
|
+
normalized.key_ops = keyOps;
|
|
4383
|
+
}
|
|
4384
|
+
return normalized;
|
|
4385
|
+
}
|
|
4386
|
+
function validateExtractableOption(extractable) {
|
|
4387
|
+
if (extractable !== void 0 && typeof extractable != "boolean")
|
|
4388
|
+
throw new TypeError('"extractable" option must be a boolean');
|
|
4389
|
+
return extractable;
|
|
4390
|
+
}
|
|
4391
|
+
async function jwkToKey(entry, jwk, extractable) {
|
|
4392
|
+
if (!entry.kty.includes(jwk.kty))
|
|
4393
|
+
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
|
4394
|
+
const algorithm = entry.resolve?.({ kty: jwk.kty, crv: jwk.crv }) ?? entry.subtle, isPrivate = !!(jwk.d || jwk.priv), keyData = { ...jwk, ext: extractable ?? jwk.ext };
|
|
4395
|
+
return keyData.kty !== "AKP" && delete keyData.alg, delete keyData.use, crypto.subtle.importKey("jwk", keyData, algorithm, keyData.ext ?? !isPrivate, jwk.key_ops ?? entry.usages[isPrivate ? 1 : 0]);
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
// node_modules/jose/dist/webapi/lib/key_descriptor.js
|
|
4399
|
+
function table(entries) {
|
|
4400
|
+
const out = { __proto__: null };
|
|
4401
|
+
for (const alg in entries)
|
|
4402
|
+
out[alg] = { ...entries[alg], alg };
|
|
4403
|
+
return out;
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4406
|
+
// node_modules/jose/dist/webapi/lib/jwe_algorithms.js
|
|
4407
|
+
var wrap = [
|
|
4408
|
+
["encrypt", "wrapKey"],
|
|
4409
|
+
["decrypt", "unwrapKey"]
|
|
4410
|
+
];
|
|
4411
|
+
var derive = [[], ["deriveBits"]];
|
|
4412
|
+
var none = [[], []];
|
|
4413
|
+
function rsaes(bits) {
|
|
4414
|
+
return {
|
|
4415
|
+
kty: ["RSA"],
|
|
4416
|
+
mode: "key-encryption",
|
|
4417
|
+
subtle: { name: "RSA-OAEP", hash: `SHA-${bits}` },
|
|
4418
|
+
usages: wrap,
|
|
4419
|
+
ops: ["wrapKey", "unwrapKey"]
|
|
4420
|
+
};
|
|
4421
|
+
}
|
|
4422
|
+
function ecdh(mode) {
|
|
4423
|
+
return {
|
|
4424
|
+
kty: ["EC", "OKP"],
|
|
4425
|
+
mode,
|
|
4426
|
+
subtle: { name: "ECDH" },
|
|
4427
|
+
resolve: ({ kty, crv, asymmetricKeyType }) => {
|
|
4428
|
+
if (crv === "X25519" || asymmetricKeyType === "x25519")
|
|
4429
|
+
return { name: "X25519" };
|
|
4430
|
+
if (kty === "OKP")
|
|
4431
|
+
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
|
4432
|
+
return { name: "ECDH", namedCurve: crv };
|
|
4433
|
+
},
|
|
4434
|
+
usages: derive,
|
|
4435
|
+
ops: [void 0, "deriveBits"]
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
function aeskw(bits, gcm = false) {
|
|
4439
|
+
return {
|
|
4440
|
+
kty: ["oct"],
|
|
4441
|
+
mode: "key-wrapping",
|
|
4442
|
+
secret: true,
|
|
4443
|
+
subtle: { name: gcm ? "AES-GCM" : "AES-KW", length: bits },
|
|
4444
|
+
usages: none,
|
|
4445
|
+
ops: gcm ? ["encrypt", "decrypt"] : ["wrapKey", "unwrapKey"]
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
function pbes2() {
|
|
4449
|
+
return {
|
|
4450
|
+
kty: ["oct"],
|
|
4451
|
+
mode: "key-wrapping",
|
|
4452
|
+
secret: true,
|
|
4453
|
+
subtle: { name: "PBKDF2" },
|
|
4454
|
+
usages: none,
|
|
4455
|
+
ops: ["deriveBits", "deriveBits"]
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
var JWE = table({
|
|
4459
|
+
dir: {
|
|
4460
|
+
kty: ["oct"],
|
|
4461
|
+
mode: "direct-encryption",
|
|
4462
|
+
secret: true,
|
|
4463
|
+
subtle: { name: "AES-GCM" },
|
|
4464
|
+
usages: none,
|
|
4465
|
+
ops: ["encrypt", "decrypt"]
|
|
4466
|
+
},
|
|
4467
|
+
"RSA-OAEP": rsaes(1),
|
|
4468
|
+
"RSA-OAEP-256": rsaes(256),
|
|
4469
|
+
"RSA-OAEP-384": rsaes(384),
|
|
4470
|
+
"RSA-OAEP-512": rsaes(512),
|
|
4471
|
+
"ECDH-ES": ecdh("direct-key-agreement"),
|
|
4472
|
+
"ECDH-ES+A128KW": ecdh("key-agreement-with-key-wrapping"),
|
|
4473
|
+
"ECDH-ES+A192KW": ecdh("key-agreement-with-key-wrapping"),
|
|
4474
|
+
"ECDH-ES+A256KW": ecdh("key-agreement-with-key-wrapping"),
|
|
4475
|
+
A128KW: aeskw(128),
|
|
4476
|
+
A192KW: aeskw(192),
|
|
4477
|
+
A256KW: aeskw(256),
|
|
4478
|
+
A128GCMKW: aeskw(128, true),
|
|
4479
|
+
A192GCMKW: aeskw(192, true),
|
|
4480
|
+
A256GCMKW: aeskw(256, true),
|
|
4481
|
+
"PBES2-HS256+A128KW": pbes2(),
|
|
4482
|
+
"PBES2-HS384+A192KW": pbes2(),
|
|
4483
|
+
"PBES2-HS512+A256KW": pbes2()
|
|
4484
|
+
});
|
|
4485
|
+
var contentOps = ["encrypt", "decrypt"];
|
|
4486
|
+
function contentEncryption(bits, cbc = false) {
|
|
4487
|
+
return {
|
|
4488
|
+
kty: ["oct"],
|
|
4489
|
+
secret: true,
|
|
4490
|
+
subtle: { name: cbc ? "AES-CBC" : "AES-GCM", length: bits },
|
|
4491
|
+
usages: none,
|
|
4492
|
+
ops: contentOps,
|
|
4493
|
+
cekBits: bits,
|
|
4494
|
+
ivBits: cbc ? 128 : 96,
|
|
4495
|
+
cbc
|
|
4496
|
+
};
|
|
4497
|
+
}
|
|
4498
|
+
var ENC = table({
|
|
4499
|
+
A128GCM: contentEncryption(128),
|
|
4500
|
+
A192GCM: contentEncryption(192),
|
|
4501
|
+
A256GCM: contentEncryption(256),
|
|
4502
|
+
"A128CBC-HS256": contentEncryption(256, true),
|
|
4503
|
+
"A192CBC-HS384": contentEncryption(384, true),
|
|
4504
|
+
"A256CBC-HS512": contentEncryption(512, true)
|
|
4505
|
+
});
|
|
4506
|
+
|
|
4507
|
+
// node_modules/jose/dist/webapi/lib/jws_algorithms.js
|
|
4508
|
+
var sig = [["verify"], ["sign"]];
|
|
4509
|
+
function hmac(bits) {
|
|
4510
|
+
const subtle = { name: "HMAC", hash: `SHA-${bits}` };
|
|
4511
|
+
return { kty: ["oct"], secret: true, subtle, signing: subtle, usages: sig };
|
|
4512
|
+
}
|
|
4513
|
+
function rsa(bits, saltLength) {
|
|
4514
|
+
const subtle = { name: saltLength ? "RSA-PSS" : "RSASSA-PKCS1-v1_5", hash: `SHA-${bits}` };
|
|
4515
|
+
return {
|
|
4516
|
+
kty: ["RSA"],
|
|
4517
|
+
subtle,
|
|
4518
|
+
signing: saltLength ? { ...subtle, saltLength } : subtle,
|
|
4519
|
+
usages: sig,
|
|
4520
|
+
minRsaBits: 2048
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
function ecdsa(crv, bits) {
|
|
4524
|
+
return {
|
|
4525
|
+
kty: ["EC"],
|
|
4526
|
+
crv,
|
|
4527
|
+
subtle: { name: "ECDSA", namedCurve: crv },
|
|
4528
|
+
signing: { name: "ECDSA", hash: `SHA-${bits}` },
|
|
4529
|
+
usages: sig
|
|
4530
|
+
};
|
|
4531
|
+
}
|
|
4532
|
+
function eddsa() {
|
|
4533
|
+
const subtle = { name: "Ed25519" };
|
|
4534
|
+
return {
|
|
4535
|
+
kty: ["OKP"],
|
|
4536
|
+
crv: "Ed25519",
|
|
4537
|
+
subtle,
|
|
4538
|
+
signing: subtle,
|
|
4539
|
+
usages: sig
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
function mldsa(bits) {
|
|
4543
|
+
const subtle = { name: `ML-DSA-${bits}` };
|
|
4544
|
+
return {
|
|
4545
|
+
kty: ["AKP"],
|
|
4546
|
+
subtle,
|
|
4547
|
+
signing: subtle,
|
|
4548
|
+
usages: sig
|
|
4549
|
+
};
|
|
4550
|
+
}
|
|
4551
|
+
var JWS = table({
|
|
4552
|
+
HS256: hmac(256),
|
|
4553
|
+
HS384: hmac(384),
|
|
4554
|
+
HS512: hmac(512),
|
|
4555
|
+
RS256: rsa(256),
|
|
4556
|
+
RS384: rsa(384),
|
|
4557
|
+
RS512: rsa(512),
|
|
4558
|
+
PS256: rsa(256, 32),
|
|
4559
|
+
PS384: rsa(384, 48),
|
|
4560
|
+
PS512: rsa(512, 64),
|
|
4561
|
+
ES256: ecdsa("P-256", 256),
|
|
4562
|
+
ES384: ecdsa("P-384", 384),
|
|
4563
|
+
ES512: ecdsa("P-521", 512),
|
|
4564
|
+
EdDSA: eddsa(),
|
|
4565
|
+
Ed25519: eddsa(),
|
|
4566
|
+
"ML-DSA-44": mldsa(44),
|
|
4567
|
+
"ML-DSA-65": mldsa(65),
|
|
4568
|
+
"ML-DSA-87": mldsa(87)
|
|
4569
|
+
});
|
|
4570
|
+
|
|
4571
|
+
// node_modules/jose/dist/webapi/lib/key_algorithm.js
|
|
4572
|
+
function unsupportedAlg(source = 'JWK "alg" (Algorithm) Parameter') {
|
|
4573
|
+
throw new JOSENotSupported(`Invalid or unsupported ${source} value`);
|
|
4574
|
+
}
|
|
4575
|
+
function keyAlgorithm(alg, source) {
|
|
4576
|
+
return (typeof alg == "string" ? JWS[alg] ?? JWE[alg] : void 0) ?? unsupportedAlg(source);
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4579
|
+
// node_modules/jose/dist/webapi/key/export.js
|
|
4580
|
+
async function exportJWK(key) {
|
|
4581
|
+
if (isKeyObject(key))
|
|
4582
|
+
if (key.type === "secret")
|
|
4583
|
+
key = key.export();
|
|
4584
|
+
else
|
|
4585
|
+
return key.export({ format: "jwk" });
|
|
4586
|
+
if (key instanceof Uint8Array)
|
|
4587
|
+
return {
|
|
4588
|
+
kty: "oct",
|
|
4589
|
+
k: encode2(key)
|
|
4590
|
+
};
|
|
4591
|
+
if (!isCryptoKey(key))
|
|
4592
|
+
throw new TypeError(invalidKeyInput(key, "CryptoKey", "KeyObject", "Uint8Array"));
|
|
4593
|
+
if (!key.extractable)
|
|
4594
|
+
throw new TypeError("non-extractable CryptoKey cannot be exported as a JWK");
|
|
4595
|
+
const jwk = await crypto.subtle.exportKey("jwk", key);
|
|
4596
|
+
delete jwk.ext, delete jwk.key_ops, delete jwk.use, jwk.kty !== "AKP" && delete jwk.alg;
|
|
4597
|
+
for (const parameter of Object.keys(jwk))
|
|
4598
|
+
jwk[parameter] === void 0 && delete jwk[parameter];
|
|
4599
|
+
return jwk;
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
// node_modules/jose/dist/webapi/jwk/thumbprint.js
|
|
4603
|
+
var check = (value, description) => {
|
|
4604
|
+
if (typeof value != "string" || !value)
|
|
4605
|
+
throw new JWKInvalid(`${description} missing or invalid`);
|
|
4606
|
+
};
|
|
4607
|
+
async function calculateJwkThumbprint(key, digestAlgorithm) {
|
|
4608
|
+
let jwk;
|
|
4609
|
+
if (isObject(key)) {
|
|
4610
|
+
if (jwk = snapshotJwk(key), typeof jwk.kty != "string")
|
|
4611
|
+
throw new TypeError(invalidKeyInput(key, "CryptoKey", "KeyObject", "JSON Web Key"));
|
|
4612
|
+
} else if (isKeyLike(key))
|
|
4613
|
+
jwk = snapshotJwk(await exportJWK(key));
|
|
4614
|
+
else
|
|
4615
|
+
throw new TypeError(invalidKeyInput(key, "CryptoKey", "KeyObject", "JSON Web Key"));
|
|
4616
|
+
if (digestAlgorithm ??= "sha256", digestAlgorithm !== "sha256" && digestAlgorithm !== "sha384" && digestAlgorithm !== "sha512")
|
|
4617
|
+
throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"');
|
|
4618
|
+
let components;
|
|
4619
|
+
switch (jwk.kty) {
|
|
4620
|
+
case "AKP":
|
|
4621
|
+
check(jwk.alg, '"alg" (Algorithm) Parameter'), check(jwk.pub, '"pub" (Public key) Parameter'), components = { alg: jwk.alg, kty: jwk.kty, pub: jwk.pub };
|
|
4622
|
+
break;
|
|
4623
|
+
case "EC":
|
|
4624
|
+
check(jwk.crv, '"crv" (Curve) Parameter'), check(jwk.x, '"x" (X Coordinate) Parameter'), check(jwk.y, '"y" (Y Coordinate) Parameter'), components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
4625
|
+
break;
|
|
4626
|
+
case "OKP":
|
|
4627
|
+
check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter'), check(jwk.x, '"x" (Public Key) Parameter'), components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
|
|
4628
|
+
break;
|
|
4629
|
+
case "RSA":
|
|
4630
|
+
check(jwk.e, '"e" (Exponent) Parameter'), check(jwk.n, '"n" (Modulus) Parameter'), components = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
4631
|
+
break;
|
|
4632
|
+
case "oct":
|
|
4633
|
+
if (typeof jwk.k != "string")
|
|
4634
|
+
throw new JWKInvalid('"k" (Key Value) Parameter missing or invalid');
|
|
4635
|
+
components = { k: jwk.k, kty: jwk.kty };
|
|
4636
|
+
break;
|
|
4637
|
+
default:
|
|
4638
|
+
throw new JOSENotSupported('"kty" (Key Type) Parameter missing or unsupported');
|
|
4639
|
+
}
|
|
4640
|
+
const data = encode(JSON.stringify(components));
|
|
4641
|
+
return encode2(await digest(digestAlgorithm, data));
|
|
4642
|
+
}
|
|
4643
|
+
|
|
4644
|
+
// node_modules/jose/dist/webapi/key/import.js
|
|
4645
|
+
async function importJWK(jwk, alg, options) {
|
|
4646
|
+
if (!isObject(jwk))
|
|
4647
|
+
throw new TypeError("JWK must be an object");
|
|
4648
|
+
const normalized = normalizeJwk(jwk), extractable = validateExtractableOption(options?.extractable), { alg: jwkAlg } = normalized;
|
|
4649
|
+
if (alg ??= jwkAlg, normalized.kty !== "oct" && !alg)
|
|
4650
|
+
throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
|
|
4651
|
+
switch (normalized.kty) {
|
|
4652
|
+
case "oct":
|
|
4653
|
+
if (typeof normalized.k != "string")
|
|
4654
|
+
throw new TypeError('missing "k" (Key Value) Parameter value');
|
|
4655
|
+
return decode(normalized.k);
|
|
4656
|
+
case "AKP": {
|
|
4657
|
+
if (typeof jwkAlg != "string" || !jwkAlg)
|
|
4658
|
+
throw new TypeError('missing "alg" (Algorithm) Parameter value');
|
|
4659
|
+
if (alg !== jwkAlg)
|
|
4660
|
+
throw new TypeError("JWK alg and alg option value mismatch");
|
|
4661
|
+
return jwkToKey(keyAlgorithm(alg), normalized, extractable);
|
|
4662
|
+
}
|
|
4663
|
+
case "RSA":
|
|
4664
|
+
case "EC":
|
|
4665
|
+
case "OKP":
|
|
4666
|
+
return jwkToKey(keyAlgorithm(alg), normalized, extractable);
|
|
4667
|
+
default:
|
|
4668
|
+
throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value');
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4672
|
+
// src/dpop.ts
|
|
4673
|
+
async function createDpopKey(issuer, clientId) {
|
|
4674
|
+
const pair = await generateDpopKeyPair({ alg: "ES256", extractable: true });
|
|
4675
|
+
return { issuer, clientId, privateJwk: await exportJWK(pair.privateKey) };
|
|
4676
|
+
}
|
|
4677
|
+
async function restoreDpopSession(stored) {
|
|
4678
|
+
const jwk = stored.privateJwk;
|
|
4679
|
+
if (!jwk || jwk.kty !== "EC" || jwk.crv !== "P-256" || ![jwk.x, jwk.y, jwk.d].every((value) => typeof value === "string" && /^[A-Za-z0-9_-]{43}$/u.test(value)))
|
|
4680
|
+
throw new Error("Invalid DPoP key");
|
|
4681
|
+
const publicJwk = { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
4682
|
+
const privateKey = await importJWK({ ...publicJwk, d: jwk.d }, "ES256", { extractable: false });
|
|
4683
|
+
const publicKey = await importJWK(publicJwk, "ES256");
|
|
4684
|
+
if (privateKey instanceof Uint8Array || publicKey instanceof Uint8Array) throw new Error("Invalid DPoP key");
|
|
4685
|
+
return DpopSession.create({ keyPair: {
|
|
4686
|
+
privateKey,
|
|
4687
|
+
publicKey,
|
|
4688
|
+
publicJwk,
|
|
4689
|
+
thumbprint: await calculateJwkThumbprint(publicJwk),
|
|
4690
|
+
alg: "ES256"
|
|
4691
|
+
} });
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4200
4694
|
// src/auth.ts
|
|
4201
4695
|
function callbackUrl(options = {}) {
|
|
4202
4696
|
return `http://127.0.0.1:${options.oauthCallbackPort ?? 19847}/callback`;
|
|
4203
4697
|
}
|
|
4204
4698
|
function parseCallback(value, redirect, state) {
|
|
4205
|
-
const
|
|
4206
|
-
if (value.length > 16384 || /[\u0000-\u0020\u007f]/u.test(value)) throw
|
|
4699
|
+
const invalid4 = () => failure("oauth_failed", { operation: "auth" });
|
|
4700
|
+
if (value.length > 16384 || /[\u0000-\u0020\u007f]/u.test(value)) throw invalid4();
|
|
4207
4701
|
let target;
|
|
4208
4702
|
try {
|
|
4209
4703
|
target = new URL(value);
|
|
4210
4704
|
} catch {
|
|
4211
|
-
throw
|
|
4705
|
+
throw invalid4();
|
|
4212
4706
|
}
|
|
4213
4707
|
const expected = new URL(redirect);
|
|
4214
4708
|
const params = target.searchParams;
|
|
4215
|
-
if (target.origin !== expected.origin || target.pathname !== expected.pathname || target.username || target.password || target.hash || [...params.keys()].some((key) => params.getAll(key).length !== 1) || params.get("state") !== state || params.has("code") === params.has("error") || !(params.get("code") || params.get("error"))) throw
|
|
4709
|
+
if (target.origin !== expected.origin || target.pathname !== expected.pathname || target.username || target.password || target.hash || [...params.keys()].some((key) => params.getAll(key).length !== 1) || params.get("state") !== state || params.has("code") === params.has("error") || !(params.get("code") || params.get("error"))) throw invalid4();
|
|
4216
4710
|
return params;
|
|
4217
4711
|
}
|
|
4218
4712
|
function credentialKey({ server, url, clientId }) {
|
|
@@ -4256,6 +4750,7 @@ var OAuthProvider = class {
|
|
|
4256
4750
|
constructor(identity, store, redirect, options = {}) {
|
|
4257
4751
|
this.store = store;
|
|
4258
4752
|
this.redirect = redirect;
|
|
4753
|
+
this.options = options;
|
|
4259
4754
|
const { server, url, clientId } = identity;
|
|
4260
4755
|
this.clientId = clientId;
|
|
4261
4756
|
this.redirectUrl = callbackUrl(options);
|
|
@@ -4279,6 +4774,9 @@ var OAuthProvider = class {
|
|
|
4279
4774
|
key;
|
|
4280
4775
|
verifier;
|
|
4281
4776
|
discovery;
|
|
4777
|
+
requestedScope;
|
|
4778
|
+
dpopSession;
|
|
4779
|
+
resolvedDpopSession;
|
|
4282
4780
|
expectedState = randomUUID2();
|
|
4283
4781
|
clientId;
|
|
4284
4782
|
assertCurrent() {
|
|
@@ -4296,7 +4794,13 @@ var OAuthProvider = class {
|
|
|
4296
4794
|
}
|
|
4297
4795
|
clientInformation(ctx) {
|
|
4298
4796
|
this.assertCurrent();
|
|
4797
|
+
return this.lookupClientInformation(ctx);
|
|
4798
|
+
}
|
|
4799
|
+
lookupClientInformation(ctx) {
|
|
4299
4800
|
if (!ctx) return void 0;
|
|
4801
|
+
const grantIssuer = this.data.grantIssuer ?? this.data.tokens?.issuer;
|
|
4802
|
+
if (grantIssuer && grantIssuer !== ctx.issuer || this.data.dpopKey && this.data.dpopKey.issuer !== ctx.issuer)
|
|
4803
|
+
throw failure("oauth_issuer_changed", { operation: "auth", oauth: true });
|
|
4300
4804
|
const stored = Object.hasOwn(this.data.clients, ctx.issuer) ? this.data.clients[ctx.issuer] : void 0;
|
|
4301
4805
|
if (!this.clientId) {
|
|
4302
4806
|
const registration = this.data.registrations?.[ctx.issuer];
|
|
@@ -4313,8 +4817,12 @@ var OAuthProvider = class {
|
|
|
4313
4817
|
if (this.clientId && (info.client_id !== this.clientId || info.client_secret))
|
|
4314
4818
|
throw new Error("Cannot replace the configured public OAuth client.");
|
|
4315
4819
|
const previous = this.data.clients[ctx.issuer];
|
|
4316
|
-
if (!this.clientId && previous && previous.client_id !== info.client_id
|
|
4317
|
-
delete this.data.tokens;
|
|
4820
|
+
if (!this.clientId && previous && previous.client_id !== info.client_id) {
|
|
4821
|
+
if (this.data.tokens?.issuer === ctx.issuer) delete this.data.tokens;
|
|
4822
|
+
delete this.data.dpopKey;
|
|
4823
|
+
this.dpopSession = void 0;
|
|
4824
|
+
this.resolvedDpopSession = void 0;
|
|
4825
|
+
}
|
|
4318
4826
|
this.data.clients = { ...this.data.clients, [ctx.issuer]: info };
|
|
4319
4827
|
if (!this.clientId) this.data.registrations = {
|
|
4320
4828
|
...this.data.registrations,
|
|
@@ -4328,14 +4836,58 @@ var OAuthProvider = class {
|
|
|
4328
4836
|
return !ctx || tokens2?.issuer === ctx.issuer ? tokens2 : void 0;
|
|
4329
4837
|
}
|
|
4330
4838
|
saveTokens(tokens2) {
|
|
4839
|
+
if (tokens2.token_type?.toLowerCase() === "dpop" && (!this.options.oauthDpop || !this.data.dpopKey || this.data.dpopKey.issuer !== tokens2.issuer))
|
|
4840
|
+
throw failure("oauth_dpop_unavailable", { operation: "auth" });
|
|
4331
4841
|
if (this.clientId) {
|
|
4332
4842
|
if (!tokens2.issuer) throw new Error("OAuth tokens have no issuer.");
|
|
4333
4843
|
const info = this.clientInformation({ issuer: tokens2.issuer });
|
|
4334
4844
|
this.data.clients = { ...this.data.clients, [tokens2.issuer]: info };
|
|
4335
4845
|
}
|
|
4336
|
-
this.data.tokens
|
|
4846
|
+
const scope = tokens2.scope ?? this.requestedScope ?? (tokens2.issuer === this.data.tokens?.issuer ? this.data.tokens?.scope : void 0);
|
|
4847
|
+
this.data.dpopRefreshBound = !!tokens2.refresh_token && (!!this.dpopSession || !!this.data.dpopRefreshBound && tokens2.refresh_token === this.data.tokens?.refresh_token);
|
|
4848
|
+
this.data.grantIssuer = tokens2.issuer ?? this.data.grantIssuer;
|
|
4849
|
+
this.data.tokens = { ...tokens2, ...scope !== void 0 ? { scope } : {} };
|
|
4337
4850
|
this.save();
|
|
4338
4851
|
}
|
|
4852
|
+
async dpop() {
|
|
4853
|
+
const bound = this.data.tokens?.token_type?.toLowerCase() === "dpop" || !!this.data.tokens?.refresh_token && !!this.data.dpopRefreshBound;
|
|
4854
|
+
if (!this.options.oauthDpop && !bound) return void 0;
|
|
4855
|
+
this.assertCurrent();
|
|
4856
|
+
if (!this.options.oauthDpop) {
|
|
4857
|
+
if (bound) throw failure("oauth_dpop_unavailable", { operation: "auth" });
|
|
4858
|
+
return void 0;
|
|
4859
|
+
}
|
|
4860
|
+
const issuer = this.discovery?.authorizationServerMetadata?.issuer ?? this.discovery?.authorizationServerUrl ?? this.data.tokens?.issuer;
|
|
4861
|
+
const client = issuer ? this.lookupClientInformation({ issuer }) : void 0;
|
|
4862
|
+
if (!issuer || !client) {
|
|
4863
|
+
if (bound) throw failure("oauth_dpop_unavailable", { operation: "auth" });
|
|
4864
|
+
return void 0;
|
|
4865
|
+
}
|
|
4866
|
+
if (!this.data.dpopKey && !this.redirect) {
|
|
4867
|
+
if (bound) throw failure("oauth_dpop_unavailable", { operation: "auth" });
|
|
4868
|
+
return void 0;
|
|
4869
|
+
}
|
|
4870
|
+
if (this.data.dpopKey && (this.data.dpopKey.issuer !== issuer || this.data.dpopKey.clientId !== client.client_id))
|
|
4871
|
+
throw failure("oauth_dpop_unavailable", { operation: "auth" });
|
|
4872
|
+
if (this.resolvedDpopSession) return this.resolvedDpopSession;
|
|
4873
|
+
this.dpopSession ??= (async () => {
|
|
4874
|
+
if (!this.data.dpopKey) {
|
|
4875
|
+
const key = await createDpopKey(issuer, client.client_id);
|
|
4876
|
+
this.assertCurrent();
|
|
4877
|
+
this.data.dpopKey = key;
|
|
4878
|
+
this.save();
|
|
4879
|
+
}
|
|
4880
|
+
try {
|
|
4881
|
+
return await restoreDpopSession(this.data.dpopKey);
|
|
4882
|
+
} catch {
|
|
4883
|
+
throw failure("oauth_dpop_unavailable", { operation: "auth" });
|
|
4884
|
+
}
|
|
4885
|
+
})();
|
|
4886
|
+
const session = await this.dpopSession;
|
|
4887
|
+
this.assertCurrent();
|
|
4888
|
+
this.resolvedDpopSession = session;
|
|
4889
|
+
return session;
|
|
4890
|
+
}
|
|
4339
4891
|
saveCodeVerifier(value) {
|
|
4340
4892
|
this.verifier = value;
|
|
4341
4893
|
}
|
|
@@ -4354,12 +4906,17 @@ var OAuthProvider = class {
|
|
|
4354
4906
|
throw failure("authentication_required", { operation: "connect", oauth: true });
|
|
4355
4907
|
if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
|
|
4356
4908
|
throw new Error("Refusing an insecure authorization URL.");
|
|
4909
|
+
await this.dpop();
|
|
4910
|
+
this.requestedScope = url.searchParams.get("scope") ?? void 0;
|
|
4357
4911
|
await this.redirect(url);
|
|
4358
4912
|
}
|
|
4359
4913
|
invalidateCredentials(scope) {
|
|
4360
4914
|
if (scope === "all" || scope === "client") {
|
|
4361
4915
|
this.data.clients = {};
|
|
4362
4916
|
delete this.data.registrations;
|
|
4917
|
+
delete this.data.dpopKey;
|
|
4918
|
+
this.dpopSession = void 0;
|
|
4919
|
+
this.resolvedDpopSession = void 0;
|
|
4363
4920
|
}
|
|
4364
4921
|
if (scope === "all" || scope === "tokens") delete this.data.tokens;
|
|
4365
4922
|
if (scope === "all" || scope === "verifier") this.verifier = void 0;
|
|
@@ -4401,6 +4958,14 @@ async function connectionAuthProvider(server, config, storeFactory = credentialS
|
|
|
4401
4958
|
throw error;
|
|
4402
4959
|
}
|
|
4403
4960
|
},
|
|
4961
|
+
dpop: async () => {
|
|
4962
|
+
try {
|
|
4963
|
+
return await (await get()).dpop();
|
|
4964
|
+
} catch (error) {
|
|
4965
|
+
if (!initialized && error instanceof DiagnosticError && error.diagnostic.code === "credential_store_unavailable") return void 0;
|
|
4966
|
+
throw error;
|
|
4967
|
+
}
|
|
4968
|
+
},
|
|
4404
4969
|
saveTokens: async (tokens2) => (await get()).saveTokens(tokens2),
|
|
4405
4970
|
saveCodeVerifier: async (value) => (await get()).saveCodeVerifier(value),
|
|
4406
4971
|
codeVerifier: async () => (await get()).codeVerifier(),
|
|
@@ -4481,9 +5046,36 @@ async function authenticate(identity, open2, signal, store, options = {}) {
|
|
|
4481
5046
|
const callback = new Promise((resolve3) => {
|
|
4482
5047
|
resolveCallback = resolve3;
|
|
4483
5048
|
});
|
|
5049
|
+
const backing = store ?? await credentialStore(identity);
|
|
5050
|
+
const original = backing.read();
|
|
5051
|
+
const epoch = credentialEpochs.get(credentialKey(identity)) ?? 0;
|
|
5052
|
+
let staged = original;
|
|
5053
|
+
const guard = () => {
|
|
5054
|
+
deadline.throwIfAborted();
|
|
5055
|
+
if ((credentialEpochs.get(credentialKey(identity)) ?? 0) !== epoch || backing.read() !== original)
|
|
5056
|
+
throw failure("authentication_required", { operation: "auth" });
|
|
5057
|
+
};
|
|
5058
|
+
const pending = {
|
|
5059
|
+
read: () => {
|
|
5060
|
+
guard();
|
|
5061
|
+
return staged;
|
|
5062
|
+
},
|
|
5063
|
+
write: (value) => {
|
|
5064
|
+
guard();
|
|
5065
|
+
staged = value;
|
|
5066
|
+
},
|
|
5067
|
+
remove: () => {
|
|
5068
|
+
guard();
|
|
5069
|
+
staged = null;
|
|
5070
|
+
}
|
|
5071
|
+
};
|
|
5072
|
+
const commit = () => {
|
|
5073
|
+
guard();
|
|
5074
|
+
if (staged !== null) backing.write(staged);
|
|
5075
|
+
};
|
|
4484
5076
|
const provider = new OAuthProvider(
|
|
4485
5077
|
identity,
|
|
4486
|
-
|
|
5078
|
+
pending,
|
|
4487
5079
|
async (target) => {
|
|
4488
5080
|
deadline.throwIfAborted();
|
|
4489
5081
|
if (!options.handoff) return open2(target.href);
|
|
@@ -4513,7 +5105,7 @@ async function authenticate(identity, open2, signal, store, options = {}) {
|
|
|
4513
5105
|
res.end(oauthCallbackHtml(params.has("error") ? "denied" : "received"));
|
|
4514
5106
|
resolveCallback(params);
|
|
4515
5107
|
});
|
|
4516
|
-
const fetchFn = (input, init) =>
|
|
5108
|
+
const fetchFn = (input, init) => privateOAuthFetch(input, {
|
|
4517
5109
|
...init,
|
|
4518
5110
|
signal: AbortSignal.any([deadline, ...init?.signal ? [init.signal] : []])
|
|
4519
5111
|
});
|
|
@@ -4530,7 +5122,10 @@ async function authenticate(identity, open2, signal, store, options = {}) {
|
|
|
4530
5122
|
forceReauthorization: true,
|
|
4531
5123
|
scope: options.oauthScopes?.join(" ")
|
|
4532
5124
|
});
|
|
4533
|
-
if (result === "AUTHORIZED")
|
|
5125
|
+
if (result === "AUTHORIZED") {
|
|
5126
|
+
commit();
|
|
5127
|
+
return;
|
|
5128
|
+
}
|
|
4534
5129
|
const params = await waitFor(callback, deadline);
|
|
4535
5130
|
if (params.has("error") || !params.get("code"))
|
|
4536
5131
|
throw new Error("OAuth authorization was not granted.");
|
|
@@ -4541,6 +5136,7 @@ async function authenticate(identity, open2, signal, store, options = {}) {
|
|
|
4541
5136
|
});
|
|
4542
5137
|
try {
|
|
4543
5138
|
await transport.finishAuth(params);
|
|
5139
|
+
commit();
|
|
4544
5140
|
} finally {
|
|
4545
5141
|
await transport.close();
|
|
4546
5142
|
}
|
|
@@ -4571,9 +5167,9 @@ function serverMatrix(servers, loaded, width = 80, theme) {
|
|
|
4571
5167
|
const fitted = truncateToWidth2(text, width, "\u2026");
|
|
4572
5168
|
return theme ? fitted : plain(fitted);
|
|
4573
5169
|
};
|
|
4574
|
-
const
|
|
5170
|
+
const wrap2 = (text) => wrapTextWithAnsi(text, width).map(fit);
|
|
4575
5171
|
const rows = [];
|
|
4576
|
-
if (!servers.length) return
|
|
5172
|
+
if (!servers.length) return wrap2(fg("muted", "No MCP servers configured.")).join("\n");
|
|
4577
5173
|
const toolsWidth = Math.max(5, ...servers.map((server) => String(server.catalogSize ?? "\u2014").length));
|
|
4578
5174
|
const loadedWidth = Math.max(6, ...servers.map((server) => String(loaded.get(server.name) ?? 0).length));
|
|
4579
5175
|
const fixedWidth = 18 + toolsWidth + loadedWidth;
|
|
@@ -4597,17 +5193,17 @@ function serverMatrix(servers, loaded, width = 80, theme) {
|
|
|
4597
5193
|
const name = fg(server.state === "disabled" ? "dim" : "text", bold(compact ? line(server.name) : padName(server.name)));
|
|
4598
5194
|
const glyph = fg(state.color, state.glyph);
|
|
4599
5195
|
if (compact) {
|
|
4600
|
-
rows.push(...
|
|
4601
|
-
rows.push(...
|
|
5196
|
+
rows.push(...wrap2(`${glyph} ${name}`));
|
|
5197
|
+
rows.push(...wrap2(` ${fg(state.color, state.label)} \xB7 ${fg("muted", `${tools} tools \xB7 ${count} loaded`)}`));
|
|
4602
5198
|
} else {
|
|
4603
5199
|
rows.push(`${glyph} ${name} ${fg(state.color, state.label.padEnd(10))} ` + fg(server.catalogSize === void 0 ? "dim" : "text", tools.padStart(toolsWidth)) + " " + fg(count ? "accent" : "dim", String(count).padStart(loadedWidth)));
|
|
4604
5200
|
}
|
|
4605
5201
|
}
|
|
4606
|
-
rows.push("", ...
|
|
5202
|
+
rows.push("", ...wrap2(fg("dim", "Connections open on demand. \u2014 = catalog not fetched.")));
|
|
4607
5203
|
const errors = servers.filter((server) => server.state === "failed" && server.error);
|
|
4608
5204
|
if (errors.length) rows.push("");
|
|
4609
5205
|
for (const server of errors) {
|
|
4610
|
-
rows.push(...
|
|
5206
|
+
rows.push(...wrap2(fg("error", `\u2718\uFE0E ${line(server.name)}: [${line(server.error.code)}]`) + fg("muted", ` ${line(server.error.message)}`)));
|
|
4611
5207
|
}
|
|
4612
5208
|
return rows.map(fit).join("\n");
|
|
4613
5209
|
}
|
|
@@ -4694,7 +5290,8 @@ function inspectServer(name, config, status, authentication) {
|
|
|
4694
5290
|
`OAuth: ${usesOAuth(config) ? "automatic" : "not used"}`,
|
|
4695
5291
|
...usesOAuth(config) ? [
|
|
4696
5292
|
`Requested scopes: ${config.oauthScopes?.map(line).join(", ") ?? "SDK/server defaults"}`,
|
|
4697
|
-
`OAuth callback: http://127.0.0.1:${config.oauthCallbackPort ?? 19847}/callback
|
|
5293
|
+
`OAuth callback: http://127.0.0.1:${config.oauthCallbackPort ?? 19847}/callback`,
|
|
5294
|
+
`DPoP: ${config.oauthDpop ? "enabled (ES256; token binding depends on the service)" : "disabled"}`
|
|
4698
5295
|
] : [],
|
|
4699
5296
|
...authentication ? [`Authentication: ${authentication}`] : [],
|
|
4700
5297
|
`Timeout: ${config.timeoutMs ? `${config.timeoutMs} ms` : "default"}`,
|
|
@@ -4713,6 +5310,111 @@ function inspectServer(name, config, status, authentication) {
|
|
|
4713
5310
|
].join("\n");
|
|
4714
5311
|
}
|
|
4715
5312
|
|
|
5313
|
+
// src/authorization.ts
|
|
5314
|
+
import { computeScopeUnion } from "@modelcontextprotocol/client";
|
|
5315
|
+
function parseScopes(value) {
|
|
5316
|
+
if (!value || value.length > 25699) return void 0;
|
|
5317
|
+
const scopes = [...new Set(value.split(" "))];
|
|
5318
|
+
if (scopes.length > 100 || scopes.some((scope) => scope.length > 256 || !/^[\x21\x23-\x5b\x5d-\x7e]+$/u.test(scope))) return void 0;
|
|
5319
|
+
return scopes;
|
|
5320
|
+
}
|
|
5321
|
+
function authorizationOptions(identity, store, options, required) {
|
|
5322
|
+
const granted = new OAuthProvider(identity, store).tokens()?.scope;
|
|
5323
|
+
const scope = computeScopeUnion(options.oauthScopes?.join(" "), granted, required.join(" "));
|
|
5324
|
+
const scopes = parseScopes(scope);
|
|
5325
|
+
if (!scopes) throw failure("oauth_scope_rejected", { operation: "auth" });
|
|
5326
|
+
return { options: { ...options, oauthScopes: scopes }, grantedScopesKnown: granted !== void 0 };
|
|
5327
|
+
}
|
|
5328
|
+
function loginSummary(options, reviewed) {
|
|
5329
|
+
const scopes = options.oauthScopes;
|
|
5330
|
+
const names = scopes?.join(", ");
|
|
5331
|
+
const summary = scopes && (reviewed || names.length > 80) ? `${scopes.length} ${reviewed ? "reviewed " : "configured "}scopes` : names ?? "SDK/server defaults";
|
|
5332
|
+
return `Requested scopes: ${summary}
|
|
5333
|
+
Callback: http://127.0.0.1:${options.oauthCallbackPort ?? 19847}/callback`;
|
|
5334
|
+
}
|
|
5335
|
+
|
|
5336
|
+
// src/authorization-review.ts
|
|
5337
|
+
import { Text as Text3, truncateToWidth as truncateToWidth3 } from "@earendil-works/pi-tui";
|
|
5338
|
+
async function reviewAuthorization(ctx, server, requested, combined, grantedScopesKnown, signal) {
|
|
5339
|
+
const warning = grantedScopesKnown ? "" : "Previously granted scopes are unknown and may not be retained. Configure oauthScopes with the permissions you need, or continue with the listed scopes.\n\n";
|
|
5340
|
+
const text = `${warning}Server-requested scopes (untrusted data):
|
|
5341
|
+
${requested.join("\n")}
|
|
5342
|
+
|
|
5343
|
+
Login scopes (configured + known grants + requested):
|
|
5344
|
+
${combined.join("\n")}
|
|
5345
|
+
|
|
5346
|
+
Approval opens sign-in. The rejected operation is never replayed.`;
|
|
5347
|
+
if (signal.aborted) return false;
|
|
5348
|
+
if (ctx.mode !== "tui") {
|
|
5349
|
+
const lines = new Text3(text, 0, 0).render(70);
|
|
5350
|
+
let page = 0;
|
|
5351
|
+
const pages = Math.ceil(lines.length / 8);
|
|
5352
|
+
while (!signal.aborted) {
|
|
5353
|
+
const choice = await chooseOption(
|
|
5354
|
+
ctx,
|
|
5355
|
+
`Permissions for ${server} \xB7 ${page + 1}/${pages}
|
|
5356
|
+
${lines.slice(page * 8, page * 8 + 8).join("\n")}`,
|
|
5357
|
+
["Cancel", ...page + 1 < pages ? ["Next page"] : ["Approve sign-in"], ...page ? ["Previous page"] : []],
|
|
5358
|
+
signal
|
|
5359
|
+
);
|
|
5360
|
+
if (choice === "Next page") page++;
|
|
5361
|
+
else if (choice === "Previous page") page--;
|
|
5362
|
+
else return choice === "Approve sign-in" && !signal.aborted;
|
|
5363
|
+
}
|
|
5364
|
+
return false;
|
|
5365
|
+
}
|
|
5366
|
+
return ctx.ui.custom((tui, theme, keys, done) => {
|
|
5367
|
+
let settled = false;
|
|
5368
|
+
const finish = (approved) => {
|
|
5369
|
+
if (settled) return;
|
|
5370
|
+
settled = true;
|
|
5371
|
+
signal.removeEventListener("abort", abort);
|
|
5372
|
+
done(approved);
|
|
5373
|
+
};
|
|
5374
|
+
const abort = () => finish(false);
|
|
5375
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
5376
|
+
const component = authorizationReview(server, text, () => tui.terminal.rows, theme, keys, finish, () => tui.requestRender());
|
|
5377
|
+
return { ...component, dispose: () => signal.removeEventListener("abort", abort) };
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
function authorizationReview(server, text, rows, theme, keys, done, render) {
|
|
5381
|
+
let offset = 0, size = 1, width = 0, height = 0;
|
|
5382
|
+
let lines = [], last2 = false;
|
|
5383
|
+
return {
|
|
5384
|
+
render(columns) {
|
|
5385
|
+
if (columns < 24 || rows() < 14) {
|
|
5386
|
+
last2 = false;
|
|
5387
|
+
return [truncateToWidth3("Enlarge terminal; Esc cancels", columns)];
|
|
5388
|
+
}
|
|
5389
|
+
const available = Math.max(1, rows() - 8);
|
|
5390
|
+
if (columns !== width || available !== height) {
|
|
5391
|
+
width = columns;
|
|
5392
|
+
height = available;
|
|
5393
|
+
offset = 0;
|
|
5394
|
+
lines = new Text3(text, 0, 0).render(Math.max(1, columns));
|
|
5395
|
+
}
|
|
5396
|
+
size = Math.max(1, available - 4);
|
|
5397
|
+
last2 = offset + size >= lines.length;
|
|
5398
|
+
return [
|
|
5399
|
+
truncateToWidth3(theme.fg("accent", `Permissions: ${server}`), columns),
|
|
5400
|
+
...lines.slice(offset, offset + size),
|
|
5401
|
+
truncateToWidth3(`${offset + 1}\u2013${Math.min(offset + size, lines.length)}/${lines.length}`, columns),
|
|
5402
|
+
truncateToWidth3(last2 ? "a: approve \xB7 Esc: cancel" : "Enter/\u2193: next", columns),
|
|
5403
|
+
truncateToWidth3("\u2191: previous \xB7 Esc cancel", columns)
|
|
5404
|
+
];
|
|
5405
|
+
},
|
|
5406
|
+
handleInput(data) {
|
|
5407
|
+
if (keys.matches(data, "tui.select.cancel")) done(false);
|
|
5408
|
+
else if (data === "a" && last2) done(true);
|
|
5409
|
+
else if (keys.matches(data, "tui.select.up")) offset = Math.max(0, offset - size);
|
|
5410
|
+
else if (!last2 && (keys.matches(data, "tui.select.confirm") || keys.matches(data, "tui.select.down"))) offset += size;
|
|
5411
|
+
render();
|
|
5412
|
+
},
|
|
5413
|
+
invalidate() {
|
|
5414
|
+
}
|
|
5415
|
+
};
|
|
5416
|
+
}
|
|
5417
|
+
|
|
4716
5418
|
// src/runtime.ts
|
|
4717
5419
|
import { mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
4718
5420
|
import { join as join2 } from "node:path";
|
|
@@ -4724,13 +5426,15 @@ import {
|
|
|
4724
5426
|
SdkError as SdkError2,
|
|
4725
5427
|
SdkErrorCode,
|
|
4726
5428
|
StreamableHTTPClientTransport,
|
|
5429
|
+
extractWWWAuthenticateParams,
|
|
5430
|
+
computeScopeUnion as computeScopeUnion2,
|
|
4727
5431
|
UriTemplate as UriTemplate2
|
|
4728
5432
|
} from "@modelcontextprotocol/client";
|
|
4729
5433
|
|
|
4730
5434
|
// package.json
|
|
4731
5435
|
var package_default = {
|
|
4732
5436
|
name: "pi-mcp-client",
|
|
4733
|
-
version: "0.
|
|
5437
|
+
version: "0.10.0",
|
|
4734
5438
|
description: "MCP tools for Pi, discovered on demand and called natively through the official SDK.",
|
|
4735
5439
|
type: "module",
|
|
4736
5440
|
license: "MIT",
|
|
@@ -4769,8 +5473,9 @@ var package_default = {
|
|
|
4769
5473
|
prepack: "bun run build"
|
|
4770
5474
|
},
|
|
4771
5475
|
dependencies: {
|
|
4772
|
-
"@modelcontextprotocol/client": "2.
|
|
5476
|
+
"@modelcontextprotocol/client": "2.1.0",
|
|
4773
5477
|
"@napi-rs/keyring": "^1.3.0",
|
|
5478
|
+
jose: "^6.1.3",
|
|
4774
5479
|
minisearch: "^7.2.0",
|
|
4775
5480
|
"smol-toml": "^1.8.0"
|
|
4776
5481
|
},
|
|
@@ -4778,7 +5483,7 @@ var package_default = {
|
|
|
4778
5483
|
"@earendil-works/pi-ai": "^0.85.1",
|
|
4779
5484
|
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
4780
5485
|
"@earendil-works/pi-tui": "^0.85.1",
|
|
4781
|
-
"@modelcontextprotocol/server": "2.
|
|
5486
|
+
"@modelcontextprotocol/server": "2.1.0",
|
|
4782
5487
|
"@types/bun": "^1.3.0",
|
|
4783
5488
|
"@types/node": "^24.0.0",
|
|
4784
5489
|
esbuild: "^0.25.0",
|
|
@@ -4901,21 +5606,21 @@ var MAX_CHOICES = 100;
|
|
|
4901
5606
|
var MAX_TEXT = 8192;
|
|
4902
5607
|
var MAX_CONTENT = 64 * 1024;
|
|
4903
5608
|
var MAX_REQUIRED = 3;
|
|
4904
|
-
var
|
|
5609
|
+
var invalid3 = (message3) => new ProtocolError2(ProtocolErrorCode2.InvalidParams, message3);
|
|
4905
5610
|
var short = (value, limit) => typeof value === "string" ? line(value).slice(0, limit) : "";
|
|
4906
5611
|
var number = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4907
|
-
function
|
|
5612
|
+
function message2(value) {
|
|
4908
5613
|
const text = plain(value).split("\n").map((row) => row.trimEnd()).slice(0, 20).join("\n").trim();
|
|
4909
5614
|
return text.slice(0, 2e3) || "(No message supplied.)";
|
|
4910
5615
|
}
|
|
4911
5616
|
function choices(values) {
|
|
4912
5617
|
if (!values.length || values.length > MAX_CHOICES || new Set(values.map((choice) => choice.value)).size !== values.length)
|
|
4913
|
-
throw
|
|
5618
|
+
throw invalid3("Elicitation choices are empty, duplicated, or too many.");
|
|
4914
5619
|
return values.map(({ value, label: label2 }) => ({ value, label: short(label2, 120) || short(value, 120) || JSON.stringify(value) }));
|
|
4915
5620
|
}
|
|
4916
5621
|
function formFields(schema) {
|
|
4917
5622
|
const entries = Object.entries(schema.properties ?? {});
|
|
4918
|
-
if (entries.length > MAX_FIELDS) throw
|
|
5623
|
+
if (entries.length > MAX_FIELDS) throw invalid3("Elicitation forms are limited to 50 fields.");
|
|
4919
5624
|
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
4920
5625
|
return entries.map(([name, value]) => {
|
|
4921
5626
|
const property = value;
|
|
@@ -5071,7 +5776,7 @@ async function elicitForm(ui, server, params, signal) {
|
|
|
5071
5776
|
for (const field of fields)
|
|
5072
5777
|
if (field.default !== void 0 && !problem(field, field.default)) values.set(field.name, field.default);
|
|
5073
5778
|
const title = `${server} requests information
|
|
5074
|
-
${
|
|
5779
|
+
${message2(params.message)}
|
|
5075
5780
|
|
|
5076
5781
|
Review the values below. Only what you submit is sent to ${server}. Never enter passwords, API keys, or payment details here.`;
|
|
5077
5782
|
while (!signal.aborted) {
|
|
@@ -5112,13 +5817,13 @@ async function elicitUrl(ui, server, params, signal) {
|
|
|
5112
5817
|
const target = safeUrl(params.url);
|
|
5113
5818
|
if (!target) {
|
|
5114
5819
|
ui.notify(`${server} asked to open an unsupported URL, so the request was refused. Only HTTPS pages, or HTTP pages on this computer, without embedded credentials can be opened.`, "warning");
|
|
5115
|
-
throw
|
|
5820
|
+
throw invalid3("Unsupported elicitation URL.");
|
|
5116
5821
|
}
|
|
5117
5822
|
let failed = false;
|
|
5118
5823
|
while (!signal.aborted) {
|
|
5119
5824
|
const choice = await ui.select(
|
|
5120
5825
|
`${server} asks you to open a web page
|
|
5121
|
-
${
|
|
5826
|
+
${message2(params.message)}
|
|
5122
5827
|
|
|
5123
5828
|
Site: ${target.host}
|
|
5124
5829
|
URL: ${target.href}
|
|
@@ -5156,7 +5861,7 @@ function requiredElicitations(error) {
|
|
|
5156
5861
|
if (!Array.isArray(list) || !list.length || list.length > MAX_REQUIRED) return;
|
|
5157
5862
|
const valid = list.every((item) => typeof item === "object" && item !== null && item.mode === "url" && typeof item.message === "string" && typeof item.url === "string" && typeof item.elicitationId === "string" && item.elicitationId.length > 0 && item.elicitationId.length <= 256);
|
|
5158
5863
|
if (!valid || new Set(list.map((item) => item.elicitationId)).size !== list.length) return;
|
|
5159
|
-
return list.map(({ mode, message:
|
|
5864
|
+
return list.map(({ mode, message: message3, url, elicitationId }) => ({ mode, message: message3, url, elicitationId }));
|
|
5160
5865
|
}
|
|
5161
5866
|
|
|
5162
5867
|
// src/runtime.ts
|
|
@@ -5252,6 +5957,8 @@ var createSdkConnector = (storeFactory = credentialStore) => async (name, config
|
|
|
5252
5957
|
}) : new StreamableHTTPClientTransport(new URL(config.url), {
|
|
5253
5958
|
requestInit: { headers: config.headers },
|
|
5254
5959
|
authProvider: await connectionAuthProvider(name, config, storeFactory),
|
|
5960
|
+
// Approval and resubmission belong to the user, not transport retries.
|
|
5961
|
+
onInsufficientScope: "throw",
|
|
5255
5962
|
// Bound HTTP responses (including OAuth), but not established SSE streams.
|
|
5256
5963
|
// Accommodate startup/tool overrides; the SDK enforces each request's tighter deadline.
|
|
5257
5964
|
fetch: async (input, init) => {
|
|
@@ -5262,7 +5969,7 @@ var createSdkConnector = (storeFactory = credentialStore) => async (name, config
|
|
|
5262
5969
|
);
|
|
5263
5970
|
timer.unref();
|
|
5264
5971
|
try {
|
|
5265
|
-
const response = await
|
|
5972
|
+
const response = await privateOAuthFetch(input, {
|
|
5266
5973
|
...init,
|
|
5267
5974
|
signal: AbortSignal.any([
|
|
5268
5975
|
signal,
|
|
@@ -5270,6 +5977,10 @@ var createSdkConnector = (storeFactory = credentialStore) => async (name, config
|
|
|
5270
5977
|
...init?.signal ? [init.signal] : []
|
|
5271
5978
|
])
|
|
5272
5979
|
});
|
|
5980
|
+
if (response.status === 403 && usesOAuth(config)) {
|
|
5981
|
+
const challenge = extractWWWAuthenticateParams(response);
|
|
5982
|
+
if (challenge.error === "insufficient_scope") handlers?.scopeChallenge?.(challenge.scope);
|
|
5983
|
+
}
|
|
5273
5984
|
if (response.headers.get("content-type")?.split(";")[0].trim() === "text/event-stream")
|
|
5274
5985
|
clearTimeout(timer);
|
|
5275
5986
|
return response;
|
|
@@ -5308,8 +6019,21 @@ var McpRuntime = class {
|
|
|
5308
6019
|
this.options = options;
|
|
5309
6020
|
}
|
|
5310
6021
|
states = /* @__PURE__ */ new Map();
|
|
6022
|
+
scopeChallenges = /* @__PURE__ */ new Map();
|
|
5311
6023
|
lifetime = new AbortController();
|
|
5312
6024
|
closing;
|
|
6025
|
+
/** Challenges are session-local, expire, and never survive configuration changes. */
|
|
6026
|
+
authorizationChallenge(name) {
|
|
6027
|
+
const challenge = this.scopeChallenges.get(name);
|
|
6028
|
+
if (!challenge || challenge.expires < Date.now() || challenge.identity !== this.identity(name)) {
|
|
6029
|
+
this.scopeChallenges.delete(name);
|
|
6030
|
+
return void 0;
|
|
6031
|
+
}
|
|
6032
|
+
return challenge;
|
|
6033
|
+
}
|
|
6034
|
+
clearAuthorizationChallenge(name, approved) {
|
|
6035
|
+
if (this.scopeChallenges.get(name) === approved) this.scopeChallenges.delete(name);
|
|
6036
|
+
}
|
|
5313
6037
|
onResourceUpdated;
|
|
5314
6038
|
subscriptionGeneration = 0;
|
|
5315
6039
|
subscriptionCleanup = Promise.resolve();
|
|
@@ -5374,6 +6098,14 @@ var McpRuntime = class {
|
|
|
5374
6098
|
),
|
|
5375
6099
|
this.lifetime.signal,
|
|
5376
6100
|
{
|
|
6101
|
+
scopeChallenge: (scope) => {
|
|
6102
|
+
if (this.closing || this.lifetime.signal.aborted || state.connectionToken !== token) return;
|
|
6103
|
+
const scopes = parseScopes(scope);
|
|
6104
|
+
const previous = this.authorizationChallenge(name);
|
|
6105
|
+
const union = scopes && parseScopes(computeScopeUnion2(previous?.scopes.join(" "), scopes.join(" ")));
|
|
6106
|
+
if (union) this.scopeChallenges.set(name, { scopes: union, identity, expires: Date.now() + 6e5 });
|
|
6107
|
+
else this.scopeChallenges.delete(name);
|
|
6108
|
+
},
|
|
5377
6109
|
toolsChanged: () => {
|
|
5378
6110
|
if (this.closing || this.lifetime.signal.aborted || state.connectionToken !== token)
|
|
5379
6111
|
return;
|
|
@@ -5950,6 +6682,7 @@ var McpRuntime = class {
|
|
|
5950
6682
|
throw failure("busy", { server: name, operation: "auth" });
|
|
5951
6683
|
}
|
|
5952
6684
|
await Promise.all(names.map(async (name) => {
|
|
6685
|
+
this.scopeChallenges.delete(name);
|
|
5953
6686
|
const state = this.states.get(name);
|
|
5954
6687
|
if (!state) return;
|
|
5955
6688
|
state.connectionToken = void 0;
|
|
@@ -6005,6 +6738,7 @@ var McpRuntime = class {
|
|
|
6005
6738
|
return this.closing ??= this.shutdown();
|
|
6006
6739
|
}
|
|
6007
6740
|
async shutdown() {
|
|
6741
|
+
this.scopeChallenges.clear();
|
|
6008
6742
|
await this.clearResourceSubscriptions();
|
|
6009
6743
|
await Promise.all(
|
|
6010
6744
|
[...this.states.values()].map(
|
|
@@ -6250,7 +6984,7 @@ Full MCP result: ${path}`;
|
|
|
6250
6984
|
|
|
6251
6985
|
// src/render.ts
|
|
6252
6986
|
import { keyText } from "@earendil-works/pi-coding-agent";
|
|
6253
|
-
import { Text as
|
|
6987
|
+
import { Text as Text4, truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
|
|
6254
6988
|
|
|
6255
6989
|
// src/format.ts
|
|
6256
6990
|
import {
|
|
@@ -6361,10 +7095,10 @@ function renderCall(title, args, theme, expanded) {
|
|
|
6361
7095
|
if (width <= 0) return [];
|
|
6362
7096
|
const text = theme.fg("toolTitle", theme.bold(line(title))) + (preview ? theme.fg("dim", ` ${preview}`) : "");
|
|
6363
7097
|
if (expanded)
|
|
6364
|
-
return new
|
|
7098
|
+
return new Text4(text, 0, 0).render(width).map((row) => truncateToWidth4(row, width));
|
|
6365
7099
|
const key = keyText("app.tools.expand");
|
|
6366
7100
|
return [
|
|
6367
|
-
|
|
7101
|
+
truncateToWidth4(
|
|
6368
7102
|
text + (key ? theme.fg("muted", ` (${key} to expand)`) : ""),
|
|
6369
7103
|
width
|
|
6370
7104
|
)
|
|
@@ -6399,30 +7133,30 @@ function renderResult(result, options, theme, isError) {
|
|
|
6399
7133
|
const lines = rows.flatMap((row) => {
|
|
6400
7134
|
const status = states[row.state] ?? states.failed;
|
|
6401
7135
|
const value = theme.fg(status.color, status.glyph) + " " + theme.fg(status === states.failed ? "error" : "accent", line(row.label)) + (row.inlineDescription ? theme.fg("dim", ` ${line(row.inlineDescription)}`) : "") + (row.inlineAction ? theme.bold(theme.fg("dim", ` ${line(row.inlineAction)}`)) : "");
|
|
6402
|
-
const rendered = options.expanded && (!details?.searchNotes || row.state === "failed") ? new
|
|
7136
|
+
const rendered = options.expanded && (!details?.searchNotes || row.state === "failed") ? new Text4(value, 0, 0).render(width).map((x) => truncateToWidth4(x, width)) : [truncateToWidth4(value, width)];
|
|
6403
7137
|
if (options.expanded && row.description)
|
|
6404
|
-
rendered.push(
|
|
7138
|
+
rendered.push(truncateToWidth4(
|
|
6405
7139
|
theme.fg("dim", ` ${line(row.description)}`),
|
|
6406
7140
|
width
|
|
6407
7141
|
));
|
|
6408
7142
|
return rendered;
|
|
6409
7143
|
});
|
|
6410
7144
|
for (const note of details?.searchNotes ?? [])
|
|
6411
|
-
lines.push(...(options.expanded ? new
|
|
7145
|
+
lines.push(...(options.expanded ? new Text4(theme.fg("warning", plain(note)), 0, 0).render(width) : [theme.fg("warning", line(note))]).map((row) => truncateToWidth4(row, width)));
|
|
6412
7146
|
if (options.expanded && !options.isPartial && text && !details?.searchNotes && !bodyInRow)
|
|
6413
7147
|
lines.push(
|
|
6414
|
-
...new
|
|
7148
|
+
...new Text4(
|
|
6415
7149
|
formattedOutput ??= (() => {
|
|
6416
7150
|
const output = validation ? formatValidation(body, theme) : formatOutput(body, details?.displayBlocks, theme);
|
|
6417
7151
|
return isError || details?.failed ? theme.fg("error", output) : output;
|
|
6418
7152
|
})(),
|
|
6419
7153
|
0,
|
|
6420
7154
|
0
|
|
6421
|
-
).render(width).map((x) =>
|
|
7155
|
+
).render(width).map((x) => truncateToWidth4(x, width))
|
|
6422
7156
|
);
|
|
6423
7157
|
if (!options.expanded && details?.fullOutputPath)
|
|
6424
7158
|
lines.push(
|
|
6425
|
-
|
|
7159
|
+
truncateToWidth4(
|
|
6426
7160
|
theme.fg("dim", `Full result: ${details.fullOutputPath}`),
|
|
6427
7161
|
width
|
|
6428
7162
|
)
|
|
@@ -6492,14 +7226,14 @@ var CommandUsageError = class extends Error {
|
|
|
6492
7226
|
};
|
|
6493
7227
|
function errorResult(error, context) {
|
|
6494
7228
|
const value = error instanceof ToolContractError ? diagnostic("tool_changed", context) : diagnose(error, context);
|
|
6495
|
-
const
|
|
6496
|
-
return textResult(
|
|
7229
|
+
const message3 = formatDiagnostic(value) + (context.operation === "call" && !value.code.startsWith("elicitation_") ? " The call was not replayed. The server may already have performed the operation; verify before retrying." : "");
|
|
7230
|
+
return textResult(message3, {
|
|
6497
7231
|
mcpClient: 1,
|
|
6498
7232
|
failed: true,
|
|
6499
7233
|
diagnostics: [value],
|
|
6500
7234
|
rows: [
|
|
6501
7235
|
{
|
|
6502
|
-
label: value.code === "cancelled" ? "Cancelled" :
|
|
7236
|
+
label: value.code === "cancelled" ? "Cancelled" : message3,
|
|
6503
7237
|
state: value.code === "cancelled" ? "cancelled" : "failed"
|
|
6504
7238
|
}
|
|
6505
7239
|
]
|
|
@@ -6521,12 +7255,12 @@ function mcpClient(pi, options = {}) {
|
|
|
6521
7255
|
STATUS_ENTRY,
|
|
6522
7256
|
(entry, _options, theme) => statusPanel(entry.data ?? { servers: [], loaded: [] }, theme)
|
|
6523
7257
|
);
|
|
6524
|
-
pi.registerMessageRenderer("mcp-prompt", (
|
|
6525
|
-
const details = object(
|
|
7258
|
+
pi.registerMessageRenderer("mcp-prompt", (message3, { expanded, outputPad }, theme) => {
|
|
7259
|
+
const details = object(message3.details) ? message3.details : {};
|
|
6526
7260
|
const count = Number(details.count ?? 0);
|
|
6527
7261
|
const label2 = `${line(String(details.server ?? "MCP"))} \xB7 ${line(String(details.name ?? "prompt"))} \xB7 ${count} ${count === 1 ? "message" : "messages"}`;
|
|
6528
|
-
return new
|
|
6529
|
-
${
|
|
7262
|
+
return new Text5(theme.fg("toolTitle", "mcp prompt") + "\n" + theme.fg("success", "\u2714\uFE0E ") + theme.fg("accent", label2) + (expanded && typeof message3.content === "string" ? `
|
|
7263
|
+
${message3.content}` : ""), outputPad, 0);
|
|
6530
7264
|
});
|
|
6531
7265
|
const exposure = new Exposure(pi, registerNative);
|
|
6532
7266
|
const launchBrowser = async (target) => {
|
|
@@ -6537,7 +7271,7 @@ ${message2.content}` : ""), outputPad, 0);
|
|
|
6537
7271
|
const elicitationUi = (ctx) => ({
|
|
6538
7272
|
select: (title, choices2, signal) => chooseOption(ctx, title, choices2, signal),
|
|
6539
7273
|
editor: (title, value) => ctx.ui.editor(title, value),
|
|
6540
|
-
notify: (
|
|
7274
|
+
notify: (message3, type) => ctx.ui.notify(message3, type),
|
|
6541
7275
|
open: launchBrowser
|
|
6542
7276
|
});
|
|
6543
7277
|
const current = () => {
|
|
@@ -6564,12 +7298,12 @@ Text output is limited to 2000 lines or 50 KiB; larger results are saved to a pr
|
|
|
6564
7298
|
renderResult: (result, options2, theme, context) => renderResult(result, options2, theme, context.isError),
|
|
6565
7299
|
async execute(_id, args, signal, onUpdate, ctx) {
|
|
6566
7300
|
const label2 = `${tool.server}.${tool.name}`;
|
|
6567
|
-
const emit = (
|
|
6568
|
-
textResult(
|
|
7301
|
+
const emit = (message3) => onUpdate?.(
|
|
7302
|
+
textResult(message3, {
|
|
6569
7303
|
mcpClient: 1,
|
|
6570
7304
|
rows: [
|
|
6571
7305
|
{
|
|
6572
|
-
label: `${label2} \xB7 ${line(
|
|
7306
|
+
label: `${label2} \xB7 ${line(message3).slice(0, 160)}`,
|
|
6573
7307
|
state: "running"
|
|
6574
7308
|
}
|
|
6575
7309
|
]
|
|
@@ -6684,6 +7418,7 @@ Text output is limited to 2000 lines or 50 KiB; larger results are saved to a pr
|
|
|
6684
7418
|
return false;
|
|
6685
7419
|
}
|
|
6686
7420
|
});
|
|
7421
|
+
loginController?.abort();
|
|
6687
7422
|
promptController?.abort();
|
|
6688
7423
|
if (mutation?.action !== "import") importController?.abort();
|
|
6689
7424
|
const old = runtime;
|
|
@@ -6697,6 +7432,7 @@ Text output is limited to 2000 lines or 50 KiB; larger results are saved to a pr
|
|
|
6697
7432
|
}
|
|
6698
7433
|
pi.on("session_start", async (_event, ctx) => {
|
|
6699
7434
|
const generation = ++sessionGeneration;
|
|
7435
|
+
loginController?.abort();
|
|
6700
7436
|
promptController?.abort();
|
|
6701
7437
|
importController?.abort();
|
|
6702
7438
|
trustController?.abort();
|
|
@@ -6914,7 +7650,7 @@ Variables: ${(candidate.variables ?? []).join(", ") || "none"}. Supply known val
|
|
|
6914
7650
|
state: "failed"
|
|
6915
7651
|
};
|
|
6916
7652
|
}));
|
|
6917
|
-
messages2.push(...discovery.unavailable.map((
|
|
7653
|
+
messages2.push(...discovery.unavailable.map((message3) => `Not searched: ${message3}`), ...discovery.warnings);
|
|
6918
7654
|
messages2.push("No tools activated or resource or prompt content read. Activate selected tools or read selected resources using their exact next-call arguments. Prompt commands are for the user to run, not the assistant.");
|
|
6919
7655
|
} else {
|
|
6920
7656
|
const resolved = resolveTools(discovery.tools, identifiers);
|
|
@@ -7037,8 +7773,8 @@ ${snapshot.body}`,
|
|
|
7037
7773
|
if (action === "subscriptions") {
|
|
7038
7774
|
if (server) throw new CommandUsageError("Use /mcp subscriptions without arguments.");
|
|
7039
7775
|
const watches = current().resourceSubscriptions();
|
|
7040
|
-
const
|
|
7041
|
-
if (ctx.hasUI) ctx.ui.notify(
|
|
7776
|
+
const message3 = watches.length ? watches.map((watch) => `${watch.changed ? "\u21BB" : "\u2714\uFE0E"} ${watch.server} \xB7 ${line(watch.uri).slice(0, 160)}${watch.changed ? " \xB7 changed" : ""}`).join("\n") : "No active resource subscriptions.";
|
|
7777
|
+
if (ctx.hasUI) ctx.ui.notify(message3, "info");
|
|
7042
7778
|
return;
|
|
7043
7779
|
}
|
|
7044
7780
|
if (action === "subscribe" || action === "unsubscribe") {
|
|
@@ -7057,8 +7793,8 @@ ${snapshot.body}`,
|
|
|
7057
7793
|
server = mutation.server;
|
|
7058
7794
|
await reloadConfiguration(ctx, mutation);
|
|
7059
7795
|
const remaining = Object.hasOwn(config, server);
|
|
7060
|
-
const
|
|
7061
|
-
if (ctx.hasUI) ctx.ui.notify(
|
|
7796
|
+
const message3 = mutation.action === "add" ? `\u2714\uFE0E ${server}: saved in ${mutation.scope} configuration. Connections, authentication, and tool discovery remain on demand.` + (mutation.scope === "global" && projectTrusted(ctx) ? " Trusted project definitions take precedence over global definitions." : "") : `\u2714\uFE0E ${server}: removed from ${mutation.scope} configuration. Credentials were retained.` + (remaining ? " A definition from the other scope remains effective; connections reopen on demand." : " Its connection is closed and its tools are no longer active.");
|
|
7797
|
+
if (ctx.hasUI) ctx.ui.notify(message3, "info");
|
|
7062
7798
|
return;
|
|
7063
7799
|
}
|
|
7064
7800
|
if (action === "reload" && !server) {
|
|
@@ -7177,7 +7913,7 @@ ${snapshot.body}`,
|
|
|
7177
7913
|
if (Object.keys(config[server].headers ?? {}).some((name) => name.toLowerCase() === "authorization"))
|
|
7178
7914
|
throw new CommandUsageError("This server uses an Authorization header. Remove it from the server definition before using /mcp login, or keep using header authentication.");
|
|
7179
7915
|
const identity = oauthSettings(server, config[server], ctx.cwd);
|
|
7180
|
-
|
|
7916
|
+
let options2 = config[server];
|
|
7181
7917
|
const loginRuntime = current();
|
|
7182
7918
|
const open2 = async (target) => {
|
|
7183
7919
|
if (!await launchBrowser(target))
|
|
@@ -7192,8 +7928,17 @@ ${snapshot.body}`,
|
|
|
7192
7928
|
const store = await storeFactory(identity);
|
|
7193
7929
|
if (generation !== sessionGeneration || current() !== loginRuntime)
|
|
7194
7930
|
throw failure("cancelled", { server, operation: "auth" });
|
|
7195
|
-
const
|
|
7196
|
-
|
|
7931
|
+
const challenge = loginRuntime.authorizationChallenge(server);
|
|
7932
|
+
if (challenge) {
|
|
7933
|
+
const plan = authorizationOptions(identity, store, options2, challenge.scopes);
|
|
7934
|
+
options2 = { ...options2, ...plan.options };
|
|
7935
|
+
const approved = await reviewAuthorization(ctx, server, challenge.scopes, options2.oauthScopes, plan.grantedScopesKnown, signal);
|
|
7936
|
+
signal.throwIfAborted();
|
|
7937
|
+
if (!approved) throw failure("cancelled", { server, operation: "auth" });
|
|
7938
|
+
if (generation !== sessionGeneration || current() !== loginRuntime || loginRuntime.authorizationChallenge(server) !== challenge)
|
|
7939
|
+
throw failure("cancelled", { server, operation: "auth" });
|
|
7940
|
+
}
|
|
7941
|
+
const summary = loginSummary(options2, !!challenge);
|
|
7197
7942
|
if (extra[0] === "--no-browser") {
|
|
7198
7943
|
await authenticate(identity, open2, signal, store, {
|
|
7199
7944
|
...options2,
|
|
@@ -7236,6 +7981,7 @@ Opening browser. Esc to cancel.`
|
|
|
7236
7981
|
throw authError ?? failure("cancelled", { server, operation: "auth" });
|
|
7237
7982
|
} else await authenticate(identity, open2, signal, store, options2);
|
|
7238
7983
|
if (generation !== sessionGeneration || current() !== loginRuntime) throw failure("cancelled", { server, operation: "auth" });
|
|
7984
|
+
if (challenge) loginRuntime.clearAuthorizationChallenge(server, challenge);
|
|
7239
7985
|
await loginRuntime.reconnect(server);
|
|
7240
7986
|
} finally {
|
|
7241
7987
|
if (loginController === controller) loginController = void 0;
|
|
@@ -7256,7 +8002,7 @@ Opening browser. Esc to cancel.`
|
|
|
7256
8002
|
"info"
|
|
7257
8003
|
);
|
|
7258
8004
|
} catch (error) {
|
|
7259
|
-
const
|
|
8005
|
+
const message3 = error instanceof CommandUsageError || error instanceof ConfigMutationError || error instanceof PromptCommandError ? error.message : formatDiagnostic(
|
|
7260
8006
|
diagnose(error, {
|
|
7261
8007
|
server,
|
|
7262
8008
|
operation: ["reload", "get", "enable", "disable", "add", "remove", "import"].includes(action) ? "configuration" : action === "tools" ? "search" : action === "prompt" ? "prompt" : ["login", "logout"].includes(action) ? "auth" : ["subscribe", "unsubscribe"].includes(action) ? "subscribe" : action === "refresh" ? "refresh" : "reconnect",
|
|
@@ -7264,8 +8010,8 @@ Opening browser. Esc to cancel.`
|
|
|
7264
8010
|
signal: ctx.signal
|
|
7265
8011
|
})
|
|
7266
8012
|
);
|
|
7267
|
-
if (ctx.hasUI) ctx.ui.notify(
|
|
7268
|
-
else throw new Error(
|
|
8013
|
+
if (ctx.hasUI) ctx.ui.notify(message3, "error");
|
|
8014
|
+
else throw new Error(message3);
|
|
7269
8015
|
}
|
|
7270
8016
|
}
|
|
7271
8017
|
});
|