@modelcontextprotocol/inspector 2.10.1 → 2.12.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/README.md +2 -0
- package/clients/cli/build/index.js +9763 -8488
- package/clients/launcher/build/index.js +4 -1
- package/clients/mcpdo/build/{chunk-2E5EZYNU.js → chunk-LBQDT5VV.js} +1070 -161
- package/clients/mcpdo/build/mcp-bin.js +20 -21
- package/clients/mcpdo/build/mcpdod.js +1 -1
- package/clients/tui/build/index.js +1137 -207
- package/clients/web/build/index.js +871 -117
- package/clients/web/dist/assets/index-qtX15-Bz.js +1587 -0
- package/clients/web/dist/assets/{markdown-CZCm46w1.js → markdown-BlYcOtJg.js} +1 -1
- package/clients/web/dist/assets/{prism-light-B0A727v7.js → prism-light-DfUe_Xlh.js} +2 -2
- package/clients/web/dist/index.html +1 -1
- package/package.json +15 -10
- package/scripts/install-clients.mjs +68 -28
- package/clients/web/dist/assets/index-DgDEc2gL.js +0 -1589
|
@@ -177,6 +177,12 @@ var DirectoryReadResultSchema = z.looseObject({
|
|
|
177
177
|
resources: z.array(DirectoryChildSchema),
|
|
178
178
|
nextCursor: z.string().optional()
|
|
179
179
|
});
|
|
180
|
+
var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
|
|
181
|
+
{
|
|
182
|
+
ttlMs: z.int().min(0),
|
|
183
|
+
cacheScope: z.enum(["public", "private"])
|
|
184
|
+
}
|
|
185
|
+
);
|
|
180
186
|
|
|
181
187
|
// ../../core/mcp/sha256.ts
|
|
182
188
|
var K = new Uint32Array([
|
|
@@ -2044,16 +2050,30 @@ function buildConfigFromOptions(options) {
|
|
|
2044
2050
|
const target = options.target ?? [];
|
|
2045
2051
|
const first = target[0];
|
|
2046
2052
|
const rest = target.slice(1);
|
|
2053
|
+
if (first && options.serverUrl) {
|
|
2054
|
+
throw new Error(
|
|
2055
|
+
`Cannot combine a positional target (${first}) with --server-url (${options.serverUrl}). Pass one or the other.`
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2047
2058
|
const urlFromTarget = first && (first.startsWith("http://") || first.startsWith("https://")) ? first : null;
|
|
2048
2059
|
const url = urlFromTarget ?? options.serverUrl ?? null;
|
|
2049
2060
|
if (url) {
|
|
2050
|
-
if (rest.length > 0
|
|
2061
|
+
if (rest.length > 0) {
|
|
2051
2062
|
throw new Error("Arguments cannot be passed to a URL-based MCP server.");
|
|
2052
2063
|
}
|
|
2064
|
+
if (options.transport === "stdio") {
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
`stdio transport cannot be used with a URL (${url}). Use --transport http or --transport sse, or pass a local command instead.`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2053
2069
|
let transportType;
|
|
2054
2070
|
const t = options.transport === "http" ? "streamable-http" : options.transport;
|
|
2055
2071
|
if (t === "sse" || t === "streamable-http") {
|
|
2056
2072
|
transportType = t;
|
|
2073
|
+
} else if (t) {
|
|
2074
|
+
throw new Error(
|
|
2075
|
+
`Invalid transport type: ${t}. Valid types are: sse, http, stdio`
|
|
2076
|
+
);
|
|
2057
2077
|
} else {
|
|
2058
2078
|
const u = new URL(url);
|
|
2059
2079
|
if (u.pathname.endsWith("/mcp")) {
|
|
@@ -4552,7 +4572,10 @@ function parseAuthChallengeFromResponse(response, context) {
|
|
|
4552
4572
|
if (status !== 401 && status !== 403) {
|
|
4553
4573
|
return void 0;
|
|
4554
4574
|
}
|
|
4555
|
-
const wwwAuthenticate = response.headers.get("WWW-Authenticate")
|
|
4575
|
+
const wwwAuthenticate = response.headers.get("WWW-Authenticate") || void 0;
|
|
4576
|
+
if (status === 403 && !wwwAuthenticate) {
|
|
4577
|
+
return void 0;
|
|
4578
|
+
}
|
|
4556
4579
|
const bearer = wwwAuthenticate ? parseWwwAuthenticateBearer(wwwAuthenticate) : {};
|
|
4557
4580
|
const requiredScopes = parseScopeString(bearer.scope);
|
|
4558
4581
|
return {
|
|
@@ -5249,6 +5272,34 @@ function buildRunnerClientAuthOptions(clientConfig, savedSettings, cliOverrides)
|
|
|
5249
5272
|
};
|
|
5250
5273
|
}
|
|
5251
5274
|
|
|
5275
|
+
// ../../core/node/version.ts
|
|
5276
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5277
|
+
import { dirname as dirname5, join as join3, parse } from "path";
|
|
5278
|
+
import { fileURLToPath } from "url";
|
|
5279
|
+
var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
|
5280
|
+
function readInspectorVersion(callerUrl) {
|
|
5281
|
+
const startDir = dirname5(fileURLToPath(callerUrl));
|
|
5282
|
+
const { root } = parse(startDir);
|
|
5283
|
+
let dir = startDir;
|
|
5284
|
+
for (; ; ) {
|
|
5285
|
+
let manifest;
|
|
5286
|
+
try {
|
|
5287
|
+
manifest = JSON.parse(
|
|
5288
|
+
readFileSync4(join3(dir, "package.json"), "utf-8")
|
|
5289
|
+
);
|
|
5290
|
+
} catch {
|
|
5291
|
+
}
|
|
5292
|
+
if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
|
|
5293
|
+
return manifest.version;
|
|
5294
|
+
}
|
|
5295
|
+
if (dir === root) break;
|
|
5296
|
+
dir = dirname5(dir);
|
|
5297
|
+
}
|
|
5298
|
+
throw new Error(
|
|
5299
|
+
`Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`
|
|
5300
|
+
);
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5252
5303
|
// ../../core/node/hostUrl.ts
|
|
5253
5304
|
import { isIPv6 } from "net";
|
|
5254
5305
|
function stripBrackets(host) {
|
|
@@ -5531,6 +5582,23 @@ function subscriptionFailureMessage(error) {
|
|
|
5531
5582
|
return error instanceof Error ? error.message : String(error);
|
|
5532
5583
|
}
|
|
5533
5584
|
|
|
5585
|
+
// ../../core/mcp/subscriptionRefusal.ts
|
|
5586
|
+
import { SdkErrorCode as SdkErrorCode2, SdkHttpError } from "@modelcontextprotocol/client";
|
|
5587
|
+
var REFUSAL_STATUSES = /* @__PURE__ */ new Set([400, 403, 404, 405]);
|
|
5588
|
+
function subscriptionListenRefusal(error) {
|
|
5589
|
+
if (!SdkHttpError.isInstance(error) || error.code !== SdkErrorCode2.ClientHttpNotImplemented || !REFUSAL_STATUSES.has(error.status)) {
|
|
5590
|
+
return void 0;
|
|
5591
|
+
}
|
|
5592
|
+
return {
|
|
5593
|
+
status: error.status,
|
|
5594
|
+
...error.statusText ? { statusText: error.statusText } : {}
|
|
5595
|
+
};
|
|
5596
|
+
}
|
|
5597
|
+
function subscriptionListenRefusalMessage(refusal) {
|
|
5598
|
+
const status = refusal.statusText ? `${refusal.status} ${refusal.statusText}` : String(refusal.status);
|
|
5599
|
+
return `The server refused subscriptions/listen (HTTP ${status}). Not retrying \u2014 the server would answer the same way again.`;
|
|
5600
|
+
}
|
|
5601
|
+
|
|
5534
5602
|
// ../../core/mcp/messageTrackingTransport.ts
|
|
5535
5603
|
var MessageTrackingTransport = class {
|
|
5536
5604
|
baseTransport;
|
|
@@ -5760,7 +5828,7 @@ var TaskProgressRouter = class {
|
|
|
5760
5828
|
};
|
|
5761
5829
|
|
|
5762
5830
|
// ../../core/extension/tasks/rawWireChannel.ts
|
|
5763
|
-
import { SdkError as SdkError2, SdkErrorCode as
|
|
5831
|
+
import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode3 } from "@modelcontextprotocol/client";
|
|
5764
5832
|
import { DispatchError as DispatchError2 } from "@modelcontextprotocol/ext-tasks/client";
|
|
5765
5833
|
var RAW_WIRE_ID_PREFIX = "inspector-ext-";
|
|
5766
5834
|
var RawWireChannel = class {
|
|
@@ -5833,8 +5901,8 @@ var RawWireChannel = class {
|
|
|
5833
5901
|
`Raw MCP request "${message.method}" timed out after ${timeoutMs} ms`,
|
|
5834
5902
|
false,
|
|
5835
5903
|
{
|
|
5836
|
-
cause: this.host.
|
|
5837
|
-
new SdkError2(
|
|
5904
|
+
cause: this.host.annotateError(
|
|
5905
|
+
new SdkError2(SdkErrorCode3.RequestTimeout, "Request timed out", {
|
|
5838
5906
|
timeout: timeoutMs
|
|
5839
5907
|
}),
|
|
5840
5908
|
message.method
|
|
@@ -5874,7 +5942,7 @@ var RawWireChannel = class {
|
|
|
5874
5942
|
if (!pending) return;
|
|
5875
5943
|
pending.cleanup();
|
|
5876
5944
|
reject(
|
|
5877
|
-
this.host.
|
|
5945
|
+
this.host.annotateError(
|
|
5878
5946
|
error instanceof Error ? error : new Error(String(error)),
|
|
5879
5947
|
message.method
|
|
5880
5948
|
)
|
|
@@ -6174,7 +6242,7 @@ function validateToolOutput(provider, tool, result) {
|
|
|
6174
6242
|
|
|
6175
6243
|
// ../../core/mcp/listSalvage.ts
|
|
6176
6244
|
import { z as z4 } from "zod/v4";
|
|
6177
|
-
import { SdkError as SdkError3, SdkErrorCode as
|
|
6245
|
+
import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
|
|
6178
6246
|
import { ToolSchema } from "@modelcontextprotocol/core";
|
|
6179
6247
|
var LIST_MAX_PAGES = 64;
|
|
6180
6248
|
function listPaginationExceeded(method) {
|
|
@@ -6183,10 +6251,10 @@ function listPaginationExceeded(method) {
|
|
|
6183
6251
|
);
|
|
6184
6252
|
}
|
|
6185
6253
|
function isClientDecodeRejection(err) {
|
|
6186
|
-
return SdkError3.isInstance(err) && (err.code ===
|
|
6254
|
+
return SdkError3.isInstance(err) && (err.code === SdkErrorCode4.InvalidResult || err.code === SdkErrorCode4.UnsupportedResultType);
|
|
6187
6255
|
}
|
|
6188
6256
|
function isSalvageableRejection(err) {
|
|
6189
|
-
return SdkError3.isInstance(err) && err.code ===
|
|
6257
|
+
return SdkError3.isInstance(err) && err.code === SdkErrorCode4.InvalidResult;
|
|
6190
6258
|
}
|
|
6191
6259
|
function lenientListPageSchema(resultSchema, itemsKey) {
|
|
6192
6260
|
return resultSchema.extend({ [itemsKey]: z4.array(z4.unknown()) });
|
|
@@ -6923,6 +6991,84 @@ function isJsonDocumentResponse(contentType) {
|
|
|
6923
6991
|
const mediaType = contentType.split(";")[0].trim().toLowerCase();
|
|
6924
6992
|
return mediaType === "application/json" || mediaType.endsWith("+json");
|
|
6925
6993
|
}
|
|
6994
|
+
var SYNTHESIZED_METADATA_HEADER = "x-inspector-oauth-metadata-synthesized";
|
|
6995
|
+
var synthesizedResponses = /* @__PURE__ */ new WeakSet();
|
|
6996
|
+
function isSynthesizedMetadataResponse(response) {
|
|
6997
|
+
return synthesizedResponses.has(response);
|
|
6998
|
+
}
|
|
6999
|
+
var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
|
|
7000
|
+
var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
|
|
7001
|
+
function continuesDiscovery(status) {
|
|
7002
|
+
return status < 500 || status === 502;
|
|
7003
|
+
}
|
|
7004
|
+
function authorizationServerForFinalCandidate(url) {
|
|
7005
|
+
let parsed;
|
|
7006
|
+
try {
|
|
7007
|
+
parsed = new URL(url);
|
|
7008
|
+
} catch {
|
|
7009
|
+
return void 0;
|
|
7010
|
+
}
|
|
7011
|
+
const { pathname } = parsed;
|
|
7012
|
+
if (pathname === OIDC_WELL_KNOWN) return parsed.origin;
|
|
7013
|
+
if (!pathname.endsWith(OIDC_WELL_KNOWN)) return void 0;
|
|
7014
|
+
if (pathname.startsWith(`${RFC8414_WELL_KNOWN}/`) || pathname.startsWith(`${OIDC_WELL_KNOWN}/`)) {
|
|
7015
|
+
return void 0;
|
|
7016
|
+
}
|
|
7017
|
+
return `${parsed.origin}${pathname.slice(0, -OIDC_WELL_KNOWN.length)}`;
|
|
7018
|
+
}
|
|
7019
|
+
function synthesizeAuthorizationServerMetadata(issuer, overrides) {
|
|
7020
|
+
return {
|
|
7021
|
+
issuer,
|
|
7022
|
+
authorization_endpoint: overrides.authorizationUrl ?? new URL("/authorize", issuer).href,
|
|
7023
|
+
token_endpoint: overrides.tokenUrl ?? new URL("/token", issuer).href,
|
|
7024
|
+
registration_endpoint: new URL("/register", issuer).href,
|
|
7025
|
+
response_types_supported: ["code"],
|
|
7026
|
+
code_challenge_methods_supported: ["S256"]
|
|
7027
|
+
};
|
|
7028
|
+
}
|
|
7029
|
+
function requestMethodOf(input, init) {
|
|
7030
|
+
if (init?.method) return init.method.toUpperCase();
|
|
7031
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
7032
|
+
return input.method.toUpperCase();
|
|
7033
|
+
}
|
|
7034
|
+
return "GET";
|
|
7035
|
+
}
|
|
7036
|
+
function acceptsJson(input, init) {
|
|
7037
|
+
let accept = null;
|
|
7038
|
+
if (init?.headers) {
|
|
7039
|
+
accept = new Headers(init.headers).get("accept");
|
|
7040
|
+
} else if (typeof input !== "string" && !(input instanceof URL)) {
|
|
7041
|
+
accept = input.headers.get("accept");
|
|
7042
|
+
}
|
|
7043
|
+
if (!accept) return false;
|
|
7044
|
+
const mediaTypes = accept.split(",").map((part) => part.split(";")[0].trim().toLowerCase());
|
|
7045
|
+
return mediaTypes.includes("application/json") && !mediaTypes.includes("text/event-stream");
|
|
7046
|
+
}
|
|
7047
|
+
function synthesizedMetadataResponse(input, init, response, overrides) {
|
|
7048
|
+
if (!continuesDiscovery(response.status)) return void 0;
|
|
7049
|
+
if (requestMethodOf(input, init) !== "GET") return void 0;
|
|
7050
|
+
if (!acceptsJson(input, init)) return void 0;
|
|
7051
|
+
const issuer = authorizationServerForFinalCandidate(requestUrlOf(input));
|
|
7052
|
+
if (!issuer) return void 0;
|
|
7053
|
+
void response.body?.cancel().catch(() => {
|
|
7054
|
+
});
|
|
7055
|
+
console.warn(
|
|
7056
|
+
`[oauth] ${issuer} publishes no authorization server metadata; using the configured endpoint override(s) in place of the SDK's /authorize and /token fallbacks.`
|
|
7057
|
+
);
|
|
7058
|
+
const synthesized = new Response(
|
|
7059
|
+
JSON.stringify(synthesizeAuthorizationServerMetadata(issuer, overrides)),
|
|
7060
|
+
{
|
|
7061
|
+
status: 200,
|
|
7062
|
+
statusText: "OK",
|
|
7063
|
+
headers: {
|
|
7064
|
+
"content-type": "application/json",
|
|
7065
|
+
[SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
);
|
|
7069
|
+
synthesizedResponses.add(synthesized);
|
|
7070
|
+
return synthesized;
|
|
7071
|
+
}
|
|
6926
7072
|
function responseWithBody(response, body) {
|
|
6927
7073
|
const headers = new Headers(response.headers);
|
|
6928
7074
|
headers.delete("content-length");
|
|
@@ -6950,7 +7096,9 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
|
|
|
6950
7096
|
const overrides = resolveOverrides();
|
|
6951
7097
|
if (!overrides) return response;
|
|
6952
7098
|
if (!isAuthorizationServerMetadataRequest(input)) return response;
|
|
6953
|
-
if (!response.ok)
|
|
7099
|
+
if (!response.ok) {
|
|
7100
|
+
return synthesizedMetadataResponse(input, init, response, overrides) ?? response;
|
|
7101
|
+
}
|
|
6954
7102
|
if (!isJsonDocumentResponse(response.headers.get("content-type")))
|
|
6955
7103
|
return response;
|
|
6956
7104
|
let parsed;
|
|
@@ -7126,8 +7274,8 @@ import {
|
|
|
7126
7274
|
OAuthMetadataSchema,
|
|
7127
7275
|
OpenIdProviderDiscoveryMetadataSchema
|
|
7128
7276
|
} from "@modelcontextprotocol/core";
|
|
7129
|
-
var
|
|
7130
|
-
var
|
|
7277
|
+
var RFC8414_WELL_KNOWN2 = "/.well-known/oauth-authorization-server";
|
|
7278
|
+
var OIDC_WELL_KNOWN2 = "/.well-known/openid-configuration";
|
|
7131
7279
|
var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
|
|
7132
7280
|
function oidcDiscoveryCandidates(rfc8414Url) {
|
|
7133
7281
|
let url;
|
|
@@ -7136,16 +7284,16 @@ function oidcDiscoveryCandidates(rfc8414Url) {
|
|
|
7136
7284
|
} catch {
|
|
7137
7285
|
return [];
|
|
7138
7286
|
}
|
|
7139
|
-
if (!url.pathname.startsWith(
|
|
7140
|
-
const path7 = url.pathname.slice(
|
|
7287
|
+
if (!url.pathname.startsWith(RFC8414_WELL_KNOWN2)) return [];
|
|
7288
|
+
const path7 = url.pathname.slice(RFC8414_WELL_KNOWN2.length);
|
|
7141
7289
|
if (path7 !== "" && !path7.startsWith("/")) return [];
|
|
7142
|
-
if (path7 === "") return [new URL(
|
|
7290
|
+
if (path7 === "") return [new URL(OIDC_WELL_KNOWN2, url.origin).href];
|
|
7143
7291
|
return [
|
|
7144
|
-
new URL(`${
|
|
7145
|
-
new URL(`${path7}${
|
|
7292
|
+
new URL(`${OIDC_WELL_KNOWN2}${path7}`, url.origin).href,
|
|
7293
|
+
new URL(`${path7}${OIDC_WELL_KNOWN2}`, url.origin).href
|
|
7146
7294
|
];
|
|
7147
7295
|
}
|
|
7148
|
-
function
|
|
7296
|
+
function continuesDiscovery2(status) {
|
|
7149
7297
|
return status >= 400 && status < 500 || status === 502;
|
|
7150
7298
|
}
|
|
7151
7299
|
async function releaseBody(response) {
|
|
@@ -7171,7 +7319,7 @@ function discoveryHeaders(input, init) {
|
|
|
7171
7319
|
}
|
|
7172
7320
|
return new Headers();
|
|
7173
7321
|
}
|
|
7174
|
-
function
|
|
7322
|
+
function requestMethodOf2(input, init) {
|
|
7175
7323
|
if (init?.method) return init.method.toUpperCase();
|
|
7176
7324
|
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
7177
7325
|
return input.method.toUpperCase();
|
|
@@ -7182,8 +7330,8 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
7182
7330
|
return async (input, init) => {
|
|
7183
7331
|
const response = await fetchFn(input, init);
|
|
7184
7332
|
if (response.ok) return response;
|
|
7185
|
-
if (!
|
|
7186
|
-
if (
|
|
7333
|
+
if (!continuesDiscovery2(response.status)) return response;
|
|
7334
|
+
if (requestMethodOf2(input, init) !== "GET") return response;
|
|
7187
7335
|
const candidates = oidcDiscoveryCandidates(requestUrlOf3(input));
|
|
7188
7336
|
if (candidates.length === 0) return response;
|
|
7189
7337
|
const headers = discoveryHeaders(input, init);
|
|
@@ -7213,7 +7361,7 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
7213
7361
|
releaseBodyDetached(response);
|
|
7214
7362
|
throw callerSignal.reason;
|
|
7215
7363
|
}
|
|
7216
|
-
if (
|
|
7364
|
+
if (continuesDiscovery2(probe.status)) continue;
|
|
7217
7365
|
return response;
|
|
7218
7366
|
}
|
|
7219
7367
|
let parsed;
|
|
@@ -7233,6 +7381,16 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
7233
7381
|
}
|
|
7234
7382
|
const source = probe.url || candidate;
|
|
7235
7383
|
releaseBodyDetached(response);
|
|
7384
|
+
if (isSynthesizedMetadataResponse(probe)) {
|
|
7385
|
+
return new Response(body, {
|
|
7386
|
+
status: 200,
|
|
7387
|
+
statusText: "OK",
|
|
7388
|
+
headers: {
|
|
7389
|
+
"content-type": "application/json",
|
|
7390
|
+
[SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
|
|
7391
|
+
}
|
|
7392
|
+
});
|
|
7393
|
+
}
|
|
7236
7394
|
console.warn(
|
|
7237
7395
|
`[oauth] ${source} returned RFC 8414 OAuth 2.0 authorization server metadata, not an OpenID provider document. The MCP TypeScript SDK validates that path as OpenID Connect Discovery and would reject it (modelcontextprotocol/typescript-sdk#2733), so the Inspector is handing it to discovery as the RFC 8414 document it is.`
|
|
7238
7396
|
);
|
|
@@ -7274,12 +7432,33 @@ function createSilentLogger() {
|
|
|
7274
7432
|
var silentLogger = createSilentLogger();
|
|
7275
7433
|
|
|
7276
7434
|
// ../../core/mcp/connectionDiagnostics.ts
|
|
7277
|
-
import { SdkError as SdkError4, SdkErrorCode as
|
|
7435
|
+
import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
|
|
7278
7436
|
function isRequestTimeoutData(value) {
|
|
7279
7437
|
return typeof value === "object" && value !== null && typeof value.timeout === "number";
|
|
7280
7438
|
}
|
|
7281
7439
|
function isRequestTimeoutError(err) {
|
|
7282
|
-
return SdkError4.isInstance(err) && err.code ===
|
|
7440
|
+
return SdkError4.isInstance(err) && err.code === SdkErrorCode5.RequestTimeout && isRequestTimeoutData(err.data);
|
|
7441
|
+
}
|
|
7442
|
+
function describeSendFailure(err) {
|
|
7443
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7444
|
+
const status = httpStatusOf(err);
|
|
7445
|
+
if (status === void 0 || message.includes(`HTTP ${status}`)) {
|
|
7446
|
+
return message;
|
|
7447
|
+
}
|
|
7448
|
+
const statusText = httpStatusTextOf(err);
|
|
7449
|
+
return `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ${message}`;
|
|
7450
|
+
}
|
|
7451
|
+
function httpStatusOf(err) {
|
|
7452
|
+
if (typeof err !== "object" || err === null || !("status" in err)) {
|
|
7453
|
+
return void 0;
|
|
7454
|
+
}
|
|
7455
|
+
return typeof err.status === "number" ? err.status : void 0;
|
|
7456
|
+
}
|
|
7457
|
+
function httpStatusTextOf(err) {
|
|
7458
|
+
if (typeof err !== "object" || err === null || !("statusText" in err)) {
|
|
7459
|
+
return void 0;
|
|
7460
|
+
}
|
|
7461
|
+
return typeof err.statusText === "string" ? err.statusText : void 0;
|
|
7283
7462
|
}
|
|
7284
7463
|
function formatDuration(ms) {
|
|
7285
7464
|
const clamped = Math.max(0, ms);
|
|
@@ -7341,11 +7520,108 @@ function annotateRequestTimeout(err, method, diagnostics) {
|
|
|
7341
7520
|
diagnostics
|
|
7342
7521
|
};
|
|
7343
7522
|
const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
|
|
7344
|
-
const annotated = new SdkError4(
|
|
7523
|
+
const annotated = new SdkError4(SdkErrorCode5.RequestTimeout, message, data);
|
|
7345
7524
|
annotated.cause = err;
|
|
7346
7525
|
return annotated;
|
|
7347
7526
|
}
|
|
7348
7527
|
|
|
7528
|
+
// ../../core/mcp/rateLimit.ts
|
|
7529
|
+
import {
|
|
7530
|
+
SdkHttpError as SdkHttpError2
|
|
7531
|
+
} from "@modelcontextprotocol/client";
|
|
7532
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
7533
|
+
function requestIdsOf(body) {
|
|
7534
|
+
if (body === void 0) return [];
|
|
7535
|
+
let parsed;
|
|
7536
|
+
try {
|
|
7537
|
+
parsed = JSON.parse(body);
|
|
7538
|
+
} catch {
|
|
7539
|
+
return [];
|
|
7540
|
+
}
|
|
7541
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
7542
|
+
const ids = [];
|
|
7543
|
+
for (const message of messages) {
|
|
7544
|
+
if (typeof message !== "object" || message === null) continue;
|
|
7545
|
+
if (!("method" in message) || !("id" in message)) continue;
|
|
7546
|
+
const { id } = message;
|
|
7547
|
+
if (typeof id === "string" || typeof id === "number") ids.push(id);
|
|
7548
|
+
}
|
|
7549
|
+
return ids;
|
|
7550
|
+
}
|
|
7551
|
+
var MAX_UNCLAIMED_RATE_LIMITS = 100;
|
|
7552
|
+
var RateLimitLedger = class {
|
|
7553
|
+
// `undefined` is a real value here: a 429 that carried no header.
|
|
7554
|
+
byRequestId = /* @__PURE__ */ new Map();
|
|
7555
|
+
/** File a tracked fetch, if it was a 429. Anything else is ignored. */
|
|
7556
|
+
record(entry) {
|
|
7557
|
+
if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
|
|
7558
|
+
const retryAfter = findHeader(entry.responseHeaders, "retry-after");
|
|
7559
|
+
for (const id of requestIdsOf(entry.requestBody)) {
|
|
7560
|
+
this.byRequestId.delete(id);
|
|
7561
|
+
this.byRequestId.set(id, retryAfter);
|
|
7562
|
+
if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
|
|
7563
|
+
const [oldest] = this.byRequestId.keys();
|
|
7564
|
+
if (oldest !== void 0) this.byRequestId.delete(oldest);
|
|
7565
|
+
}
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
/** The `Retry-After` filed for `id`, removed as it is read. */
|
|
7569
|
+
take(id) {
|
|
7570
|
+
const retryAfter = this.byRequestId.get(id);
|
|
7571
|
+
this.byRequestId.delete(id);
|
|
7572
|
+
return retryAfter;
|
|
7573
|
+
}
|
|
7574
|
+
clear() {
|
|
7575
|
+
this.byRequestId.clear();
|
|
7576
|
+
}
|
|
7577
|
+
};
|
|
7578
|
+
var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
7579
|
+
function parseRetryAfter(value, now) {
|
|
7580
|
+
if (value === void 0) return void 0;
|
|
7581
|
+
const trimmed = value.trim();
|
|
7582
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
7583
|
+
if (!IMF_FIXDATE.test(trimmed)) return void 0;
|
|
7584
|
+
const at = Date.parse(trimmed);
|
|
7585
|
+
if (Number.isNaN(at) || new Date(at).toUTCString() !== trimmed) {
|
|
7586
|
+
return void 0;
|
|
7587
|
+
}
|
|
7588
|
+
return Math.max(0, at - now);
|
|
7589
|
+
}
|
|
7590
|
+
function isRateLimitError(err) {
|
|
7591
|
+
return SdkHttpError2.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
|
|
7592
|
+
}
|
|
7593
|
+
function retryAfterOn(err) {
|
|
7594
|
+
const value = err.data.retryAfter;
|
|
7595
|
+
return typeof value === "string" ? value : void 0;
|
|
7596
|
+
}
|
|
7597
|
+
function attachRetryAfter(err, retryAfter) {
|
|
7598
|
+
if (!isRateLimitError(err) || retryAfter === void 0) return;
|
|
7599
|
+
if (retryAfterOn(err) !== void 0) return;
|
|
7600
|
+
err.data.retryAfter = retryAfter;
|
|
7601
|
+
}
|
|
7602
|
+
function describeRetry(retryAfter, retryAfterMs) {
|
|
7603
|
+
if (retryAfterMs !== void 0) {
|
|
7604
|
+
return `The server asked to retry after ${formatDuration(retryAfterMs)}.`;
|
|
7605
|
+
}
|
|
7606
|
+
if (retryAfter !== void 0) {
|
|
7607
|
+
return `The server sent an unparseable Retry-After (${retryAfter}).`;
|
|
7608
|
+
}
|
|
7609
|
+
return "The server sent no Retry-After header.";
|
|
7610
|
+
}
|
|
7611
|
+
function annotateRateLimit(err, method, now = Date.now()) {
|
|
7612
|
+
if (!isRateLimitError(err)) return err;
|
|
7613
|
+
const header = retryAfterOn(err);
|
|
7614
|
+
const retryAfterMs = parseRetryAfter(header, now);
|
|
7615
|
+
const data = {
|
|
7616
|
+
...err.data,
|
|
7617
|
+
method,
|
|
7618
|
+
...header !== void 0 && { retryAfter: header },
|
|
7619
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
7620
|
+
};
|
|
7621
|
+
const message = `Rate limited by the server (HTTP 429) on ${method}. ${describeRetry(header, retryAfterMs)} ${err.message}`;
|
|
7622
|
+
return new SdkHttpError2(err.code, message, data, { cause: err });
|
|
7623
|
+
}
|
|
7624
|
+
|
|
7349
7625
|
// ../../core/auth/authorizationParams.ts
|
|
7350
7626
|
var RESERVED_AUTHORIZATION_PARAMS = [
|
|
7351
7627
|
"client_id",
|
|
@@ -7463,6 +7739,13 @@ var BaseOAuthClientProvider = class {
|
|
|
7463
7739
|
requestRefreshToken;
|
|
7464
7740
|
/** Server-configured scope, for the #2068 `offline_access` distinction. */
|
|
7465
7741
|
configuredScope;
|
|
7742
|
+
/** Per-flow verifier store (#2726); absent → the shared storage slot. */
|
|
7743
|
+
flowStore;
|
|
7744
|
+
/**
|
|
7745
|
+
* The authId of the flow this provider is driving: set by {@link state} on the
|
|
7746
|
+
* redirect leg and by {@link resumeAuthorizationFlow} on the callback leg.
|
|
7747
|
+
*/
|
|
7748
|
+
flowId;
|
|
7466
7749
|
constructor(serverUrl, oauthConfig) {
|
|
7467
7750
|
this.serverUrl = serverUrl;
|
|
7468
7751
|
this.storage = oauthConfig.storage;
|
|
@@ -7472,6 +7755,7 @@ var BaseOAuthClientProvider = class {
|
|
|
7472
7755
|
this.authorizationParams = oauthConfig.authorizationParams;
|
|
7473
7756
|
this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
|
|
7474
7757
|
this.configuredScope = oauthConfig.configuredScope;
|
|
7758
|
+
this.flowStore = oauthConfig.flowStore;
|
|
7475
7759
|
}
|
|
7476
7760
|
/**
|
|
7477
7761
|
* Load persisted scope into {@link cachedScope} before SDK `auth()` (which
|
|
@@ -7542,7 +7826,24 @@ var BaseOAuthClientProvider = class {
|
|
|
7542
7826
|
return metadata;
|
|
7543
7827
|
}
|
|
7544
7828
|
state() {
|
|
7545
|
-
|
|
7829
|
+
const state = generateOAuthState();
|
|
7830
|
+
this.flowId = state;
|
|
7831
|
+
return state;
|
|
7832
|
+
}
|
|
7833
|
+
/**
|
|
7834
|
+
* Bind this provider to the flow a callback's `state` names, so
|
|
7835
|
+
* {@link codeVerifier} reads that flow's verifier and no other (#2726). A
|
|
7836
|
+
* `state` that is not one of ours binds nothing — and, with a flow store, a
|
|
7837
|
+
* flow-less read then fails rather than reaching for the shared slot.
|
|
7838
|
+
*/
|
|
7839
|
+
resumeAuthorizationFlow(state) {
|
|
7840
|
+
this.flowId = parseOAuthState(state)?.authId;
|
|
7841
|
+
}
|
|
7842
|
+
/** Drop the bound flow's verifier once its exchange has completed. */
|
|
7843
|
+
async clearAuthorizationFlow() {
|
|
7844
|
+
if (this.flowStore && this.flowId) {
|
|
7845
|
+
await this.flowStore.clearCodeVerifier(this.flowId);
|
|
7846
|
+
}
|
|
7546
7847
|
}
|
|
7547
7848
|
async clientInformation(ctx) {
|
|
7548
7849
|
const preregistered = await this.storage.getClientInformation(
|
|
@@ -7687,9 +7988,26 @@ var BaseOAuthClientProvider = class {
|
|
|
7687
7988
|
}
|
|
7688
7989
|
}
|
|
7689
7990
|
async saveCodeVerifier(codeVerifier) {
|
|
7991
|
+
if (this.flowStore && this.flowId) {
|
|
7992
|
+
await this.flowStore.saveCodeVerifier(
|
|
7993
|
+
this.flowId,
|
|
7994
|
+
this.serverUrl,
|
|
7995
|
+
codeVerifier
|
|
7996
|
+
);
|
|
7997
|
+
return;
|
|
7998
|
+
}
|
|
7690
7999
|
await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
|
|
7691
8000
|
}
|
|
7692
8001
|
async codeVerifier() {
|
|
8002
|
+
if (this.flowStore) {
|
|
8003
|
+
const verifier2 = this.flowId ? await this.flowStore.getCodeVerifier(this.flowId, this.serverUrl) : void 0;
|
|
8004
|
+
if (!verifier2) {
|
|
8005
|
+
throw new Error(
|
|
8006
|
+
"No code verifier saved for this authorization flow. It may have been started in another browser tab or already completed; please re-authenticate."
|
|
8007
|
+
);
|
|
8008
|
+
}
|
|
8009
|
+
return verifier2;
|
|
8010
|
+
}
|
|
7693
8011
|
const verifier = await this.storage.getCodeVerifier(this.serverUrl);
|
|
7694
8012
|
if (!verifier) {
|
|
7695
8013
|
throw new Error("No code verifier saved for session");
|
|
@@ -7736,6 +8054,10 @@ var BaseOAuthClientProvider = class {
|
|
|
7736
8054
|
await this.storage.clearTokens(this.serverUrl);
|
|
7737
8055
|
return;
|
|
7738
8056
|
case "verifier":
|
|
8057
|
+
if (this.flowStore) {
|
|
8058
|
+
await this.clearAuthorizationFlow();
|
|
8059
|
+
return;
|
|
8060
|
+
}
|
|
7739
8061
|
await this.storage.clearCodeVerifier(this.serverUrl);
|
|
7740
8062
|
return;
|
|
7741
8063
|
case "discovery":
|
|
@@ -8213,10 +8535,10 @@ async function resolveIdpMetadata(issuer, storage, fetchFn) {
|
|
|
8213
8535
|
}
|
|
8214
8536
|
return discoverIdpMetadata(issuer, fetchFn);
|
|
8215
8537
|
}
|
|
8216
|
-
var
|
|
8538
|
+
var OIDC_WELL_KNOWN3 = "/.well-known/openid-configuration";
|
|
8217
8539
|
async function fetchOpenIdConfiguration(issuer, issuerUrl, fetchFn) {
|
|
8218
8540
|
const url = new URL(
|
|
8219
|
-
`${issuerUrl.pathname.replace(/\/$/, "")}${
|
|
8541
|
+
`${issuerUrl.pathname.replace(/\/$/, "")}${OIDC_WELL_KNOWN3}`,
|
|
8220
8542
|
issuerUrl.origin
|
|
8221
8543
|
);
|
|
8222
8544
|
try {
|
|
@@ -8606,11 +8928,6 @@ async function mintEmaResourceTokens(config, resourceContext) {
|
|
|
8606
8928
|
"EMA requires resource authorization server clientId (per-server oauth.clientId)"
|
|
8607
8929
|
);
|
|
8608
8930
|
}
|
|
8609
|
-
if (!config.resourceClientSecret?.trim()) {
|
|
8610
|
-
throw new Error(
|
|
8611
|
-
"EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
|
|
8612
|
-
);
|
|
8613
|
-
}
|
|
8614
8931
|
const idToken = await getValidIdToken({
|
|
8615
8932
|
idp: config.idp,
|
|
8616
8933
|
storage: config.storage,
|
|
@@ -9117,7 +9434,9 @@ var OAuthManager = class {
|
|
|
9117
9434
|
requestRefreshToken: this.oauthConfig.requestRefreshToken,
|
|
9118
9435
|
// #2068: lets the provider tell an `offline_access` the user asked for
|
|
9119
9436
|
// from one inherited from a previous grant's persisted scope.
|
|
9120
|
-
configuredScope: this.oauthConfig.scope
|
|
9437
|
+
configuredScope: this.oauthConfig.scope,
|
|
9438
|
+
// #2726: in-flight PKCE verifiers keyed by flow, not by server URL.
|
|
9439
|
+
flowStore: this.oauthConfig.flowStore
|
|
9121
9440
|
});
|
|
9122
9441
|
provider.setEventTarget(this.params.getEventTarget());
|
|
9123
9442
|
await provider.prepareForAuth();
|
|
@@ -9281,7 +9600,12 @@ var OAuthManager = class {
|
|
|
9281
9600
|
recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
|
|
9282
9601
|
this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
|
|
9283
9602
|
}
|
|
9284
|
-
|
|
9603
|
+
/**
|
|
9604
|
+
* `state` is the OAuth `state` the callback was redirected back with. It
|
|
9605
|
+
* selects the flow whose PKCE verifier the exchange uses (#2726); without it
|
|
9606
|
+
* a provider with a flow store has no verifier to send.
|
|
9607
|
+
*/
|
|
9608
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
9285
9609
|
try {
|
|
9286
9610
|
if (this.isEnterpriseManaged()) {
|
|
9287
9611
|
const emaConfig = await this.getEmaFlowConfig();
|
|
@@ -9305,6 +9629,9 @@ var OAuthManager = class {
|
|
|
9305
9629
|
}
|
|
9306
9630
|
const provider = await this.createOAuthProvider();
|
|
9307
9631
|
const serverUrl = this.getServerUrl();
|
|
9632
|
+
if (state !== void 0) {
|
|
9633
|
+
provider.resumeAuthorizationFlow(state);
|
|
9634
|
+
}
|
|
9308
9635
|
const result = await mcpAuth(provider, {
|
|
9309
9636
|
serverUrl,
|
|
9310
9637
|
authorizationCode,
|
|
@@ -9316,6 +9643,7 @@ var OAuthManager = class {
|
|
|
9316
9643
|
`Expected AUTHORIZED after providing authorization code, got: ${result}`
|
|
9317
9644
|
);
|
|
9318
9645
|
}
|
|
9646
|
+
await provider.clearAuthorizationFlow();
|
|
9319
9647
|
const tokens = await provider.tokens();
|
|
9320
9648
|
if (!tokens) {
|
|
9321
9649
|
throw new Error("Failed to retrieve tokens after authorization");
|
|
@@ -9447,15 +9775,21 @@ var OAuthManager = class {
|
|
|
9447
9775
|
* Re-read persisted OAuth state and determine whether `challenge` is already
|
|
9448
9776
|
* satisfied without an authorization-server round-trip.
|
|
9449
9777
|
*
|
|
9450
|
-
*
|
|
9451
|
-
*
|
|
9452
|
-
*
|
|
9453
|
-
*
|
|
9454
|
-
*
|
|
9455
|
-
*
|
|
9456
|
-
*
|
|
9778
|
+
* For `token_expired` and `insufficient_scope`, returns `true` only when
|
|
9779
|
+
* storage holds a *provably* unexpired token — a JWT whose `exp` is still in
|
|
9780
|
+
* the future. An opaque token carries no local expiry evidence, so the
|
|
9781
|
+
* resource server's verdict stands and re-authorization proceeds.
|
|
9782
|
+
* `insufficient_scope` additionally requires stored + token scope to cover
|
|
9783
|
+
* the SEP-2350 union: some resource servers answer an *expired* token with
|
|
9784
|
+
* `insufficient_scope`, and a scope-only check would replay that dead token
|
|
9785
|
+
* instead of refreshing it (#2724, the same gap #2051 closed for
|
|
9786
|
+
* `token_expired`). `invalid_token` and `unauthorized` always return
|
|
9787
|
+
* `false` — the resource server explicitly rejected the credential.
|
|
9788
|
+
*
|
|
9789
|
+
* A caller that has just completed a grant passes `scopeOnly` — see
|
|
9790
|
+
* {@link CheckAuthChallengeSatisfiedOptions}.
|
|
9457
9791
|
*/
|
|
9458
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
9792
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
9459
9793
|
const storage = this.oauthConfig.storage;
|
|
9460
9794
|
if (!storage) {
|
|
9461
9795
|
return false;
|
|
@@ -9468,6 +9802,14 @@ var OAuthManager = class {
|
|
|
9468
9802
|
if (challenge.reason !== "insufficient_scope") {
|
|
9469
9803
|
return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
|
|
9470
9804
|
}
|
|
9805
|
+
if (!options?.scopeOnly && !isAccessTokenProvablyUnexpired(tokens)) {
|
|
9806
|
+
return false;
|
|
9807
|
+
}
|
|
9808
|
+
return this.isStepUpScopeCovered(challenge, storage, tokens);
|
|
9809
|
+
}
|
|
9810
|
+
/** Stored + token scope cover the challenge's SEP-2350 union. */
|
|
9811
|
+
async isStepUpScopeCovered(challenge, storage, tokens) {
|
|
9812
|
+
const serverUrl = this.getServerUrl();
|
|
9471
9813
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(
|
|
9472
9814
|
challenge,
|
|
9473
9815
|
tokens.scope
|
|
@@ -9571,14 +9913,14 @@ var OAuthManager = class {
|
|
|
9571
9913
|
}
|
|
9572
9914
|
async handleEnterpriseManagedAuthChallenge(challenge, options) {
|
|
9573
9915
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
|
|
9574
|
-
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
|
|
9916
|
+
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp && !await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9575
9917
|
return { kind: "step_up_confirm", challenge: enriched };
|
|
9576
9918
|
}
|
|
9577
9919
|
const config = await this.emaFlowConfigForChallenge(enriched);
|
|
9578
9920
|
if (enriched.reason === "insufficient_scope") {
|
|
9579
9921
|
const silent = await trySilentEmaAuth(config);
|
|
9580
9922
|
if (silent.status === "success") {
|
|
9581
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
9923
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9582
9924
|
const minted = await this.requireStorage().getTokens(
|
|
9583
9925
|
this.getServerUrl()
|
|
9584
9926
|
);
|
|
@@ -9656,9 +9998,16 @@ var OAuthManager = class {
|
|
|
9656
9998
|
fetchFn: this.params.effectiveAuthFetch,
|
|
9657
9999
|
resourceMetadataUrl,
|
|
9658
10000
|
...enriched.reason === "insufficient_scope" && {
|
|
10001
|
+
// Judge coverage the way checkAuthChallengeSatisfied does: a token
|
|
10002
|
+
// response that omitted `scope` granted what was stored, so a
|
|
10003
|
+
// covered challenge on an expired token refreshes rather than
|
|
10004
|
+
// forcing interactive authorization (#2724).
|
|
9659
10005
|
forceReauthorization: isStrictScopeSuperset(
|
|
9660
10006
|
scopeForAuth,
|
|
9661
|
-
|
|
10007
|
+
resolveEffectiveGrantedScope(
|
|
10008
|
+
await this.oauthConfig.storage?.getScope(serverUrl),
|
|
10009
|
+
tokens?.scope
|
|
10010
|
+
)
|
|
9662
10011
|
)
|
|
9663
10012
|
}
|
|
9664
10013
|
});
|
|
@@ -9667,7 +10016,7 @@ var OAuthManager = class {
|
|
|
9667
10016
|
}
|
|
9668
10017
|
if (result === "AUTHORIZED") {
|
|
9669
10018
|
if (enriched.reason === "insufficient_scope") {
|
|
9670
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
10019
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9671
10020
|
const freshTokens = await provider.tokens();
|
|
9672
10021
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
9673
10022
|
freshTokens?.scope,
|
|
@@ -9769,7 +10118,7 @@ var OAuthManager = class {
|
|
|
9769
10118
|
if (result !== "AUTHORIZED") {
|
|
9770
10119
|
return null;
|
|
9771
10120
|
}
|
|
9772
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
10121
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9773
10122
|
const freshTokens = await provider.tokens();
|
|
9774
10123
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
9775
10124
|
freshTokens?.scope,
|
|
@@ -9799,7 +10148,11 @@ var OAuthManager = class {
|
|
|
9799
10148
|
};
|
|
9800
10149
|
|
|
9801
10150
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
9802
|
-
import {
|
|
10151
|
+
import {
|
|
10152
|
+
SdkError as SdkError5,
|
|
10153
|
+
SdkErrorCode as SdkErrorCode6,
|
|
10154
|
+
SdkHttpError as SdkHttpError3
|
|
10155
|
+
} from "@modelcontextprotocol/client";
|
|
9803
10156
|
|
|
9804
10157
|
// ../../core/mcp/remote/types.ts
|
|
9805
10158
|
function oauthTokensToRemoteAuthState(tokens) {
|
|
@@ -9854,6 +10207,18 @@ function isConnectAuthChallenge(json) {
|
|
|
9854
10207
|
function isConnectTransportError(json) {
|
|
9855
10208
|
return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
|
|
9856
10209
|
}
|
|
10210
|
+
var SDK_ERROR_CODES = new Set(
|
|
10211
|
+
Object.values(SdkErrorCode6)
|
|
10212
|
+
);
|
|
10213
|
+
function toSdkHttpError(message, http) {
|
|
10214
|
+
const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode6.ClientHttpNotImplemented;
|
|
10215
|
+
return new SdkHttpError3(code, message, {
|
|
10216
|
+
status: http.status,
|
|
10217
|
+
...http.statusText !== void 0 && { statusText: http.statusText },
|
|
10218
|
+
...http.text !== void 0 && { text: http.text },
|
|
10219
|
+
...http.retryAfter !== void 0 && { retryAfter: http.retryAfter }
|
|
10220
|
+
});
|
|
10221
|
+
}
|
|
9857
10222
|
function legacySessionId(json) {
|
|
9858
10223
|
if (typeof json === "object" && json !== null && "sessionId" in json && typeof json.sessionId === "string" && !("ok" in json)) {
|
|
9859
10224
|
return json.sessionId;
|
|
@@ -10285,7 +10650,7 @@ var RemoteClientTransport = class {
|
|
|
10285
10650
|
timer = setTimeout(() => {
|
|
10286
10651
|
this.sseResponseWaits.delete(requestId);
|
|
10287
10652
|
reject(
|
|
10288
|
-
new SdkError5(
|
|
10653
|
+
new SdkError5(SdkErrorCode6.RequestTimeout, "Request timed out", {
|
|
10289
10654
|
timeout: this.sseResponseTimeoutMs
|
|
10290
10655
|
})
|
|
10291
10656
|
);
|
|
@@ -10354,6 +10719,9 @@ var RemoteClientTransport = class {
|
|
|
10354
10719
|
});
|
|
10355
10720
|
return;
|
|
10356
10721
|
}
|
|
10722
|
+
if (json.http) {
|
|
10723
|
+
throw toSdkHttpError(json.error, json.http);
|
|
10724
|
+
}
|
|
10357
10725
|
throw new Error(json.error);
|
|
10358
10726
|
}
|
|
10359
10727
|
async requestSend(message, options) {
|
|
@@ -10426,8 +10794,9 @@ var corePackageJson = {
|
|
|
10426
10794
|
version: "0.0.0"
|
|
10427
10795
|
};
|
|
10428
10796
|
var MAX_URL_ELICITATION_RETRIES = 5;
|
|
10429
|
-
|
|
10430
|
-
|
|
10797
|
+
var WEB_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, you may increase the timeout value in Server Settings.";
|
|
10798
|
+
function connectionTimeoutMessage(timeoutMs, remedy = WEB_CONNECTION_TIMEOUT_REMEDY) {
|
|
10799
|
+
return `Connection timed out after ${timeoutMs} ms. ${remedy}`;
|
|
10431
10800
|
}
|
|
10432
10801
|
function createPendingAbortError() {
|
|
10433
10802
|
return new Error("Pending request aborted");
|
|
@@ -10493,7 +10862,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10493
10862
|
transport: () => this.transport,
|
|
10494
10863
|
defaultTimeoutMs: () => this.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC,
|
|
10495
10864
|
resetTimeoutOnProgress: () => this.resetTimeoutOnProgress,
|
|
10496
|
-
|
|
10865
|
+
annotateError: (error, method) => this.annotateRequestError(error, method)
|
|
10497
10866
|
});
|
|
10498
10867
|
dispatchTaskRequest = (request, options) => this.rawWire.dispatch(request, options);
|
|
10499
10868
|
// Every outbound request still awaiting a response, keyed by JSON-RPC id.
|
|
@@ -10513,11 +10882,16 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10513
10882
|
// `GET` on Streamable HTTP). Both per-session, reset with the maps above.
|
|
10514
10883
|
lastResponse;
|
|
10515
10884
|
notificationStream;
|
|
10885
|
+
// Each tracked `429`'s `Retry-After`, by the JSON-RPC id that drew it, until
|
|
10886
|
+
// that id's failed send claims it (#2442). Per-session, reset with the
|
|
10887
|
+
// diagnostics above.
|
|
10888
|
+
rateLimits = new RateLimitLedger();
|
|
10516
10889
|
/** True when the cached transport was built with an OAuth authProvider attached. */
|
|
10517
10890
|
transportHasAuthProvider = false;
|
|
10518
10891
|
/** Dedupes concurrent ambient auth challenges (reason + scopes). */
|
|
10519
10892
|
ambientAuthChallengeInFlight = /* @__PURE__ */ new Map();
|
|
10520
10893
|
pipeStderr;
|
|
10894
|
+
connectionTimeoutRemedy;
|
|
10521
10895
|
initialLoggingLevel;
|
|
10522
10896
|
// Modern-era per-request log level (#1629). On 2026-07-28 servers
|
|
10523
10897
|
// `logging/setLevel` is gone; the client opts into logs per request via the
|
|
@@ -10656,6 +11030,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10656
11030
|
// site to the two failure handlers. Cleared by any user-initiated refresh (a
|
|
10657
11031
|
// subscribe/unsubscribe is a fresh attempt, and the server may have changed).
|
|
10658
11032
|
modernNeverAcknowledged = false;
|
|
11033
|
+
// Set when the last `listen()` was refused at the HTTP layer with a status a
|
|
11034
|
+
// retry cannot change (#2722) — the sibling of `modernNeverAcknowledged`,
|
|
11035
|
+
// carried and cleared the same way, holding the HTTP answer the UI reports.
|
|
11036
|
+
modernListenRefusal = null;
|
|
10659
11037
|
/** Which in-flight task calls own each progress token (#2316). */
|
|
10660
11038
|
taskProgress = new TaskProgressRouter();
|
|
10661
11039
|
// Abort controller for the in-flight ordinary (non-task) tool call. Aborting
|
|
@@ -10755,6 +11133,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10755
11133
|
this.fetchFn = options.environment.fetch;
|
|
10756
11134
|
this.logger = options.environment.logger ?? silentLogger;
|
|
10757
11135
|
this.pipeStderr = options.pipeStderr ?? false;
|
|
11136
|
+
this.connectionTimeoutRemedy = options.connectionTimeoutRemedy ?? WEB_CONNECTION_TIMEOUT_REMEDY;
|
|
10758
11137
|
this.initialLoggingLevel = options.initialLoggingLevel;
|
|
10759
11138
|
this.sample = options.sample ?? true;
|
|
10760
11139
|
this.elicit = options.elicit ?? true;
|
|
@@ -10912,7 +11291,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10912
11291
|
this.clientInfo,
|
|
10913
11292
|
Object.keys(clientOptions).length > 0 ? clientOptions : void 0
|
|
10914
11293
|
);
|
|
10915
|
-
this.
|
|
11294
|
+
this.annotateSdkRequestErrors(this.client);
|
|
10916
11295
|
if (this.tasksCapabilityAdvertised) {
|
|
10917
11296
|
this.bindReceiverTasks();
|
|
10918
11297
|
}
|
|
@@ -10920,7 +11299,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10920
11299
|
/**
|
|
10921
11300
|
* Decorate the SDK Client's `request` so a per-request timeout rejects with
|
|
10922
11301
|
* the connection's state at that moment instead of a bare `Request timed
|
|
10923
|
-
* out` (#2318)
|
|
11302
|
+
* out` (#2318), and a `429` rejects as rate limiting with the server's
|
|
11303
|
+
* `Retry-After` instead of a bare `Error POSTing to endpoint` (#2442).
|
|
10924
11304
|
*
|
|
10925
11305
|
* `Protocol.request` is the one funnel every SDK verb goes through —
|
|
10926
11306
|
* `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
|
|
@@ -10938,21 +11318,30 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10938
11318
|
* implementation signature serves both at runtime; the single cast on the
|
|
10939
11319
|
* way back is what the overloads cost.
|
|
10940
11320
|
*/
|
|
10941
|
-
|
|
11321
|
+
annotateSdkRequestErrors(client) {
|
|
10942
11322
|
const sdkRequest = client.request.bind(client);
|
|
10943
11323
|
const decorated = async (...args) => {
|
|
10944
11324
|
try {
|
|
10945
11325
|
return await sdkRequest(...args);
|
|
10946
11326
|
} catch (err) {
|
|
10947
|
-
throw
|
|
10948
|
-
err,
|
|
10949
|
-
args[0].method,
|
|
10950
|
-
this.getConnectionDiagnostics()
|
|
10951
|
-
);
|
|
11327
|
+
throw this.annotateRequestError(err, args[0].method);
|
|
10952
11328
|
}
|
|
10953
11329
|
};
|
|
10954
11330
|
client.request = decorated;
|
|
10955
11331
|
}
|
|
11332
|
+
/**
|
|
11333
|
+
* The one place a failed request's error is decorated, shared by the SDK
|
|
11334
|
+
* funnel above and the raw-wire channel that bypasses it (modern-era tool
|
|
11335
|
+
* calls and task operations), so a caller sees the same error whichever
|
|
11336
|
+
* path its request took: a timeout with the connection's state (#2318), a
|
|
11337
|
+
* `429` named as rate limiting (#2442), anything else untouched.
|
|
11338
|
+
*/
|
|
11339
|
+
annotateRequestError(err, method) {
|
|
11340
|
+
return annotateRateLimit(
|
|
11341
|
+
annotateRequestTimeout(err, method, this.getConnectionDiagnostics()),
|
|
11342
|
+
method
|
|
11343
|
+
);
|
|
11344
|
+
}
|
|
10956
11345
|
/**
|
|
10957
11346
|
* @param base - the composed OAuth-path fetch, deadline innermost (#2319).
|
|
10958
11347
|
*
|
|
@@ -10996,9 +11385,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10996
11385
|
},
|
|
10997
11386
|
trackSendFailure: (message, error) => {
|
|
10998
11387
|
if (isRequestTimeoutError(error)) return;
|
|
11388
|
+
attachRetryAfter(error, this.rateLimits.take(message.id));
|
|
10999
11389
|
if (this.outstandingRequests.delete(message.id)) {
|
|
11000
11390
|
this.dispatchConnectionDiagnosticsChange();
|
|
11001
11391
|
}
|
|
11392
|
+
this.dispatchTypedEvent("requestFailed", {
|
|
11393
|
+
id: message.id,
|
|
11394
|
+
reason: describeSendFailure(error)
|
|
11395
|
+
});
|
|
11002
11396
|
},
|
|
11003
11397
|
trackResponse: (message, origin) => {
|
|
11004
11398
|
const responseId = message.id;
|
|
@@ -11064,6 +11458,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11064
11458
|
};
|
|
11065
11459
|
baseTransport.onerror = (error) => {
|
|
11066
11460
|
if (this.status === "connecting") return;
|
|
11461
|
+
if (subscriptionListenRefusal(error) !== void 0) return;
|
|
11067
11462
|
this.status = "error";
|
|
11068
11463
|
this.dispatchTypedEvent("statusChange", this.status);
|
|
11069
11464
|
this.dispatchTypedEvent("error", error);
|
|
@@ -11309,6 +11704,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11309
11704
|
this.clearModernReconnectTimer();
|
|
11310
11705
|
this.modernReconnectAttempts = 0;
|
|
11311
11706
|
this.modernNeverAcknowledged = false;
|
|
11707
|
+
this.modernListenRefusal = null;
|
|
11312
11708
|
this.modernSubscription = null;
|
|
11313
11709
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
11314
11710
|
this.dispatchSubscriptionsChange();
|
|
@@ -11347,6 +11743,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11347
11743
|
this.taskProgress.clear();
|
|
11348
11744
|
this.lastResponse = void 0;
|
|
11349
11745
|
this.notificationStream = void 0;
|
|
11746
|
+
this.rateLimits.clear();
|
|
11350
11747
|
this.dispatchConnectionDiagnosticsChange();
|
|
11351
11748
|
if (this.malformedListItems.size > 0) {
|
|
11352
11749
|
this.malformedListItems.clear();
|
|
@@ -11449,6 +11846,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11449
11846
|
onFetchRequest: (entry) => {
|
|
11450
11847
|
this.dispatchFetchRequest({ ...entry, category: "transport" });
|
|
11451
11848
|
this.noteTransportStream(entry);
|
|
11849
|
+
this.rateLimits.record(entry);
|
|
11452
11850
|
},
|
|
11453
11851
|
onFetchResponseBody: (id, body) => {
|
|
11454
11852
|
this.dispatchFetchRequestBodyUpdate(id, body);
|
|
@@ -11496,9 +11894,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11496
11894
|
// for that upstream gap and should be deleted with it.
|
|
11497
11895
|
//
|
|
11498
11896
|
// Known, accepted side effect of turning intercept on with no stored
|
|
11499
|
-
// tokens: `parseAuthChallengeFromResponse` treats 403
|
|
11500
|
-
// too, so a probe answered
|
|
11501
|
-
//
|
|
11897
|
+
// tokens: `parseAuthChallengeFromResponse` treats a 403 carrying
|
|
11898
|
+
// `WWW-Authenticate` as a challenge too, so a probe answered that way
|
|
11899
|
+
// for a *non-auth* reason (a gateway rejecting the unknown
|
|
11900
|
+
// `server/discover` method, say) now starts OAuth
|
|
11502
11901
|
// discovery instead of letting "auto" fall back to the legacy
|
|
11503
11902
|
// `initialize`. The outcome is a surfaced `oauthError`, not a hang, and
|
|
11504
11903
|
// it goes away with this clause.
|
|
@@ -11565,7 +11964,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11565
11964
|
const timeoutPromise = new Promise((_, reject) => {
|
|
11566
11965
|
timer = setTimeout(() => {
|
|
11567
11966
|
connectTimedOut = true;
|
|
11568
|
-
reject(
|
|
11967
|
+
reject(
|
|
11968
|
+
new Error(
|
|
11969
|
+
connectionTimeoutMessage(
|
|
11970
|
+
connectTimeoutMs,
|
|
11971
|
+
this.connectionTimeoutRemedy
|
|
11972
|
+
)
|
|
11973
|
+
)
|
|
11974
|
+
);
|
|
11569
11975
|
}, connectTimeoutMs);
|
|
11570
11976
|
});
|
|
11571
11977
|
try {
|
|
@@ -13780,11 +14186,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13780
14186
|
// for page one.
|
|
13781
14187
|
...cursor !== void 0 ? { cursor } : {}
|
|
13782
14188
|
};
|
|
14189
|
+
const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
|
|
13783
14190
|
try {
|
|
13784
14191
|
return await this.invokeMcpClient(
|
|
13785
14192
|
() => this.client.request(
|
|
13786
14193
|
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
|
|
13787
|
-
|
|
14194
|
+
resultSchema,
|
|
13788
14195
|
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13789
14196
|
),
|
|
13790
14197
|
{ method: RESOURCES_DIRECTORY_READ_METHOD }
|
|
@@ -14239,6 +14646,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14239
14646
|
this.clearModernReconnectTimer();
|
|
14240
14647
|
this.modernReconnectAttempts = 0;
|
|
14241
14648
|
this.modernNeverAcknowledged = false;
|
|
14649
|
+
this.modernListenRefusal = null;
|
|
14242
14650
|
}
|
|
14243
14651
|
const generation = ++this.modernListenGeneration;
|
|
14244
14652
|
const previous = this.modernSubscription;
|
|
@@ -14257,8 +14665,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14257
14665
|
this.getRequestOptions()
|
|
14258
14666
|
);
|
|
14259
14667
|
} catch (error) {
|
|
14260
|
-
if (generation === this.modernListenGeneration
|
|
14261
|
-
|
|
14668
|
+
if (generation === this.modernListenGeneration) {
|
|
14669
|
+
if (isNeverAcknowledgedSubscriptionClose(error)) {
|
|
14670
|
+
this.modernNeverAcknowledged = true;
|
|
14671
|
+
}
|
|
14672
|
+
this.modernListenRefusal = subscriptionListenRefusal(error) ?? null;
|
|
14262
14673
|
}
|
|
14263
14674
|
throw error;
|
|
14264
14675
|
}
|
|
@@ -14339,6 +14750,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14339
14750
|
* its error either way — the retry is about the subscriptions, not the call.
|
|
14340
14751
|
*/
|
|
14341
14752
|
reconcileModernStreamStateAfterFailedRefresh() {
|
|
14753
|
+
if (this.modernListenRefusal) {
|
|
14754
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
14755
|
+
return;
|
|
14756
|
+
}
|
|
14342
14757
|
if (!this.wantsModernStream()) {
|
|
14343
14758
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
14344
14759
|
return;
|
|
@@ -14371,6 +14786,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14371
14786
|
honoredUris: []
|
|
14372
14787
|
});
|
|
14373
14788
|
}
|
|
14789
|
+
/**
|
|
14790
|
+
* Settle the stream on an HTTP refusal of the listen (#2722): the
|
|
14791
|
+
* `"refused"` status carrying the HTTP answer, no reconnect, and a log line
|
|
14792
|
+
* with the sentence the UI shows. Reported whatever `active` says — see
|
|
14793
|
+
* `ResourceSubscriptionStreamState`.
|
|
14794
|
+
*/
|
|
14795
|
+
endModernStreamRefused(refusal) {
|
|
14796
|
+
this.clearModernReconnectTimer();
|
|
14797
|
+
const message = subscriptionListenRefusalMessage(refusal);
|
|
14798
|
+
this.logger.warn({ message }, "subscriptions/listen refused over HTTP");
|
|
14799
|
+
this.setModernStreamState({
|
|
14800
|
+
active: this.modernStreamActive(),
|
|
14801
|
+
status: "refused",
|
|
14802
|
+
honoredUris: [],
|
|
14803
|
+
refusal
|
|
14804
|
+
});
|
|
14805
|
+
}
|
|
14374
14806
|
/**
|
|
14375
14807
|
* Schedule a reconnect re-listen after the current backoff delay (#1630).
|
|
14376
14808
|
* `modernReconnectAttempts` reflects the number of *consecutive failed*
|
|
@@ -14408,6 +14840,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14408
14840
|
this.endModernStreamNeverAcknowledged();
|
|
14409
14841
|
return;
|
|
14410
14842
|
}
|
|
14843
|
+
if (this.modernListenRefusal && !isTerminalStatus(this.status)) {
|
|
14844
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
14845
|
+
return;
|
|
14846
|
+
}
|
|
14411
14847
|
this.modernReconnectAttempts += 1;
|
|
14412
14848
|
if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
|
|
14413
14849
|
this.setModernStreamState({
|
|
@@ -14562,8 +14998,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14562
14998
|
* Re-read OAuth storage and test whether a challenge is already satisfied.
|
|
14563
14999
|
* See {@link OAuthManager.checkAuthChallengeSatisfied}.
|
|
14564
15000
|
*/
|
|
14565
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
14566
|
-
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
15001
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
15002
|
+
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
15003
|
+
challenge,
|
|
15004
|
+
options
|
|
15005
|
+
);
|
|
14567
15006
|
}
|
|
14568
15007
|
/**
|
|
14569
15008
|
* Push recovered OAuth auth state to the remote backend (same MCP session).
|
|
@@ -14727,8 +15166,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14727
15166
|
* Direct transports reconnect after token exchange so the live MCP session
|
|
14728
15167
|
* picks up the new Bearer token (mirrors silent recovery reconnect).
|
|
14729
15168
|
*/
|
|
14730
|
-
async completeOAuthFlow(authorizationCode, iss) {
|
|
14731
|
-
await this.ensureOAuthManager().completeOAuthFlow(
|
|
15169
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
15170
|
+
await this.ensureOAuthManager().completeOAuthFlow(
|
|
15171
|
+
authorizationCode,
|
|
15172
|
+
iss,
|
|
15173
|
+
state
|
|
15174
|
+
);
|
|
14732
15175
|
if (this.usesDirectAuthRecovery()) {
|
|
14733
15176
|
await this.reconnectAfterAuthRecovery();
|
|
14734
15177
|
}
|
|
@@ -14752,7 +15195,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14752
15195
|
* Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
|
|
14753
15196
|
*/
|
|
14754
15197
|
async resumeAfterOAuth(authorizationCode, options) {
|
|
14755
|
-
await this.completeOAuthFlow(
|
|
15198
|
+
await this.completeOAuthFlow(
|
|
15199
|
+
authorizationCode,
|
|
15200
|
+
options?.iss,
|
|
15201
|
+
options?.state
|
|
15202
|
+
);
|
|
14756
15203
|
const remoteSessionId = options?.remoteSessionId;
|
|
14757
15204
|
const transport = this.baseTransport;
|
|
14758
15205
|
if (remoteSessionId && transport instanceof RemoteClientTransport) {
|
|
@@ -14843,6 +15290,11 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
14843
15290
|
// rendered by the list panel instead of vanishing into an unhandled
|
|
14844
15291
|
// rejection — which read as "this server has no tools" (#1953).
|
|
14845
15292
|
error = null;
|
|
15293
|
+
// Whether a fetch has committed a list this session. Until it has, the empty
|
|
15294
|
+
// `items` mean "not loaded yet", not "the server has none" — a distinction a
|
|
15295
|
+
// count of the list cannot make from `items` alone (#2607). Set and cleared
|
|
15296
|
+
// just before the `*Change` dispatch, so a reader keyed on that event sees it.
|
|
15297
|
+
loaded = false;
|
|
14846
15298
|
config;
|
|
14847
15299
|
// Debounce a burst of `list_changed` notifications into a single
|
|
14848
15300
|
// refresh (or one indicator light) once it settles.
|
|
@@ -14878,6 +15330,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
14878
15330
|
this.listChangedTimer = null;
|
|
14879
15331
|
}
|
|
14880
15332
|
this.items = [];
|
|
15333
|
+
this.loaded = false;
|
|
14881
15334
|
this.dispatchChange();
|
|
14882
15335
|
this.setListChanged(false);
|
|
14883
15336
|
this.setError(null);
|
|
@@ -14936,6 +15389,14 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
14936
15389
|
getItems() {
|
|
14937
15390
|
return [...this.items];
|
|
14938
15391
|
}
|
|
15392
|
+
/**
|
|
15393
|
+
* Whether a fetch has committed a list since the last connect. False while
|
|
15394
|
+
* the connect-time load is in flight, when it was deferred (paginated mode),
|
|
15395
|
+
* and after a disconnect. Changes only alongside the `*Change` event (#2607).
|
|
15396
|
+
*/
|
|
15397
|
+
isLoaded() {
|
|
15398
|
+
return this.loaded;
|
|
15399
|
+
}
|
|
14939
15400
|
/** Whether a `list_changed` arrived since the last refresh (indicator on). */
|
|
14940
15401
|
getListChanged() {
|
|
14941
15402
|
return this.listChanged;
|
|
@@ -15029,6 +15490,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
15029
15490
|
/** Commit a fetched list as the current one and notify subscribers. */
|
|
15030
15491
|
applyItems(items) {
|
|
15031
15492
|
this.items = items;
|
|
15493
|
+
this.loaded = true;
|
|
15032
15494
|
this.dispatchChange();
|
|
15033
15495
|
}
|
|
15034
15496
|
dispatchChange() {
|
|
@@ -15039,6 +15501,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
15039
15501
|
this.unsubscribe?.();
|
|
15040
15502
|
this.unsubscribe = null;
|
|
15041
15503
|
this.items = [];
|
|
15504
|
+
this.loaded = false;
|
|
15042
15505
|
}
|
|
15043
15506
|
};
|
|
15044
15507
|
|
|
@@ -15122,6 +15585,16 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
15122
15585
|
return;
|
|
15123
15586
|
}
|
|
15124
15587
|
};
|
|
15588
|
+
const onRequestFailed = (event) => {
|
|
15589
|
+
const { id, reason } = event.detail;
|
|
15590
|
+
const requestEntry = this.pendingRequestEntries.get(id);
|
|
15591
|
+
if (!requestEntry) return;
|
|
15592
|
+
this.pendingRequestEntries.delete(id);
|
|
15593
|
+
requestEntry.clientError = reason;
|
|
15594
|
+
requestEntry.duration = Date.now() - requestEntry.timestamp.getTime();
|
|
15595
|
+
this.dispatchTypedEvent("message", requestEntry);
|
|
15596
|
+
this.dispatchTypedEvent("messagesChange", this.getMessages());
|
|
15597
|
+
};
|
|
15125
15598
|
const onStatusChange = () => {
|
|
15126
15599
|
if (isTerminalStatus(this.client?.getStatus())) {
|
|
15127
15600
|
this.messages = [];
|
|
@@ -15131,11 +15604,13 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
15131
15604
|
};
|
|
15132
15605
|
this.client.addEventListener("message", onMessage);
|
|
15133
15606
|
this.client.addEventListener("responseRejected", onResponseRejected);
|
|
15607
|
+
this.client.addEventListener("requestFailed", onRequestFailed);
|
|
15134
15608
|
this.client.addEventListener("statusChange", onStatusChange);
|
|
15135
15609
|
this.unsubscribe = () => {
|
|
15136
15610
|
if (this.client) {
|
|
15137
15611
|
this.client.removeEventListener("message", onMessage);
|
|
15138
15612
|
this.client.removeEventListener("responseRejected", onResponseRejected);
|
|
15613
|
+
this.client.removeEventListener("requestFailed", onRequestFailed);
|
|
15139
15614
|
this.client.removeEventListener("statusChange", onStatusChange);
|
|
15140
15615
|
}
|
|
15141
15616
|
this.client = null;
|
|
@@ -16087,6 +16562,7 @@ function useListError(state) {
|
|
|
16087
16562
|
var NO_TOOLS = [];
|
|
16088
16563
|
var readTools = (state) => state.getTools();
|
|
16089
16564
|
var readListChanged = (state) => state.getListChanged();
|
|
16565
|
+
var readLoaded = (state) => state.isLoaded();
|
|
16090
16566
|
function useManagedTools(client, managedToolsState) {
|
|
16091
16567
|
const tools = useStoreSnapshot(
|
|
16092
16568
|
managedToolsState,
|
|
@@ -16100,6 +16576,12 @@ function useManagedTools(client, managedToolsState) {
|
|
|
16100
16576
|
readListChanged,
|
|
16101
16577
|
false
|
|
16102
16578
|
);
|
|
16579
|
+
const loaded = useStoreSnapshot(
|
|
16580
|
+
managedToolsState,
|
|
16581
|
+
"toolsChange",
|
|
16582
|
+
readLoaded,
|
|
16583
|
+
false
|
|
16584
|
+
);
|
|
16103
16585
|
const error = useListError(managedToolsState);
|
|
16104
16586
|
const refresh = useCallback4(async () => {
|
|
16105
16587
|
if (!managedToolsState || !client) return NO_TOOLS;
|
|
@@ -16109,7 +16591,7 @@ function useManagedTools(client, managedToolsState) {
|
|
|
16109
16591
|
const clearListChanged = useCallback4(() => {
|
|
16110
16592
|
managedToolsState?.clearListChanged();
|
|
16111
16593
|
}, [managedToolsState]);
|
|
16112
|
-
return { tools, error, listChanged, refresh, clearListChanged };
|
|
16594
|
+
return { tools, error, listChanged, loaded, refresh, clearListChanged };
|
|
16113
16595
|
}
|
|
16114
16596
|
|
|
16115
16597
|
// ../../core/react/useManagedResources.ts
|
|
@@ -16117,6 +16599,7 @@ import { useCallback as useCallback5 } from "react";
|
|
|
16117
16599
|
var NO_RESOURCES = [];
|
|
16118
16600
|
var readResources = (state) => state.getResources();
|
|
16119
16601
|
var readListChanged2 = (state) => state.getListChanged();
|
|
16602
|
+
var readLoaded2 = (state) => state.isLoaded();
|
|
16120
16603
|
function useManagedResources(client, managedResourcesState) {
|
|
16121
16604
|
const resources = useStoreSnapshot(
|
|
16122
16605
|
managedResourcesState,
|
|
@@ -16130,6 +16613,12 @@ function useManagedResources(client, managedResourcesState) {
|
|
|
16130
16613
|
readListChanged2,
|
|
16131
16614
|
false
|
|
16132
16615
|
);
|
|
16616
|
+
const loaded = useStoreSnapshot(
|
|
16617
|
+
managedResourcesState,
|
|
16618
|
+
"resourcesChange",
|
|
16619
|
+
readLoaded2,
|
|
16620
|
+
false
|
|
16621
|
+
);
|
|
16133
16622
|
const error = useListError(managedResourcesState);
|
|
16134
16623
|
const refresh = useCallback5(async () => {
|
|
16135
16624
|
if (!managedResourcesState || !client) return NO_RESOURCES;
|
|
@@ -16139,7 +16628,7 @@ function useManagedResources(client, managedResourcesState) {
|
|
|
16139
16628
|
const clearListChanged = useCallback5(() => {
|
|
16140
16629
|
managedResourcesState?.clearListChanged();
|
|
16141
16630
|
}, [managedResourcesState]);
|
|
16142
|
-
return { resources, error, listChanged, refresh, clearListChanged };
|
|
16631
|
+
return { resources, error, listChanged, loaded, refresh, clearListChanged };
|
|
16143
16632
|
}
|
|
16144
16633
|
|
|
16145
16634
|
// ../../core/react/useManagedResourceTemplates.ts
|
|
@@ -16168,6 +16657,7 @@ import { useCallback as useCallback7 } from "react";
|
|
|
16168
16657
|
var NO_PROMPTS = [];
|
|
16169
16658
|
var readPrompts = (state) => state.getPrompts();
|
|
16170
16659
|
var readListChanged3 = (state) => state.getListChanged();
|
|
16660
|
+
var readLoaded3 = (state) => state.isLoaded();
|
|
16171
16661
|
function useManagedPrompts(client, managedPromptsState) {
|
|
16172
16662
|
const prompts = useStoreSnapshot(
|
|
16173
16663
|
managedPromptsState,
|
|
@@ -16181,6 +16671,12 @@ function useManagedPrompts(client, managedPromptsState) {
|
|
|
16181
16671
|
readListChanged3,
|
|
16182
16672
|
false
|
|
16183
16673
|
);
|
|
16674
|
+
const loaded = useStoreSnapshot(
|
|
16675
|
+
managedPromptsState,
|
|
16676
|
+
"promptsChange",
|
|
16677
|
+
readLoaded3,
|
|
16678
|
+
false
|
|
16679
|
+
);
|
|
16184
16680
|
const error = useListError(managedPromptsState);
|
|
16185
16681
|
const refresh = useCallback7(async () => {
|
|
16186
16682
|
if (!managedPromptsState || !client) return NO_PROMPTS;
|
|
@@ -16190,7 +16686,7 @@ function useManagedPrompts(client, managedPromptsState) {
|
|
|
16190
16686
|
const clearListChanged = useCallback7(() => {
|
|
16191
16687
|
managedPromptsState?.clearListChanged();
|
|
16192
16688
|
}, [managedPromptsState]);
|
|
16193
|
-
return { prompts, error, listChanged, refresh, clearListChanged };
|
|
16689
|
+
return { prompts, error, listChanged, loaded, refresh, clearListChanged };
|
|
16194
16690
|
}
|
|
16195
16691
|
|
|
16196
16692
|
// ../../core/react/useManagedSkills.ts
|
|
@@ -16367,34 +16863,6 @@ function classifyAuthorizationServerMismatch(err) {
|
|
|
16367
16863
|
};
|
|
16368
16864
|
}
|
|
16369
16865
|
|
|
16370
|
-
// ../../core/node/version.ts
|
|
16371
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
16372
|
-
import { dirname as dirname5, join as join3, parse } from "path";
|
|
16373
|
-
import { fileURLToPath } from "url";
|
|
16374
|
-
var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
|
16375
|
-
function readInspectorVersion(callerUrl) {
|
|
16376
|
-
const startDir = dirname5(fileURLToPath(callerUrl));
|
|
16377
|
-
const { root } = parse(startDir);
|
|
16378
|
-
let dir = startDir;
|
|
16379
|
-
for (; ; ) {
|
|
16380
|
-
let manifest;
|
|
16381
|
-
try {
|
|
16382
|
-
manifest = JSON.parse(
|
|
16383
|
-
readFileSync4(join3(dir, "package.json"), "utf-8")
|
|
16384
|
-
);
|
|
16385
|
-
} catch {
|
|
16386
|
-
}
|
|
16387
|
-
if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
|
|
16388
|
-
return manifest.version;
|
|
16389
|
-
}
|
|
16390
|
-
if (dir === root) break;
|
|
16391
|
-
dir = dirname5(dir);
|
|
16392
|
-
}
|
|
16393
|
-
throw new Error(
|
|
16394
|
-
`Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`
|
|
16395
|
-
);
|
|
16396
|
-
}
|
|
16397
|
-
|
|
16398
16866
|
// ../../core/auth/oauth-storage.ts
|
|
16399
16867
|
import {
|
|
16400
16868
|
OAuthClientInformationSchema as OAuthClientInformationSchema2,
|
|
@@ -16910,7 +17378,7 @@ function serializeOAuthPersistBlob(snapshot) {
|
|
|
16910
17378
|
import { resolve as resolve6 } from "path";
|
|
16911
17379
|
|
|
16912
17380
|
// ../../core/auth/node/oauth-secrets.ts
|
|
16913
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
17381
|
+
import { createHmac, randomUUID as randomUUID2 } from "crypto";
|
|
16914
17382
|
import { OAuthTokensSchema as OAuthTokensSchema5 } from "@modelcontextprotocol/core";
|
|
16915
17383
|
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
16916
17384
|
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
@@ -16943,6 +17411,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
|
|
|
16943
17411
|
var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
|
|
16944
17412
|
var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
|
|
16945
17413
|
var IDP_SESSION_FIELD = "idp-session";
|
|
17414
|
+
var clientCredentialStampField = (field) => `${field}#owner`;
|
|
17415
|
+
var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
|
|
17416
|
+
function clientCredentialStamp(clientId, value) {
|
|
17417
|
+
return JSON.stringify({
|
|
17418
|
+
client_id: clientId,
|
|
17419
|
+
mac: credentialMac(clientId, value)
|
|
17420
|
+
});
|
|
17421
|
+
}
|
|
17422
|
+
function stampedOwner(stamp, value) {
|
|
17423
|
+
if (stamp === void 0) return void 0;
|
|
17424
|
+
let parsed;
|
|
17425
|
+
try {
|
|
17426
|
+
parsed = JSON.parse(stamp);
|
|
17427
|
+
} catch {
|
|
17428
|
+
return void 0;
|
|
17429
|
+
}
|
|
17430
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
17431
|
+
const { client_id: clientId, mac } = parsed;
|
|
17432
|
+
if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
|
|
17433
|
+
return void 0;
|
|
17434
|
+
}
|
|
17435
|
+
return clientId;
|
|
17436
|
+
}
|
|
16946
17437
|
function applyTokensPolicy(tokens, policy) {
|
|
16947
17438
|
if (policy === "none") return void 0;
|
|
16948
17439
|
if (policy === "access" && tokens.refresh_token !== void 0) {
|
|
@@ -16971,12 +17462,18 @@ function splitClientInformation(info, fields, secrets) {
|
|
|
16971
17462
|
if (client_secret === void 0 && registration_access_token === void 0) {
|
|
16972
17463
|
return info;
|
|
16973
17464
|
}
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
16979
|
-
|
|
17465
|
+
const put = (field, value) => {
|
|
17466
|
+
if (value === void 0) return;
|
|
17467
|
+
secrets[field] = value;
|
|
17468
|
+
if (fields.stamped && typeof info.client_id === "string") {
|
|
17469
|
+
secrets[clientCredentialStampField(field)] = clientCredentialStamp(
|
|
17470
|
+
info.client_id,
|
|
17471
|
+
value
|
|
17472
|
+
);
|
|
17473
|
+
}
|
|
17474
|
+
};
|
|
17475
|
+
put(fields.clientSecret, client_secret);
|
|
17476
|
+
put(fields.registrationToken, registration_access_token);
|
|
16980
17477
|
return publicInfo;
|
|
16981
17478
|
}
|
|
16982
17479
|
function joinClientInformation(info, fields, secrets) {
|
|
@@ -16995,15 +17492,18 @@ function joinClientInformation(info, fields, secrets) {
|
|
|
16995
17492
|
}
|
|
16996
17493
|
var issuerClientInfoFields = (issuer) => ({
|
|
16997
17494
|
clientSecret: issuerClientSecretField(issuer),
|
|
16998
|
-
registrationToken: issuerRegistrationTokenField(issuer)
|
|
17495
|
+
registrationToken: issuerRegistrationTokenField(issuer),
|
|
17496
|
+
stamped: true
|
|
16999
17497
|
});
|
|
17000
17498
|
var LEGACY_CLIENT_INFO_FIELDS = {
|
|
17001
17499
|
clientSecret: LEGACY_CLIENT_SECRET_FIELD,
|
|
17002
|
-
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
|
|
17500
|
+
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
17501
|
+
stamped: true
|
|
17003
17502
|
};
|
|
17004
17503
|
var PREREG_CLIENT_INFO_FIELDS = {
|
|
17005
17504
|
clientSecret: PREREG_CLIENT_SECRET_FIELD,
|
|
17006
|
-
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
|
|
17505
|
+
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
|
|
17506
|
+
stamped: false
|
|
17007
17507
|
};
|
|
17008
17508
|
function splitServerOAuthState(state, policy) {
|
|
17009
17509
|
const secrets = {};
|
|
@@ -17153,20 +17653,54 @@ function joinIdpSession(residue, secrets) {
|
|
|
17153
17653
|
return stored ? { ...residue, ...stored } : { ...residue };
|
|
17154
17654
|
}
|
|
17155
17655
|
function serverSecretFields(state) {
|
|
17656
|
+
const stamped = (field) => [
|
|
17657
|
+
field,
|
|
17658
|
+
clientCredentialStampField(field)
|
|
17659
|
+
];
|
|
17156
17660
|
const fields = /* @__PURE__ */ new Set([
|
|
17157
17661
|
LEGACY_TOKENS_FIELD,
|
|
17158
|
-
LEGACY_CLIENT_SECRET_FIELD,
|
|
17159
|
-
LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
17662
|
+
...stamped(LEGACY_CLIENT_SECRET_FIELD),
|
|
17663
|
+
...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
|
|
17160
17664
|
PREREG_CLIENT_SECRET_FIELD,
|
|
17161
17665
|
PREREG_REGISTRATION_TOKEN_FIELD
|
|
17162
17666
|
]);
|
|
17163
17667
|
for (const issuer of Object.keys(state?.byIssuer ?? {})) {
|
|
17164
17668
|
fields.add(issuerTokensField(issuer));
|
|
17165
|
-
|
|
17166
|
-
|
|
17669
|
+
for (const field of stamped(issuerClientSecretField(issuer))) {
|
|
17670
|
+
fields.add(field);
|
|
17671
|
+
}
|
|
17672
|
+
for (const field of stamped(issuerRegistrationTokenField(issuer))) {
|
|
17673
|
+
fields.add(field);
|
|
17674
|
+
}
|
|
17167
17675
|
}
|
|
17168
17676
|
return [...fields];
|
|
17169
17677
|
}
|
|
17678
|
+
function clientCredentialOwners(state) {
|
|
17679
|
+
const owners = /* @__PURE__ */ new Map();
|
|
17680
|
+
const add2 = (info, fields) => {
|
|
17681
|
+
if (typeof info?.client_id !== "string") return;
|
|
17682
|
+
owners.set(fields.clientSecret, info.client_id);
|
|
17683
|
+
owners.set(fields.registrationToken, info.client_id);
|
|
17684
|
+
};
|
|
17685
|
+
add2(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
|
|
17686
|
+
for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
|
|
17687
|
+
add2(slot.clientInformation, issuerClientInfoFields(issuer));
|
|
17688
|
+
}
|
|
17689
|
+
return owners;
|
|
17690
|
+
}
|
|
17691
|
+
function restampClientCredentials(state, secrets) {
|
|
17692
|
+
const out = { ...secrets };
|
|
17693
|
+
for (const [field, clientId] of clientCredentialOwners(state)) {
|
|
17694
|
+
const stampField = clientCredentialStampField(field);
|
|
17695
|
+
const value = Object.hasOwn(out, field) ? out[field] : void 0;
|
|
17696
|
+
if (value === void 0) {
|
|
17697
|
+
delete out[stampField];
|
|
17698
|
+
} else if (stampedOwner(out[stampField], value) === void 0) {
|
|
17699
|
+
setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
|
|
17700
|
+
}
|
|
17701
|
+
}
|
|
17702
|
+
return out;
|
|
17703
|
+
}
|
|
17170
17704
|
function clientInfoHasPlaintext(info) {
|
|
17171
17705
|
return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
|
|
17172
17706
|
}
|
|
@@ -17207,6 +17741,44 @@ function coveredLocations(location, byLocation) {
|
|
|
17207
17741
|
([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
|
|
17208
17742
|
).map(([recorded, keys]) => ({ location: recorded, keys }));
|
|
17209
17743
|
}
|
|
17744
|
+
var emptyKeys = () => ({
|
|
17745
|
+
servers: /* @__PURE__ */ new Set(),
|
|
17746
|
+
idpSessions: /* @__PURE__ */ new Set(),
|
|
17747
|
+
owners: /* @__PURE__ */ new Map()
|
|
17748
|
+
});
|
|
17749
|
+
function candidateFields(location, byLocation) {
|
|
17750
|
+
const fields = /* @__PURE__ */ new Map();
|
|
17751
|
+
for (const { keys } of coveredLocations(location, byLocation)) {
|
|
17752
|
+
for (const [url, owned] of keys.owners) {
|
|
17753
|
+
const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
|
|
17754
|
+
for (const field of owned.keys()) kept.add(field);
|
|
17755
|
+
fields.set(url, kept);
|
|
17756
|
+
}
|
|
17757
|
+
}
|
|
17758
|
+
return fields;
|
|
17759
|
+
}
|
|
17760
|
+
function addMissingOwners(target, source) {
|
|
17761
|
+
for (const [url, fields] of source) {
|
|
17762
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
17763
|
+
for (const [field, clientId] of fields) {
|
|
17764
|
+
if (!kept.has(field)) kept.set(field, clientId);
|
|
17765
|
+
}
|
|
17766
|
+
if (kept.size > 0) target.set(url, kept);
|
|
17767
|
+
}
|
|
17768
|
+
}
|
|
17769
|
+
function mergeOwners(target, source) {
|
|
17770
|
+
let changed = false;
|
|
17771
|
+
for (const [url, fields] of source) {
|
|
17772
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
17773
|
+
for (const [field, clientId] of fields) {
|
|
17774
|
+
if (kept.get(field) === clientId) continue;
|
|
17775
|
+
kept.set(field, clientId);
|
|
17776
|
+
changed = true;
|
|
17777
|
+
}
|
|
17778
|
+
if (kept.size > 0) target.set(url, kept);
|
|
17779
|
+
}
|
|
17780
|
+
return changed;
|
|
17781
|
+
}
|
|
17210
17782
|
var warnedLedgerFailures = /* @__PURE__ */ new Set();
|
|
17211
17783
|
function warnLedgerFailure(what, error) {
|
|
17212
17784
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -17220,6 +17792,19 @@ function warnLedgerFailure(what, error) {
|
|
|
17220
17792
|
function isRecord5(value) {
|
|
17221
17793
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17222
17794
|
}
|
|
17795
|
+
function parseOwners(value) {
|
|
17796
|
+
const owners = /* @__PURE__ */ new Map();
|
|
17797
|
+
if (!isRecord5(value)) return owners;
|
|
17798
|
+
for (const [url, fields] of Object.entries(value)) {
|
|
17799
|
+
if (!isRecord5(fields)) continue;
|
|
17800
|
+
const byField = /* @__PURE__ */ new Map();
|
|
17801
|
+
for (const [field, clientId] of Object.entries(fields)) {
|
|
17802
|
+
if (typeof clientId === "string") byField.set(field, clientId);
|
|
17803
|
+
}
|
|
17804
|
+
if (byField.size > 0) owners.set(url, byField);
|
|
17805
|
+
}
|
|
17806
|
+
return owners;
|
|
17807
|
+
}
|
|
17223
17808
|
function stringsOf(value) {
|
|
17224
17809
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
17225
17810
|
}
|
|
@@ -17241,7 +17826,9 @@ function parseLedger(raw) {
|
|
|
17241
17826
|
if (!isRecord5(keys)) continue;
|
|
17242
17827
|
byLocation.set(location, {
|
|
17243
17828
|
servers: new Set(stringsOf(keys.servers)),
|
|
17244
|
-
idpSessions: new Set(stringsOf(keys.idpSessions))
|
|
17829
|
+
idpSessions: new Set(stringsOf(keys.idpSessions)),
|
|
17830
|
+
owners: parseOwners(keys.owners),
|
|
17831
|
+
...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
|
|
17245
17832
|
});
|
|
17246
17833
|
}
|
|
17247
17834
|
if (byLocation.size > 0) ledger.set(namespace, byLocation);
|
|
@@ -17255,7 +17842,19 @@ function serializeLedger(ledger) {
|
|
|
17255
17842
|
Object.fromEntries(
|
|
17256
17843
|
[...byLocation].map(([location, keys]) => [
|
|
17257
17844
|
location,
|
|
17258
|
-
{
|
|
17845
|
+
{
|
|
17846
|
+
servers: [...keys.servers],
|
|
17847
|
+
idpSessions: [...keys.idpSessions],
|
|
17848
|
+
...keys.owners.size > 0 && {
|
|
17849
|
+
owners: Object.fromEntries(
|
|
17850
|
+
[...keys.owners].map(([url, fields]) => [
|
|
17851
|
+
url,
|
|
17852
|
+
Object.fromEntries(fields)
|
|
17853
|
+
])
|
|
17854
|
+
)
|
|
17855
|
+
},
|
|
17856
|
+
...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
|
|
17857
|
+
}
|
|
17259
17858
|
])
|
|
17260
17859
|
)
|
|
17261
17860
|
])
|
|
@@ -17267,7 +17866,7 @@ async function writeLedger(stateFilePath, ledger) {
|
|
|
17267
17866
|
if (ledger.size === 0) await deleteStoreFile(path7);
|
|
17268
17867
|
else await writeStoreFile(path7, serializeLedger(ledger));
|
|
17269
17868
|
}
|
|
17270
|
-
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
|
|
17869
|
+
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
|
|
17271
17870
|
try {
|
|
17272
17871
|
const ledger = parseLedger(
|
|
17273
17872
|
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
@@ -17281,7 +17880,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
17281
17880
|
let keys = byLocation.get(location);
|
|
17282
17881
|
let changed = false;
|
|
17283
17882
|
if (!keys) {
|
|
17284
|
-
keys =
|
|
17883
|
+
keys = emptyKeys();
|
|
17285
17884
|
byLocation.set(location, keys);
|
|
17286
17885
|
changed = true;
|
|
17287
17886
|
}
|
|
@@ -17295,12 +17894,105 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
17295
17894
|
keys.idpSessions.add(issuer);
|
|
17296
17895
|
changed = true;
|
|
17297
17896
|
}
|
|
17897
|
+
if (mergeOwners(keys.owners, owners)) changed = true;
|
|
17298
17898
|
if (changed) await writeLedger(stateFilePath, ledger);
|
|
17299
17899
|
} catch (error) {
|
|
17300
17900
|
warnLedgerFailure("update", error);
|
|
17301
17901
|
}
|
|
17302
17902
|
}
|
|
17303
|
-
async function
|
|
17903
|
+
async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
|
|
17904
|
+
const ledger = parseLedger(
|
|
17905
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
17906
|
+
);
|
|
17907
|
+
const location = await secretStoreLocation(secretStore);
|
|
17908
|
+
let changed = false;
|
|
17909
|
+
for (const [namespace, byLocation] of ledger) {
|
|
17910
|
+
if (namespace === into) continue;
|
|
17911
|
+
for (const [recorded, keys] of byLocation) {
|
|
17912
|
+
if (recorded === location || keys.owners.size === 0) continue;
|
|
17913
|
+
if (keys.owedTo === into) continue;
|
|
17914
|
+
keys.owedTo = into;
|
|
17915
|
+
changed = true;
|
|
17916
|
+
}
|
|
17917
|
+
}
|
|
17918
|
+
if (changed) await writeLedger(stateFilePath, ledger);
|
|
17919
|
+
}
|
|
17920
|
+
async function pendingCarries(stateFilePath, secretStore, current) {
|
|
17921
|
+
const ledger = parseLedger(
|
|
17922
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
17923
|
+
);
|
|
17924
|
+
const location = await secretStoreLocation(secretStore);
|
|
17925
|
+
const carries = [];
|
|
17926
|
+
for (const [namespace, byLocation] of ledger) {
|
|
17927
|
+
const owed = coveredLocations(location, byLocation).some(
|
|
17928
|
+
({ keys }) => keys.owedTo === current
|
|
17929
|
+
);
|
|
17930
|
+
if (owed) {
|
|
17931
|
+
carries.push({
|
|
17932
|
+
namespace,
|
|
17933
|
+
fields: candidateFields(location, byLocation)
|
|
17934
|
+
});
|
|
17935
|
+
}
|
|
17936
|
+
}
|
|
17937
|
+
return carries.reverse();
|
|
17938
|
+
}
|
|
17939
|
+
async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
|
|
17940
|
+
let carried = true;
|
|
17941
|
+
for (const [url, fields] of recorded) {
|
|
17942
|
+
for (const field of fields) {
|
|
17943
|
+
const clientId = now.get(url)?.get(field);
|
|
17944
|
+
if (clientId === void 0) continue;
|
|
17945
|
+
try {
|
|
17946
|
+
const from = oauthSecretServerId(url, namespace);
|
|
17947
|
+
const to = oauthSecretServerId(url, current);
|
|
17948
|
+
const stampField = clientCredentialStampField(field);
|
|
17949
|
+
const value = await secretStoreGetStrict(secretStore, from, field);
|
|
17950
|
+
if (value === null) continue;
|
|
17951
|
+
const stamp = await secretStoreGetStrict(secretStore, from, stampField);
|
|
17952
|
+
if (stamp === null || stampedOwner(stamp, value) !== clientId) {
|
|
17953
|
+
continue;
|
|
17954
|
+
}
|
|
17955
|
+
if (await secretStoreGetStrict(secretStore, to, field) !== null) {
|
|
17956
|
+
continue;
|
|
17957
|
+
}
|
|
17958
|
+
let byLocation = ledger.get(current);
|
|
17959
|
+
if (!byLocation) {
|
|
17960
|
+
byLocation = /* @__PURE__ */ new Map();
|
|
17961
|
+
ledger.set(current, byLocation);
|
|
17962
|
+
}
|
|
17963
|
+
const target = byLocation.get(location) ?? emptyKeys();
|
|
17964
|
+
byLocation.set(location, target);
|
|
17965
|
+
const isNew = !target.servers.has(url);
|
|
17966
|
+
target.servers.add(url);
|
|
17967
|
+
const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
|
|
17968
|
+
if (mergeOwners(target.owners, owner) || isNew) {
|
|
17969
|
+
await writeLedger(stateFilePath, ledger);
|
|
17970
|
+
}
|
|
17971
|
+
await secretStore.set(to, stampField, stamp);
|
|
17972
|
+
await secretStore.set(to, field, value);
|
|
17973
|
+
} catch (error) {
|
|
17974
|
+
carried = false;
|
|
17975
|
+
warnLedgerFailure(
|
|
17976
|
+
"carry over a superseded namespace recorded in",
|
|
17977
|
+
error
|
|
17978
|
+
);
|
|
17979
|
+
}
|
|
17980
|
+
}
|
|
17981
|
+
}
|
|
17982
|
+
return carried;
|
|
17983
|
+
}
|
|
17984
|
+
async function recordedNamespaces(stateFilePath) {
|
|
17985
|
+
try {
|
|
17986
|
+
const ledger = parseLedger(
|
|
17987
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
17988
|
+
);
|
|
17989
|
+
return [...ledger.keys()].reverse();
|
|
17990
|
+
} catch (error) {
|
|
17991
|
+
warnLedgerFailure("read", error);
|
|
17992
|
+
return [];
|
|
17993
|
+
}
|
|
17994
|
+
}
|
|
17995
|
+
async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
|
|
17304
17996
|
let ledger;
|
|
17305
17997
|
let location;
|
|
17306
17998
|
try {
|
|
@@ -17313,8 +18005,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
17313
18005
|
return;
|
|
17314
18006
|
}
|
|
17315
18007
|
let changed = false;
|
|
17316
|
-
for (const [namespace, byLocation] of ledger) {
|
|
18008
|
+
for (const [namespace, byLocation] of [...ledger].reverse()) {
|
|
17317
18009
|
if (namespace === current) continue;
|
|
18010
|
+
if (current !== void 0 && coveredLocations(location, byLocation).some(
|
|
18011
|
+
({ keys }) => keys.owedTo === current
|
|
18012
|
+
) && !await carryOwed(
|
|
18013
|
+
stateFilePath,
|
|
18014
|
+
ledger,
|
|
18015
|
+
location,
|
|
18016
|
+
secretStore,
|
|
18017
|
+
namespace,
|
|
18018
|
+
current,
|
|
18019
|
+
candidateFields(location, byLocation),
|
|
18020
|
+
owners
|
|
18021
|
+
)) {
|
|
18022
|
+
break;
|
|
18023
|
+
}
|
|
17318
18024
|
for (const { location: recorded, keys } of coveredLocations(
|
|
17319
18025
|
location,
|
|
17320
18026
|
byLocation
|
|
@@ -17339,12 +18045,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
17339
18045
|
if (!purged || recorded !== location) continue;
|
|
17340
18046
|
byLocation.delete(recorded);
|
|
17341
18047
|
if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
|
|
17342
|
-
const keyring = byLocation.get(KEYRING_LOCATION) ??
|
|
17343
|
-
|
|
17344
|
-
idpSessions: /* @__PURE__ */ new Set()
|
|
17345
|
-
};
|
|
18048
|
+
const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
|
|
18049
|
+
addMissingOwners(keyring.owners, keys.owners);
|
|
17346
18050
|
for (const url of keys.servers) keyring.servers.add(url);
|
|
17347
18051
|
for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
|
|
18052
|
+
keyring.owedTo ??= keys.owedTo;
|
|
17348
18053
|
byLocation.set(KEYRING_LOCATION, keyring);
|
|
17349
18054
|
}
|
|
17350
18055
|
changed = true;
|
|
@@ -17455,7 +18160,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17455
18160
|
const existing = parseSecretsNamespace(raw);
|
|
17456
18161
|
if (existing !== void 0) {
|
|
17457
18162
|
if (locked) {
|
|
17458
|
-
await purgeSupersededNamespaces(
|
|
18163
|
+
await purgeSupersededNamespaces(
|
|
18164
|
+
filePath,
|
|
18165
|
+
secretStore,
|
|
18166
|
+
existing,
|
|
18167
|
+
snapshotClientOwners(parseOAuthPersistBlob(raw))
|
|
18168
|
+
);
|
|
17459
18169
|
}
|
|
17460
18170
|
return existing;
|
|
17461
18171
|
}
|
|
@@ -17463,42 +18173,53 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17463
18173
|
if (raw !== null && snapshot === null) {
|
|
17464
18174
|
throw new OAuthStateFileUnrecognizedError(filePath, "save");
|
|
17465
18175
|
}
|
|
17466
|
-
if (locked) {
|
|
17467
|
-
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
17468
|
-
}
|
|
17469
18176
|
const namespace = newSecretsNamespace();
|
|
17470
|
-
if (snapshot === null)
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
})),
|
|
17477
|
-
...Object.keys(snapshot.idpSessions).map((issuer) => ({
|
|
17478
|
-
legacyId: oauthIdpSecretServerId(issuer),
|
|
17479
|
-
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
17480
|
-
fields: [IDP_SESSION_FIELD]
|
|
17481
|
-
}))
|
|
17482
|
-
];
|
|
17483
|
-
if (moves.length === 0) return namespace;
|
|
18177
|
+
if (snapshot === null || Object.keys(snapshot.servers).length === 0 && Object.keys(snapshot.idpSessions).length === 0) {
|
|
18178
|
+
if (locked) {
|
|
18179
|
+
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
18180
|
+
}
|
|
18181
|
+
return namespace;
|
|
18182
|
+
}
|
|
17484
18183
|
if (!locked) {
|
|
17485
18184
|
throw new SecretStoreUnavailableError(
|
|
17486
18185
|
`Could not save OAuth state: ${filePath} predates per-state-file secret namespaces, and migrating its secret-store entries needs the file lock, which is unavailable here (see the lock warning above). Migrating without it could lose credentials if two processes migrate at once. Nothing was changed; make the lock directory writable and retry \u2014 or, if you cannot, clear this file's stored OAuth state and re-authorize (clearing does not migrate, and the fresh state file mints its namespace without the lock).`
|
|
17487
18186
|
);
|
|
17488
18187
|
}
|
|
18188
|
+
const values = await readEntrySecrets(
|
|
18189
|
+
snapshot,
|
|
18190
|
+
secretStore,
|
|
18191
|
+
void 0,
|
|
18192
|
+
await recordedNamespaces(filePath)
|
|
18193
|
+
);
|
|
18194
|
+
const owners = snapshotClientOwners(snapshot);
|
|
17489
18195
|
await recordNamespaceKeys(
|
|
17490
18196
|
filePath,
|
|
17491
18197
|
secretStore,
|
|
17492
18198
|
namespace,
|
|
17493
18199
|
Object.keys(snapshot.servers),
|
|
17494
|
-
Object.keys(snapshot.idpSessions)
|
|
18200
|
+
Object.keys(snapshot.idpSessions),
|
|
18201
|
+
writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
|
|
17495
18202
|
);
|
|
18203
|
+
await markUncarriedNamespaces(filePath, secretStore, namespace);
|
|
17496
18204
|
const touched = [];
|
|
17497
18205
|
try {
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
18206
|
+
const copies = [
|
|
18207
|
+
// Restamped: a value read from the legacy ids predates stamps, and a
|
|
18208
|
+
// fallback's stamp may belong to a value read from elsewhere (#2734).
|
|
18209
|
+
...[...values.servers].map(([url, secrets]) => ({
|
|
18210
|
+
scopedId: oauthSecretServerId(url, namespace),
|
|
18211
|
+
secrets: restampClientCredentials(
|
|
18212
|
+
getOwnEntry(snapshot.servers, url) ?? {},
|
|
18213
|
+
secrets
|
|
18214
|
+
)
|
|
18215
|
+
})),
|
|
18216
|
+
...[...values.idpSessions].map(([issuer, secrets]) => ({
|
|
18217
|
+
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
18218
|
+
secrets
|
|
18219
|
+
}))
|
|
18220
|
+
];
|
|
18221
|
+
for (const { scopedId, secrets } of copies) {
|
|
18222
|
+
for (const [field, value] of Object.entries(secrets)) {
|
|
17502
18223
|
touched.push({ serverId: scopedId, field, value: null });
|
|
17503
18224
|
await secretStore.set(scopedId, field, value);
|
|
17504
18225
|
}
|
|
@@ -17508,7 +18229,14 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17508
18229
|
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
17509
18230
|
throw error;
|
|
17510
18231
|
}
|
|
17511
|
-
|
|
18232
|
+
await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
|
|
18233
|
+
const legacyIds = [
|
|
18234
|
+
...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
|
|
18235
|
+
...Object.keys(snapshot.idpSessions).map(
|
|
18236
|
+
(issuer) => oauthIdpSecretServerId(issuer)
|
|
18237
|
+
)
|
|
18238
|
+
];
|
|
18239
|
+
for (const legacyId of legacyIds) {
|
|
17512
18240
|
try {
|
|
17513
18241
|
await secretStore.deleteAllForServer(legacyId);
|
|
17514
18242
|
} catch (error) {
|
|
@@ -17561,6 +18289,14 @@ function preserveNonDurableSecrets(diskSecrets, secrets) {
|
|
|
17561
18289
|
return keep;
|
|
17562
18290
|
}
|
|
17563
18291
|
async function writeOAuthSections(filePath, snapshot, sections, secretStore = defaultSecretStore()) {
|
|
18292
|
+
await writeOAuthSectionsFrom(
|
|
18293
|
+
filePath,
|
|
18294
|
+
async () => snapshot,
|
|
18295
|
+
sections,
|
|
18296
|
+
secretStore
|
|
18297
|
+
);
|
|
18298
|
+
}
|
|
18299
|
+
async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secretStore) {
|
|
17564
18300
|
const policy = getPersistTokensPolicy();
|
|
17565
18301
|
const durable = await secretStoreIsDurable(secretStore);
|
|
17566
18302
|
await withOAuthStateLock(filePath, "save", async (locked) => {
|
|
@@ -17644,6 +18380,7 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17644
18380
|
unconfirmed = null;
|
|
17645
18381
|
namespace = diskNamespace;
|
|
17646
18382
|
}
|
|
18383
|
+
const snapshot = await resolveSnapshot(disk, namespace);
|
|
17647
18384
|
const effective = sections ? {
|
|
17648
18385
|
servers: sections.servers && [...new Set(sections.servers)],
|
|
17649
18386
|
idpSessions: sections.idpSessions && [
|
|
@@ -17669,7 +18406,22 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17669
18406
|
secretStore,
|
|
17670
18407
|
namespace,
|
|
17671
18408
|
Object.keys(merged.servers),
|
|
17672
|
-
Object.keys(merged.idpSessions)
|
|
18409
|
+
Object.keys(merged.idpSessions),
|
|
18410
|
+
// Only the entries this save writes: another entry's secret in
|
|
18411
|
+
// this store was written for whatever client it was written for,
|
|
18412
|
+
// and relabeling it from the file would misattribute it (#2672).
|
|
18413
|
+
writtenClientOwners(
|
|
18414
|
+
{
|
|
18415
|
+
servers: Object.fromEntries(
|
|
18416
|
+
(effective.servers ?? []).flatMap((url) => {
|
|
18417
|
+
const entry = getOwnEntry(merged.servers, url);
|
|
18418
|
+
return entry === void 0 ? [] : [[url, entry]];
|
|
18419
|
+
})
|
|
18420
|
+
),
|
|
18421
|
+
idpSessions: {}
|
|
18422
|
+
},
|
|
18423
|
+
splitSecretsOf
|
|
18424
|
+
)
|
|
17673
18425
|
);
|
|
17674
18426
|
for (const url of effective.servers ?? []) {
|
|
17675
18427
|
const serverId = oauthSecretServerId(url, namespace);
|
|
@@ -17677,7 +18429,8 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17677
18429
|
const candidates = [
|
|
17678
18430
|
.../* @__PURE__ */ new Set([
|
|
17679
18431
|
...serverSecretFields(getOwnEntry(disk?.servers, url)),
|
|
17680
|
-
...serverSecretFields(next)
|
|
18432
|
+
...serverSecretFields(next),
|
|
18433
|
+
...[...restoreBaseline.values()].filter((prior) => prior.serverId === serverId).map(({ field }) => field)
|
|
17681
18434
|
])
|
|
17682
18435
|
];
|
|
17683
18436
|
const entryPrior = await snapshotSecretFields(
|
|
@@ -17686,8 +18439,12 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17686
18439
|
candidates
|
|
17687
18440
|
);
|
|
17688
18441
|
priorSecrets.push(...entryPrior);
|
|
18442
|
+
const entryPriorValue = new Map(
|
|
18443
|
+
entryPrior.map(({ field, value }) => [field, value])
|
|
18444
|
+
);
|
|
17689
18445
|
if (next === void 0) {
|
|
17690
18446
|
for (const field of candidates) {
|
|
18447
|
+
if (entryPriorValue.get(field) === null) continue;
|
|
17691
18448
|
ourWrites.set(priorKey({ serverId, field }), null);
|
|
17692
18449
|
}
|
|
17693
18450
|
await secretStore.deleteAllForServer(serverId);
|
|
@@ -17702,10 +18459,9 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17702
18459
|
}
|
|
17703
18460
|
const { residue, secrets } = splitServerOAuthState(next, policy);
|
|
17704
18461
|
for (const field of candidates) {
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
);
|
|
18462
|
+
const value = secrets[field] ?? null;
|
|
18463
|
+
if (value === entryPriorValue.get(field)) continue;
|
|
18464
|
+
ourWrites.set(priorKey({ serverId, field }), value);
|
|
17709
18465
|
}
|
|
17710
18466
|
setOwnEntry(merged.servers, url, residue);
|
|
17711
18467
|
if (!durable) {
|
|
@@ -17848,26 +18604,109 @@ function secretRequestsFor(snapshot, namespace) {
|
|
|
17848
18604
|
}
|
|
17849
18605
|
return requests;
|
|
17850
18606
|
}
|
|
17851
|
-
|
|
17852
|
-
const
|
|
17853
|
-
|
|
18607
|
+
function fillMissingSecrets(target, source) {
|
|
18608
|
+
for (const [field, value] of Object.entries(source ?? {})) {
|
|
18609
|
+
if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
|
|
18610
|
+
}
|
|
18611
|
+
}
|
|
18612
|
+
function writtenClientOwners(snapshot, secretsOf) {
|
|
18613
|
+
const owners = /* @__PURE__ */ new Map();
|
|
18614
|
+
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
18615
|
+
const secrets = secretsOf(url, state);
|
|
18616
|
+
const fields = new Map(
|
|
18617
|
+
[...clientCredentialOwners(state)].filter(
|
|
18618
|
+
([field]) => Object.hasOwn(secrets, field)
|
|
18619
|
+
)
|
|
18620
|
+
);
|
|
18621
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
18622
|
+
}
|
|
18623
|
+
return owners;
|
|
18624
|
+
}
|
|
18625
|
+
var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
|
|
18626
|
+
function snapshotClientOwners(snapshot) {
|
|
18627
|
+
const owners = /* @__PURE__ */ new Map();
|
|
18628
|
+
for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
|
|
18629
|
+
const fields = clientCredentialOwners(state);
|
|
18630
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
18631
|
+
}
|
|
18632
|
+
return owners;
|
|
18633
|
+
}
|
|
18634
|
+
async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
18635
|
+
const servers = /* @__PURE__ */ new Map();
|
|
18636
|
+
const idpSessions = /* @__PURE__ */ new Map();
|
|
18637
|
+
for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
|
|
18638
|
+
for (const issuer of Object.keys(snapshot.idpSessions)) {
|
|
18639
|
+
idpSessions.set(issuer, {});
|
|
18640
|
+
}
|
|
18641
|
+
for (const source of [namespace, ...fallbacks]) {
|
|
18642
|
+
const requests = secretRequestsFor(snapshot, source);
|
|
18643
|
+
if (requests.length === 0) break;
|
|
18644
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
18645
|
+
for (const [url, target] of servers) {
|
|
18646
|
+
fillMissingSecrets(target, values[oauthSecretServerId(url, source)]);
|
|
18647
|
+
}
|
|
18648
|
+
for (const [issuer, target] of idpSessions) {
|
|
18649
|
+
fillMissingSecrets(
|
|
18650
|
+
target,
|
|
18651
|
+
values[oauthIdpSecretServerId(issuer, source)]
|
|
18652
|
+
);
|
|
18653
|
+
}
|
|
18654
|
+
}
|
|
18655
|
+
const owners = snapshotClientOwners(snapshot);
|
|
18656
|
+
for (const { namespace: from, fields: recorded } of carries) {
|
|
18657
|
+
const requests = [];
|
|
18658
|
+
for (const [url, fields] of recorded) {
|
|
18659
|
+
const target = servers.get(url);
|
|
18660
|
+
if (!target) continue;
|
|
18661
|
+
const wanted = [...fields].filter(
|
|
18662
|
+
(field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
|
|
18663
|
+
);
|
|
18664
|
+
if (wanted.length > 0) {
|
|
18665
|
+
requests.push({
|
|
18666
|
+
serverId: oauthSecretServerId(url, from),
|
|
18667
|
+
fields: wanted.flatMap((field) => [
|
|
18668
|
+
field,
|
|
18669
|
+
clientCredentialStampField(field)
|
|
18670
|
+
])
|
|
18671
|
+
});
|
|
18672
|
+
}
|
|
18673
|
+
}
|
|
18674
|
+
if (requests.length === 0) continue;
|
|
18675
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
18676
|
+
for (const [url, target] of servers) {
|
|
18677
|
+
const found = values[oauthSecretServerId(url, from)] ?? {};
|
|
18678
|
+
for (const [field, clientId] of owners.get(url) ?? []) {
|
|
18679
|
+
const value = Object.hasOwn(found, field) ? found[field] : void 0;
|
|
18680
|
+
const stampField = clientCredentialStampField(field);
|
|
18681
|
+
if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
|
|
18682
|
+
continue;
|
|
18683
|
+
}
|
|
18684
|
+
setOwnEntry(target, field, value);
|
|
18685
|
+
setOwnEntry(target, stampField, found[stampField]);
|
|
18686
|
+
}
|
|
18687
|
+
}
|
|
18688
|
+
}
|
|
18689
|
+
return { servers, idpSessions };
|
|
18690
|
+
}
|
|
18691
|
+
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
18692
|
+
const values = await readEntrySecrets(
|
|
18693
|
+
snapshot,
|
|
18694
|
+
secretStore,
|
|
18695
|
+
namespace,
|
|
18696
|
+
fallbacks,
|
|
18697
|
+
carries
|
|
18698
|
+
);
|
|
17854
18699
|
return {
|
|
17855
18700
|
servers: Object.fromEntries(
|
|
17856
18701
|
Object.entries(snapshot.servers).map(([url, state]) => [
|
|
17857
18702
|
url,
|
|
17858
|
-
joinServerOAuthState(
|
|
17859
|
-
state,
|
|
17860
|
-
values[oauthSecretServerId(url, namespace)] ?? {}
|
|
17861
|
-
)
|
|
18703
|
+
joinServerOAuthState(state, values.servers.get(url) ?? {})
|
|
17862
18704
|
])
|
|
17863
18705
|
),
|
|
17864
18706
|
idpSessions: Object.fromEntries(
|
|
17865
18707
|
Object.entries(snapshot.idpSessions).map(([issuer, session]) => [
|
|
17866
18708
|
issuer,
|
|
17867
|
-
joinIdpSession(
|
|
17868
|
-
session,
|
|
17869
|
-
values[oauthIdpSecretServerId(issuer, namespace)] ?? {}
|
|
17870
|
-
)
|
|
18709
|
+
joinIdpSession(session, values.idpSessions.get(issuer) ?? {})
|
|
17871
18710
|
])
|
|
17872
18711
|
)
|
|
17873
18712
|
};
|
|
@@ -17883,7 +18722,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
17883
18722
|
secretStore,
|
|
17884
18723
|
namespace,
|
|
17885
18724
|
Object.keys(fresh.servers),
|
|
17886
|
-
Object.keys(fresh.idpSessions)
|
|
18725
|
+
Object.keys(fresh.idpSessions),
|
|
18726
|
+
writtenClientOwners(fresh, splitSecretsOf)
|
|
17887
18727
|
);
|
|
17888
18728
|
}
|
|
17889
18729
|
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
@@ -17934,7 +18774,10 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
17934
18774
|
warnMigrationFailure(error);
|
|
17935
18775
|
}
|
|
17936
18776
|
}
|
|
17937
|
-
|
|
18777
|
+
const namespace = parseSecretsNamespace(raw);
|
|
18778
|
+
const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
|
|
18779
|
+
const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
|
|
18780
|
+
return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
|
|
17938
18781
|
});
|
|
17939
18782
|
}
|
|
17940
18783
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
@@ -18244,7 +19087,11 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18244
19087
|
path: options.callbackListen.pathname,
|
|
18245
19088
|
onCallback: async (params) => {
|
|
18246
19089
|
try {
|
|
18247
|
-
await options.client.completeOAuthFlow(
|
|
19090
|
+
await options.client.completeOAuthFlow(
|
|
19091
|
+
params.code,
|
|
19092
|
+
params.iss,
|
|
19093
|
+
params.state
|
|
19094
|
+
);
|
|
18248
19095
|
flowResolve();
|
|
18249
19096
|
} catch (err) {
|
|
18250
19097
|
flowReject(toRunnerOAuthError(err));
|
|
@@ -18296,7 +19143,10 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18296
19143
|
}
|
|
18297
19144
|
if (options.authChallenge) {
|
|
18298
19145
|
const satisfied = await racingSignals(
|
|
18299
|
-
|
|
19146
|
+
// The token exchange just completed, so only scope is in question.
|
|
19147
|
+
options.client.checkAuthChallengeSatisfied(options.authChallenge, {
|
|
19148
|
+
scopeOnly: true
|
|
19149
|
+
})
|
|
18300
19150
|
);
|
|
18301
19151
|
if (!satisfied) {
|
|
18302
19152
|
return {
|
|
@@ -18349,14 +19199,25 @@ function getTuiLogger() {
|
|
|
18349
19199
|
// ../../core/node/openUrl.ts
|
|
18350
19200
|
import open from "open";
|
|
18351
19201
|
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
19202
|
+
var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
19203
|
+
function toOpenableHref(href) {
|
|
19204
|
+
let parsed;
|
|
19205
|
+
try {
|
|
19206
|
+
parsed = new URL(href);
|
|
19207
|
+
} catch {
|
|
19208
|
+
throw new Error("refusing to open an invalid URL");
|
|
19209
|
+
}
|
|
19210
|
+
if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
|
|
19211
|
+
throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
|
|
19212
|
+
}
|
|
19213
|
+
return parsed.href;
|
|
19214
|
+
}
|
|
19215
|
+
function absorbLateErrors(child) {
|
|
19216
|
+
child.on("error", () => {
|
|
18356
19217
|
});
|
|
18357
19218
|
}
|
|
18358
19219
|
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
18359
|
-
const href = typeof url === "string" ? url : url.href;
|
|
19220
|
+
const href = toOpenableHref(typeof url === "string" ? url : url.href);
|
|
18360
19221
|
let timer;
|
|
18361
19222
|
const timeout = new Promise((_, reject) => {
|
|
18362
19223
|
timer = setTimeout(
|
|
@@ -18365,8 +19226,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
|
18365
19226
|
);
|
|
18366
19227
|
});
|
|
18367
19228
|
try {
|
|
18368
|
-
|
|
18369
|
-
await Promise.race([opened, timeout]);
|
|
19229
|
+
await Promise.race([open(href).then(absorbLateErrors), timeout]);
|
|
18370
19230
|
} finally {
|
|
18371
19231
|
clearTimeout(timer);
|
|
18372
19232
|
}
|
|
@@ -19069,8 +19929,24 @@ function copyViaOsc52(text, sink) {
|
|
|
19069
19929
|
large: payloadBytes > OSC52_LARGE_PAYLOAD_BYTES
|
|
19070
19930
|
};
|
|
19071
19931
|
}
|
|
19932
|
+
var copyDirs = /* @__PURE__ */ new Set();
|
|
19933
|
+
var exitCleanupRegistered = false;
|
|
19934
|
+
function removeCopyFiles() {
|
|
19935
|
+
for (const dir of copyDirs) {
|
|
19936
|
+
try {
|
|
19937
|
+
fs5.rmSync(dir, { recursive: true, force: true });
|
|
19938
|
+
} catch {
|
|
19939
|
+
}
|
|
19940
|
+
copyDirs.delete(dir);
|
|
19941
|
+
}
|
|
19942
|
+
}
|
|
19072
19943
|
function writeCopyFile(text, baseDir = os.tmpdir()) {
|
|
19073
19944
|
const dir = fs5.mkdtempSync(path6.join(baseDir, "mcp-inspector-copy-"));
|
|
19945
|
+
copyDirs.add(dir);
|
|
19946
|
+
if (!exitCleanupRegistered) {
|
|
19947
|
+
process.on("exit", removeCopyFiles);
|
|
19948
|
+
exitCleanupRegistered = true;
|
|
19949
|
+
}
|
|
19074
19950
|
const file = path6.join(dir, "value.txt");
|
|
19075
19951
|
fs5.writeFileSync(file, text, { encoding: "utf8", mode: 384 });
|
|
19076
19952
|
return file;
|
|
@@ -19107,7 +19983,10 @@ function useCopyKeys(value, label) {
|
|
|
19107
19983
|
if (key === SAVE_KEY) {
|
|
19108
19984
|
try {
|
|
19109
19985
|
const file = writeCopyFile(value);
|
|
19110
|
-
setStatus({
|
|
19986
|
+
setStatus({
|
|
19987
|
+
tone: "success",
|
|
19988
|
+
message: `Saved ${label} to ${file} (deleted when the TUI exits)`
|
|
19989
|
+
});
|
|
19111
19990
|
} catch (err) {
|
|
19112
19991
|
setStatus({
|
|
19113
19992
|
tone: "error",
|
|
@@ -19516,7 +20395,7 @@ import { useCallback as useCallback14, useEffect as useEffect4, useMemo, useStat
|
|
|
19516
20395
|
function printable(input) {
|
|
19517
20396
|
return input.replace(/[\u0000-\u001f\u007f]/g, "");
|
|
19518
20397
|
}
|
|
19519
|
-
function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
20398
|
+
function useListFilter(items, fields, { enabled, onEditingChange, onEscapeClaimChange }) {
|
|
19520
20399
|
const [query, setQuery] = useState7("");
|
|
19521
20400
|
const [editingRequested, setEditingRequested] = useState7(false);
|
|
19522
20401
|
const editing = editingRequested && enabled;
|
|
@@ -19525,6 +20404,13 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
|
19525
20404
|
onEditingChange?.(true);
|
|
19526
20405
|
return () => onEditingChange?.(false);
|
|
19527
20406
|
}, [editing, onEditingChange]);
|
|
20407
|
+
const active = query.trim() !== "";
|
|
20408
|
+
const claimsEscape = enabled && active && !editingRequested;
|
|
20409
|
+
useEffect4(() => {
|
|
20410
|
+
if (!claimsEscape) return;
|
|
20411
|
+
onEscapeClaimChange?.(true);
|
|
20412
|
+
return () => onEscapeClaimChange?.(false);
|
|
20413
|
+
}, [claimsEscape, onEscapeClaimChange]);
|
|
19528
20414
|
const { filtered, indices } = useMemo(() => {
|
|
19529
20415
|
const needle = query.trim().toLowerCase();
|
|
19530
20416
|
const filtered2 = [];
|
|
@@ -19545,6 +20431,10 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
|
19545
20431
|
setEditingRequested(true);
|
|
19546
20432
|
return true;
|
|
19547
20433
|
}
|
|
20434
|
+
if (key.escape && query.trim() !== "") {
|
|
20435
|
+
setQuery("");
|
|
20436
|
+
return true;
|
|
20437
|
+
}
|
|
19548
20438
|
return false;
|
|
19549
20439
|
}
|
|
19550
20440
|
if (key.escape) {
|
|
@@ -19568,12 +20458,12 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
|
19568
20458
|
if (text) setQuery((prev) => prev + text);
|
|
19569
20459
|
return true;
|
|
19570
20460
|
},
|
|
19571
|
-
[enabled, editingRequested]
|
|
20461
|
+
[enabled, editingRequested, query]
|
|
19572
20462
|
);
|
|
19573
20463
|
return {
|
|
19574
20464
|
query,
|
|
19575
20465
|
editing,
|
|
19576
|
-
active
|
|
20466
|
+
active,
|
|
19577
20467
|
items: filtered,
|
|
19578
20468
|
indices,
|
|
19579
20469
|
handleInput
|
|
@@ -19593,7 +20483,7 @@ function ListFilterBar({ query, editing, focused }) {
|
|
|
19593
20483
|
] }) : query.trim() !== "" ? /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate-end", children: [
|
|
19594
20484
|
/* @__PURE__ */ jsx6(Text5, { color: "cyan", children: "/" }),
|
|
19595
20485
|
query,
|
|
19596
|
-
/* @__PURE__ */ jsx6(Text5, { dimColor: true, children: " (/ to edit)" })
|
|
20486
|
+
/* @__PURE__ */ jsx6(Text5, { dimColor: true, children: focused ? " (/ edit \xB7 Esc clear)" : " (/ to edit)" })
|
|
19597
20487
|
] }) : focused ? /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "/ to filter" }) : /* @__PURE__ */ jsx6(Text5, { children: " " }) });
|
|
19598
20488
|
}
|
|
19599
20489
|
function filterCount(active, shown, total) {
|
|
@@ -19618,7 +20508,8 @@ function ResourcesTab({
|
|
|
19618
20508
|
onFetchTemplate,
|
|
19619
20509
|
onAuthRecoveryRequired,
|
|
19620
20510
|
modalOpen = false,
|
|
19621
|
-
onFilterEditingChange
|
|
20511
|
+
onFilterEditingChange,
|
|
20512
|
+
onFilterEscapeClaimChange
|
|
19622
20513
|
}) {
|
|
19623
20514
|
const [error, setError] = useState8(null);
|
|
19624
20515
|
const [resourceContent, setResourceContent] = useState8(null);
|
|
@@ -19641,7 +20532,8 @@ function ResourcesTab({
|
|
|
19641
20532
|
const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
|
|
19642
20533
|
const filter = useListFilter(allItems, resourceFilterFields, {
|
|
19643
20534
|
enabled: !modalOpen && focusedPane === "list",
|
|
19644
|
-
onEditingChange: onFilterEditingChange
|
|
20535
|
+
onEditingChange: onFilterEditingChange,
|
|
20536
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
19645
20537
|
});
|
|
19646
20538
|
const shownItems = filter.items;
|
|
19647
20539
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -19914,12 +20806,14 @@ function PromptsTab({
|
|
|
19914
20806
|
onFetchPrompt,
|
|
19915
20807
|
onAuthRecoveryRequired,
|
|
19916
20808
|
modalOpen = false,
|
|
19917
|
-
onFilterEditingChange
|
|
20809
|
+
onFilterEditingChange,
|
|
20810
|
+
onFilterEscapeClaimChange
|
|
19918
20811
|
}) {
|
|
19919
20812
|
const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
|
|
19920
20813
|
const filter = useListFilter(prompts, promptFilterFields, {
|
|
19921
20814
|
enabled: !modalOpen && focusedPane === "list",
|
|
19922
|
-
onEditingChange: onFilterEditingChange
|
|
20815
|
+
onEditingChange: onFilterEditingChange,
|
|
20816
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
19923
20817
|
});
|
|
19924
20818
|
const shownPrompts = filter.items;
|
|
19925
20819
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -20137,6 +21031,10 @@ import {
|
|
|
20137
21031
|
import { Box as Box9, Text as Text8, useInput as useInput5 } from "ink";
|
|
20138
21032
|
|
|
20139
21033
|
// ../../core/mcp/skillsVerification.ts
|
|
21034
|
+
var DEFAULT_SKILL_BUDGET_HINT = {
|
|
21035
|
+
raise: "with the server's skillCatalogMaxSkills / skillCatalogMaxBytes settings",
|
|
21036
|
+
verifyOne: "by verifying this skill alone"
|
|
21037
|
+
};
|
|
20140
21038
|
function reasonOf(err) {
|
|
20141
21039
|
return err instanceof Error ? err.message : String(err);
|
|
20142
21040
|
}
|
|
@@ -20198,7 +21096,7 @@ function responseBytes(result) {
|
|
|
20198
21096
|
}
|
|
20199
21097
|
return total;
|
|
20200
21098
|
}
|
|
20201
|
-
async function verifySkills(client, entries, metadata) {
|
|
21099
|
+
async function verifySkills(client, entries, metadata, budgetHint = DEFAULT_SKILL_BUDGET_HINT) {
|
|
20202
21100
|
const collisions = checkSkillNameCollisions(entries);
|
|
20203
21101
|
const reports = [];
|
|
20204
21102
|
const budget = resolveSkillCatalogBudget(client.getServerSettings?.());
|
|
@@ -20210,7 +21108,7 @@ async function verifySkills(client, entries, metadata) {
|
|
|
20210
21108
|
const files = [];
|
|
20211
21109
|
const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
|
|
20212
21110
|
const manifest = withinBudget ? boundedManifest(declared) : [];
|
|
20213
|
-
let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it
|
|
21111
|
+
let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it ${budgetHint.raise}). Nothing about this skill's files has been checked \u2014 verify it on its own ${budgetHint.verifyOne} to get a verdict.` : manifest.length < declared.length ? `Only ${manifest.length} of ${declared.length} manifest entries were read: the skill exceeds the ${SKILL_MAX_RESOURCE_ENTRIES}-entry / ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limits, so the rest were not fetched and cannot be reported on.` : void 0;
|
|
20214
21112
|
let receivedBytes = 0;
|
|
20215
21113
|
const entryIdentity = skillUriIdentity(entry.uri);
|
|
20216
21114
|
let selfAttempted = false;
|
|
@@ -20365,6 +21263,10 @@ var FILE_COLOR = {
|
|
|
20365
21263
|
error: "red",
|
|
20366
21264
|
"read-error": "red"
|
|
20367
21265
|
};
|
|
21266
|
+
var TUI_SKILL_BUDGET_HINT = {
|
|
21267
|
+
raise: "with --skill-catalog-max-skills / --skill-catalog-max-bytes at launch",
|
|
21268
|
+
verifyOne: "by selecting it and pressing Enter"
|
|
21269
|
+
};
|
|
20368
21270
|
var VERIFY_STATUS = {
|
|
20369
21271
|
verified: "[Verified \u2014 Enter to re-verify]",
|
|
20370
21272
|
unverifiable: "[UNVERIFIABLE \u2014 Enter to re-verify]",
|
|
@@ -20425,12 +21327,14 @@ function SkillsTab({
|
|
|
20425
21327
|
focusedPane = null,
|
|
20426
21328
|
onAuthRecoveryRequired,
|
|
20427
21329
|
modalOpen = false,
|
|
20428
|
-
onFilterEditingChange
|
|
21330
|
+
onFilterEditingChange,
|
|
21331
|
+
onFilterEscapeClaimChange
|
|
20429
21332
|
}) {
|
|
20430
21333
|
const visibleCount = Math.max(1, height - 8 - LIST_FILTER_ROWS);
|
|
20431
21334
|
const filter = useListFilter(skills, skillFilterFields, {
|
|
20432
21335
|
enabled: !modalOpen && focusedPane === "list",
|
|
20433
|
-
onEditingChange: onFilterEditingChange
|
|
21336
|
+
onEditingChange: onFilterEditingChange,
|
|
21337
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
20434
21338
|
});
|
|
20435
21339
|
const shownSkills = filter.items;
|
|
20436
21340
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -20454,7 +21358,12 @@ function SkillsTab({
|
|
|
20454
21358
|
setError(null);
|
|
20455
21359
|
void (async () => {
|
|
20456
21360
|
try {
|
|
20457
|
-
const results = await verifySkills(
|
|
21361
|
+
const results = await verifySkills(
|
|
21362
|
+
inspectorClient,
|
|
21363
|
+
entries,
|
|
21364
|
+
void 0,
|
|
21365
|
+
TUI_SKILL_BUDGET_HINT
|
|
21366
|
+
);
|
|
20458
21367
|
const live = new Set(skillsRef.current.map(skillEntryKey));
|
|
20459
21368
|
setReports(
|
|
20460
21369
|
(previous) => mergeReports(previous, entries, results, live)
|
|
@@ -21022,12 +21931,14 @@ function ToolsTab({
|
|
|
21022
21931
|
onTestTool,
|
|
21023
21932
|
onViewDetails,
|
|
21024
21933
|
modalOpen = false,
|
|
21025
|
-
onFilterEditingChange
|
|
21934
|
+
onFilterEditingChange,
|
|
21935
|
+
onFilterEscapeClaimChange
|
|
21026
21936
|
}) {
|
|
21027
21937
|
const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
|
|
21028
21938
|
const filter = useListFilter(tools, toolFilterFields, {
|
|
21029
21939
|
enabled: !modalOpen && focusedPane === "list",
|
|
21030
|
-
onEditingChange: onFilterEditingChange
|
|
21940
|
+
onEditingChange: onFilterEditingChange,
|
|
21941
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
21031
21942
|
});
|
|
21032
21943
|
const shownTools = filter.items;
|
|
21033
21944
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -24574,7 +25485,10 @@ function tabAcceleratorBinding(visible) {
|
|
|
24574
25485
|
function globalBindings(visible = tabs) {
|
|
24575
25486
|
return [
|
|
24576
25487
|
{ keys: "?", action: "Show or hide this help" },
|
|
24577
|
-
{
|
|
25488
|
+
{
|
|
25489
|
+
keys: "Esc / Ctrl+C",
|
|
25490
|
+
action: "Exit (Esc closes a dialog or clears a kept filter first)"
|
|
25491
|
+
},
|
|
24578
25492
|
{
|
|
24579
25493
|
keys: "Tab / Shift+Tab",
|
|
24580
25494
|
action: "Move focus: servers \u2192 tabs \u2192 list \u2192 details"
|
|
@@ -25063,7 +25977,8 @@ var STREAM_STATUS_TEXT = {
|
|
|
25063
25977
|
"never-acknowledged": {
|
|
25064
25978
|
text: "closed without acknowledging (server answered listen with a result)",
|
|
25065
25979
|
color: "red"
|
|
25066
|
-
}
|
|
25980
|
+
},
|
|
25981
|
+
refused: { text: "refused by the server", color: "red" }
|
|
25067
25982
|
};
|
|
25068
25983
|
function SubscriptionsTab({
|
|
25069
25984
|
resources,
|
|
@@ -25152,6 +26067,7 @@ function SubscriptionsTab({
|
|
|
25152
26067
|
scrollViewRef.current?.scrollTo(0);
|
|
25153
26068
|
}, [selectedIndex]);
|
|
25154
26069
|
const streamText = STREAM_STATUS_TEXT[streamState.status];
|
|
26070
|
+
const streamRefusal = streamState.status === "refused" ? streamState.refusal : void 0;
|
|
25155
26071
|
const notHonored = !!selected && selected.subscribed && streamState.active && streamState.status === "acknowledged" && !streamState.honoredUris.includes(selected.resource.uri);
|
|
25156
26072
|
return /* @__PURE__ */ jsxs21(Box33, { flexDirection: "row", width, height, children: [
|
|
25157
26073
|
/* @__PURE__ */ jsxs21(
|
|
@@ -25238,11 +26154,12 @@ function SubscriptionsTab({
|
|
|
25238
26154
|
] }) }),
|
|
25239
26155
|
notHonored && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "yellow", children: "Server did not honor this URI in its listen filter" }) })
|
|
25240
26156
|
] }),
|
|
25241
|
-
streamState.active && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { children: [
|
|
26157
|
+
(streamState.active || streamRefusal) && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { children: [
|
|
25242
26158
|
"Listen stream:",
|
|
25243
26159
|
" ",
|
|
25244
26160
|
/* @__PURE__ */ jsx23(Text30, { color: streamText.color, children: streamText.text })
|
|
25245
26161
|
] }) }),
|
|
26162
|
+
streamRefusal && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "red", children: subscriptionListenRefusalMessage(streamRefusal) }) }),
|
|
25246
26163
|
error && /* @__PURE__ */ jsx23(Box33, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "red", children: error }) }),
|
|
25247
26164
|
/* @__PURE__ */ jsx23(Box33, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { bold: true, children: [
|
|
25248
26165
|
"Updates (",
|
|
@@ -25451,6 +26368,7 @@ var APP_NAME = "MCP Inspector TUI";
|
|
|
25451
26368
|
var APP_DESCRIPTION = "Terminal User Interface for the Model Context Protocol Inspector";
|
|
25452
26369
|
var APP_VERSION = readInspectorVersion(import.meta.url);
|
|
25453
26370
|
var TUI_CLIENT_NAME = "inspector-tui";
|
|
26371
|
+
var TUI_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, set connectionTimeout on the server's entry in a catalog (--catalog) or config (--config) file.";
|
|
25454
26372
|
var NO_ROOTS = [];
|
|
25455
26373
|
var readRoots = (client) => client.getRoots();
|
|
25456
26374
|
function App({
|
|
@@ -25474,6 +26392,7 @@ function App({
|
|
|
25474
26392
|
const [helpOpen, setHelpOpen] = useState22(false);
|
|
25475
26393
|
const focus = helpOpen ? "help" : paneFocus;
|
|
25476
26394
|
const [listFilterEditing, setListFilterEditing] = useState22(false);
|
|
26395
|
+
const [listFilterClaimsEscape, setListFilterClaimsEscape] = useState22(false);
|
|
25477
26396
|
const [tabCounts, setTabCounts] = useState22({});
|
|
25478
26397
|
const [oauthStatus, setOauthStatus] = useState22("idle");
|
|
25479
26398
|
const [oauthMessage, setOauthMessage] = useState22(null);
|
|
@@ -25569,6 +26488,7 @@ function App({
|
|
|
25569
26488
|
environment,
|
|
25570
26489
|
clientIdentity: { name: TUI_CLIENT_NAME, version: APP_VERSION },
|
|
25571
26490
|
pipeStderr: true,
|
|
26491
|
+
connectionTimeoutRemedy: TUI_CONNECTION_TIMEOUT_REMEDY,
|
|
25572
26492
|
...savedSettings && savedSettings.requestTimeout > 0 && {
|
|
25573
26493
|
timeout: savedSettings.requestTimeout
|
|
25574
26494
|
},
|
|
@@ -26452,6 +27372,7 @@ function App({
|
|
|
26452
27372
|
return;
|
|
26453
27373
|
}
|
|
26454
27374
|
if (key.escape) {
|
|
27375
|
+
if (listFilterClaimsEscape) return;
|
|
26455
27376
|
exit();
|
|
26456
27377
|
}
|
|
26457
27378
|
const showAuthTab = !!selectedServer && !!selectedServerConfig && isOAuthCapableServerConfig(selectedServerConfig);
|
|
@@ -26553,7 +27474,7 @@ function App({
|
|
|
26553
27474
|
}
|
|
26554
27475
|
}
|
|
26555
27476
|
});
|
|
26556
|
-
const headerHeight =
|
|
27477
|
+
const headerHeight = 2;
|
|
26557
27478
|
const serverListWidth = Math.floor(dimensions.width * 0.3);
|
|
26558
27479
|
const contentWidth = dimensions.width - serverListWidth;
|
|
26559
27480
|
const shownTabs = visibleTabs({
|
|
@@ -26612,17 +27533,18 @@ function App({
|
|
|
26612
27533
|
justifyContent: "space-between",
|
|
26613
27534
|
alignItems: "center",
|
|
26614
27535
|
children: [
|
|
26615
|
-
/* @__PURE__ */ jsxs23(Box35, { children: [
|
|
26616
|
-
/* @__PURE__ */ jsx25(Text32, { bold: true, color: "cyan", children: APP_NAME }),
|
|
26617
|
-
/* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
|
|
26618
|
-
"
|
|
27536
|
+
/* @__PURE__ */ jsxs23(Box35, { flexShrink: 1, overflow: "hidden", children: [
|
|
27537
|
+
/* @__PURE__ */ jsx25(Box35, { flexShrink: 0, children: /* @__PURE__ */ jsx25(Text32, { bold: true, color: "cyan", children: APP_NAME }) }),
|
|
27538
|
+
/* @__PURE__ */ jsxs23(Text32, { dimColor: true, wrap: "truncate-end", children: [
|
|
27539
|
+
" ",
|
|
27540
|
+
"- ",
|
|
26619
27541
|
APP_DESCRIPTION
|
|
26620
27542
|
] })
|
|
26621
27543
|
] }),
|
|
26622
|
-
/* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
|
|
27544
|
+
/* @__PURE__ */ jsx25(Box35, { flexShrink: 0, marginLeft: 1, children: /* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
|
|
26623
27545
|
"v",
|
|
26624
27546
|
APP_VERSION
|
|
26625
|
-
] })
|
|
27547
|
+
] }) })
|
|
26626
27548
|
]
|
|
26627
27549
|
}
|
|
26628
27550
|
),
|
|
@@ -26884,6 +27806,7 @@ function App({
|
|
|
26884
27806
|
ResourcesTab,
|
|
26885
27807
|
{
|
|
26886
27808
|
onFilterEditingChange: setListFilterEditing,
|
|
27809
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26887
27810
|
resources: currentServerState.resources,
|
|
26888
27811
|
resourceTemplates: currentServerState.resourceTemplates,
|
|
26889
27812
|
inspectorClient: selectedInspectorClient,
|
|
@@ -26948,6 +27871,7 @@ function App({
|
|
|
26948
27871
|
SkillsTab,
|
|
26949
27872
|
{
|
|
26950
27873
|
onFilterEditingChange: setListFilterEditing,
|
|
27874
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26951
27875
|
skills: managedSkills,
|
|
26952
27876
|
pageCount: managedSkillsPageCount,
|
|
26953
27877
|
loadError: managedSkillsError,
|
|
@@ -26963,6 +27887,7 @@ function App({
|
|
|
26963
27887
|
PromptsTab,
|
|
26964
27888
|
{
|
|
26965
27889
|
onFilterEditingChange: setListFilterEditing,
|
|
27890
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26966
27891
|
prompts: currentServerState.prompts,
|
|
26967
27892
|
inspectorClient: selectedInspectorClient,
|
|
26968
27893
|
width: contentWidth,
|
|
@@ -26988,6 +27913,7 @@ function App({
|
|
|
26988
27913
|
ToolsTab,
|
|
26989
27914
|
{
|
|
26990
27915
|
onFilterEditingChange: setListFilterEditing,
|
|
27916
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26991
27917
|
tools: currentServerState.tools,
|
|
26992
27918
|
isConnected: inspectorStatus === "connected",
|
|
26993
27919
|
width: contentWidth,
|
|
@@ -27145,7 +28071,11 @@ var App_default = App;
|
|
|
27145
28071
|
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
27146
28072
|
async function runTui(args) {
|
|
27147
28073
|
const program = new Command();
|
|
27148
|
-
program.name("mcp-inspector-tui").description("Terminal UI for MCP Inspector").
|
|
28074
|
+
program.name("mcp-inspector-tui").description("Terminal UI for MCP Inspector").version(
|
|
28075
|
+
readInspectorVersion(import.meta.url),
|
|
28076
|
+
"-V, --version",
|
|
28077
|
+
"Print the Inspector version"
|
|
28078
|
+
).option(
|
|
27149
28079
|
"--catalog <path>",
|
|
27150
28080
|
"Writable catalog file (created if missing; default: ~/.mcp-inspector/mcp.json, or MCP_CATALOG_PATH)"
|
|
27151
28081
|
).option(
|