@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
|
@@ -2707,6 +2707,84 @@ function isJsonDocumentResponse(contentType) {
|
|
|
2707
2707
|
const mediaType = contentType.split(";")[0].trim().toLowerCase();
|
|
2708
2708
|
return mediaType === "application/json" || mediaType.endsWith("+json");
|
|
2709
2709
|
}
|
|
2710
|
+
var SYNTHESIZED_METADATA_HEADER = "x-inspector-oauth-metadata-synthesized";
|
|
2711
|
+
var synthesizedResponses = /* @__PURE__ */ new WeakSet();
|
|
2712
|
+
function isSynthesizedMetadataResponse(response) {
|
|
2713
|
+
return synthesizedResponses.has(response);
|
|
2714
|
+
}
|
|
2715
|
+
var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
|
|
2716
|
+
var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
|
|
2717
|
+
function continuesDiscovery(status) {
|
|
2718
|
+
return status < 500 || status === 502;
|
|
2719
|
+
}
|
|
2720
|
+
function authorizationServerForFinalCandidate(url) {
|
|
2721
|
+
let parsed;
|
|
2722
|
+
try {
|
|
2723
|
+
parsed = new URL(url);
|
|
2724
|
+
} catch {
|
|
2725
|
+
return void 0;
|
|
2726
|
+
}
|
|
2727
|
+
const { pathname } = parsed;
|
|
2728
|
+
if (pathname === OIDC_WELL_KNOWN) return parsed.origin;
|
|
2729
|
+
if (!pathname.endsWith(OIDC_WELL_KNOWN)) return void 0;
|
|
2730
|
+
if (pathname.startsWith(`${RFC8414_WELL_KNOWN}/`) || pathname.startsWith(`${OIDC_WELL_KNOWN}/`)) {
|
|
2731
|
+
return void 0;
|
|
2732
|
+
}
|
|
2733
|
+
return `${parsed.origin}${pathname.slice(0, -OIDC_WELL_KNOWN.length)}`;
|
|
2734
|
+
}
|
|
2735
|
+
function synthesizeAuthorizationServerMetadata(issuer, overrides) {
|
|
2736
|
+
return {
|
|
2737
|
+
issuer,
|
|
2738
|
+
authorization_endpoint: overrides.authorizationUrl ?? new URL("/authorize", issuer).href,
|
|
2739
|
+
token_endpoint: overrides.tokenUrl ?? new URL("/token", issuer).href,
|
|
2740
|
+
registration_endpoint: new URL("/register", issuer).href,
|
|
2741
|
+
response_types_supported: ["code"],
|
|
2742
|
+
code_challenge_methods_supported: ["S256"]
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
function requestMethodOf(input, init) {
|
|
2746
|
+
if (init?.method) return init.method.toUpperCase();
|
|
2747
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
2748
|
+
return input.method.toUpperCase();
|
|
2749
|
+
}
|
|
2750
|
+
return "GET";
|
|
2751
|
+
}
|
|
2752
|
+
function acceptsJson(input, init) {
|
|
2753
|
+
let accept = null;
|
|
2754
|
+
if (init?.headers) {
|
|
2755
|
+
accept = new Headers(init.headers).get("accept");
|
|
2756
|
+
} else if (typeof input !== "string" && !(input instanceof URL)) {
|
|
2757
|
+
accept = input.headers.get("accept");
|
|
2758
|
+
}
|
|
2759
|
+
if (!accept) return false;
|
|
2760
|
+
const mediaTypes = accept.split(",").map((part) => part.split(";")[0].trim().toLowerCase());
|
|
2761
|
+
return mediaTypes.includes("application/json") && !mediaTypes.includes("text/event-stream");
|
|
2762
|
+
}
|
|
2763
|
+
function synthesizedMetadataResponse(input, init, response, overrides) {
|
|
2764
|
+
if (!continuesDiscovery(response.status)) return void 0;
|
|
2765
|
+
if (requestMethodOf(input, init) !== "GET") return void 0;
|
|
2766
|
+
if (!acceptsJson(input, init)) return void 0;
|
|
2767
|
+
const issuer = authorizationServerForFinalCandidate(requestUrlOf(input));
|
|
2768
|
+
if (!issuer) return void 0;
|
|
2769
|
+
void response.body?.cancel().catch(() => {
|
|
2770
|
+
});
|
|
2771
|
+
console.warn(
|
|
2772
|
+
`[oauth] ${issuer} publishes no authorization server metadata; using the configured endpoint override(s) in place of the SDK's /authorize and /token fallbacks.`
|
|
2773
|
+
);
|
|
2774
|
+
const synthesized = new Response(
|
|
2775
|
+
JSON.stringify(synthesizeAuthorizationServerMetadata(issuer, overrides)),
|
|
2776
|
+
{
|
|
2777
|
+
status: 200,
|
|
2778
|
+
statusText: "OK",
|
|
2779
|
+
headers: {
|
|
2780
|
+
"content-type": "application/json",
|
|
2781
|
+
[SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
);
|
|
2785
|
+
synthesizedResponses.add(synthesized);
|
|
2786
|
+
return synthesized;
|
|
2787
|
+
}
|
|
2710
2788
|
function responseWithBody(response, body) {
|
|
2711
2789
|
const headers = new Headers(response.headers);
|
|
2712
2790
|
headers.delete("content-length");
|
|
@@ -2734,7 +2812,9 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
|
|
|
2734
2812
|
const overrides = resolveOverrides();
|
|
2735
2813
|
if (!overrides) return response;
|
|
2736
2814
|
if (!isAuthorizationServerMetadataRequest(input)) return response;
|
|
2737
|
-
if (!response.ok)
|
|
2815
|
+
if (!response.ok) {
|
|
2816
|
+
return synthesizedMetadataResponse(input, init, response, overrides) ?? response;
|
|
2817
|
+
}
|
|
2738
2818
|
if (!isJsonDocumentResponse(response.headers.get("content-type")))
|
|
2739
2819
|
return response;
|
|
2740
2820
|
let parsed;
|
|
@@ -2910,8 +2990,8 @@ import {
|
|
|
2910
2990
|
OAuthMetadataSchema,
|
|
2911
2991
|
OpenIdProviderDiscoveryMetadataSchema
|
|
2912
2992
|
} from "@modelcontextprotocol/core";
|
|
2913
|
-
var
|
|
2914
|
-
var
|
|
2993
|
+
var RFC8414_WELL_KNOWN2 = "/.well-known/oauth-authorization-server";
|
|
2994
|
+
var OIDC_WELL_KNOWN2 = "/.well-known/openid-configuration";
|
|
2915
2995
|
var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
|
|
2916
2996
|
function oidcDiscoveryCandidates(rfc8414Url) {
|
|
2917
2997
|
let url;
|
|
@@ -2920,16 +3000,16 @@ function oidcDiscoveryCandidates(rfc8414Url) {
|
|
|
2920
3000
|
} catch {
|
|
2921
3001
|
return [];
|
|
2922
3002
|
}
|
|
2923
|
-
if (!url.pathname.startsWith(
|
|
2924
|
-
const path7 = url.pathname.slice(
|
|
3003
|
+
if (!url.pathname.startsWith(RFC8414_WELL_KNOWN2)) return [];
|
|
3004
|
+
const path7 = url.pathname.slice(RFC8414_WELL_KNOWN2.length);
|
|
2925
3005
|
if (path7 !== "" && !path7.startsWith("/")) return [];
|
|
2926
|
-
if (path7 === "") return [new URL(
|
|
3006
|
+
if (path7 === "") return [new URL(OIDC_WELL_KNOWN2, url.origin).href];
|
|
2927
3007
|
return [
|
|
2928
|
-
new URL(`${
|
|
2929
|
-
new URL(`${path7}${
|
|
3008
|
+
new URL(`${OIDC_WELL_KNOWN2}${path7}`, url.origin).href,
|
|
3009
|
+
new URL(`${path7}${OIDC_WELL_KNOWN2}`, url.origin).href
|
|
2930
3010
|
];
|
|
2931
3011
|
}
|
|
2932
|
-
function
|
|
3012
|
+
function continuesDiscovery2(status) {
|
|
2933
3013
|
return status >= 400 && status < 500 || status === 502;
|
|
2934
3014
|
}
|
|
2935
3015
|
async function releaseBody(response) {
|
|
@@ -2955,7 +3035,7 @@ function discoveryHeaders(input, init) {
|
|
|
2955
3035
|
}
|
|
2956
3036
|
return new Headers();
|
|
2957
3037
|
}
|
|
2958
|
-
function
|
|
3038
|
+
function requestMethodOf2(input, init) {
|
|
2959
3039
|
if (init?.method) return init.method.toUpperCase();
|
|
2960
3040
|
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
2961
3041
|
return input.method.toUpperCase();
|
|
@@ -2966,8 +3046,8 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
2966
3046
|
return async (input, init) => {
|
|
2967
3047
|
const response = await fetchFn(input, init);
|
|
2968
3048
|
if (response.ok) return response;
|
|
2969
|
-
if (!
|
|
2970
|
-
if (
|
|
3049
|
+
if (!continuesDiscovery2(response.status)) return response;
|
|
3050
|
+
if (requestMethodOf2(input, init) !== "GET") return response;
|
|
2971
3051
|
const candidates = oidcDiscoveryCandidates(requestUrlOf3(input));
|
|
2972
3052
|
if (candidates.length === 0) return response;
|
|
2973
3053
|
const headers = discoveryHeaders(input, init);
|
|
@@ -2997,7 +3077,7 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
2997
3077
|
releaseBodyDetached(response);
|
|
2998
3078
|
throw callerSignal.reason;
|
|
2999
3079
|
}
|
|
3000
|
-
if (
|
|
3080
|
+
if (continuesDiscovery2(probe.status)) continue;
|
|
3001
3081
|
return response;
|
|
3002
3082
|
}
|
|
3003
3083
|
let parsed;
|
|
@@ -3017,6 +3097,16 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
3017
3097
|
}
|
|
3018
3098
|
const source = probe.url || candidate;
|
|
3019
3099
|
releaseBodyDetached(response);
|
|
3100
|
+
if (isSynthesizedMetadataResponse(probe)) {
|
|
3101
|
+
return new Response(body, {
|
|
3102
|
+
status: 200,
|
|
3103
|
+
statusText: "OK",
|
|
3104
|
+
headers: {
|
|
3105
|
+
"content-type": "application/json",
|
|
3106
|
+
[SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3020
3110
|
console.warn(
|
|
3021
3111
|
`[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.`
|
|
3022
3112
|
);
|
|
@@ -3087,6 +3177,13 @@ var BaseOAuthClientProvider = class {
|
|
|
3087
3177
|
requestRefreshToken;
|
|
3088
3178
|
/** Server-configured scope, for the #2068 `offline_access` distinction. */
|
|
3089
3179
|
configuredScope;
|
|
3180
|
+
/** Per-flow verifier store (#2726); absent → the shared storage slot. */
|
|
3181
|
+
flowStore;
|
|
3182
|
+
/**
|
|
3183
|
+
* The authId of the flow this provider is driving: set by {@link state} on the
|
|
3184
|
+
* redirect leg and by {@link resumeAuthorizationFlow} on the callback leg.
|
|
3185
|
+
*/
|
|
3186
|
+
flowId;
|
|
3090
3187
|
constructor(serverUrl, oauthConfig) {
|
|
3091
3188
|
this.serverUrl = serverUrl;
|
|
3092
3189
|
this.storage = oauthConfig.storage;
|
|
@@ -3096,6 +3193,7 @@ var BaseOAuthClientProvider = class {
|
|
|
3096
3193
|
this.authorizationParams = oauthConfig.authorizationParams;
|
|
3097
3194
|
this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
|
|
3098
3195
|
this.configuredScope = oauthConfig.configuredScope;
|
|
3196
|
+
this.flowStore = oauthConfig.flowStore;
|
|
3099
3197
|
}
|
|
3100
3198
|
/**
|
|
3101
3199
|
* Load persisted scope into {@link cachedScope} before SDK `auth()` (which
|
|
@@ -3166,7 +3264,24 @@ var BaseOAuthClientProvider = class {
|
|
|
3166
3264
|
return metadata;
|
|
3167
3265
|
}
|
|
3168
3266
|
state() {
|
|
3169
|
-
|
|
3267
|
+
const state = generateOAuthState();
|
|
3268
|
+
this.flowId = state;
|
|
3269
|
+
return state;
|
|
3270
|
+
}
|
|
3271
|
+
/**
|
|
3272
|
+
* Bind this provider to the flow a callback's `state` names, so
|
|
3273
|
+
* {@link codeVerifier} reads that flow's verifier and no other (#2726). A
|
|
3274
|
+
* `state` that is not one of ours binds nothing — and, with a flow store, a
|
|
3275
|
+
* flow-less read then fails rather than reaching for the shared slot.
|
|
3276
|
+
*/
|
|
3277
|
+
resumeAuthorizationFlow(state) {
|
|
3278
|
+
this.flowId = parseOAuthState(state)?.authId;
|
|
3279
|
+
}
|
|
3280
|
+
/** Drop the bound flow's verifier once its exchange has completed. */
|
|
3281
|
+
async clearAuthorizationFlow() {
|
|
3282
|
+
if (this.flowStore && this.flowId) {
|
|
3283
|
+
await this.flowStore.clearCodeVerifier(this.flowId);
|
|
3284
|
+
}
|
|
3170
3285
|
}
|
|
3171
3286
|
async clientInformation(ctx) {
|
|
3172
3287
|
const preregistered = await this.storage.getClientInformation(
|
|
@@ -3311,9 +3426,26 @@ var BaseOAuthClientProvider = class {
|
|
|
3311
3426
|
}
|
|
3312
3427
|
}
|
|
3313
3428
|
async saveCodeVerifier(codeVerifier) {
|
|
3429
|
+
if (this.flowStore && this.flowId) {
|
|
3430
|
+
await this.flowStore.saveCodeVerifier(
|
|
3431
|
+
this.flowId,
|
|
3432
|
+
this.serverUrl,
|
|
3433
|
+
codeVerifier
|
|
3434
|
+
);
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3314
3437
|
await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
|
|
3315
3438
|
}
|
|
3316
3439
|
async codeVerifier() {
|
|
3440
|
+
if (this.flowStore) {
|
|
3441
|
+
const verifier2 = this.flowId ? await this.flowStore.getCodeVerifier(this.flowId, this.serverUrl) : void 0;
|
|
3442
|
+
if (!verifier2) {
|
|
3443
|
+
throw new Error(
|
|
3444
|
+
"No code verifier saved for this authorization flow. It may have been started in another browser tab or already completed; please re-authenticate."
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
return verifier2;
|
|
3448
|
+
}
|
|
3317
3449
|
const verifier = await this.storage.getCodeVerifier(this.serverUrl);
|
|
3318
3450
|
if (!verifier) {
|
|
3319
3451
|
throw new Error("No code verifier saved for session");
|
|
@@ -3360,6 +3492,10 @@ var BaseOAuthClientProvider = class {
|
|
|
3360
3492
|
await this.storage.clearTokens(this.serverUrl);
|
|
3361
3493
|
return;
|
|
3362
3494
|
case "verifier":
|
|
3495
|
+
if (this.flowStore) {
|
|
3496
|
+
await this.clearAuthorizationFlow();
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3363
3499
|
await this.storage.clearCodeVerifier(this.serverUrl);
|
|
3364
3500
|
return;
|
|
3365
3501
|
case "discovery":
|
|
@@ -3497,7 +3633,10 @@ function parseAuthChallengeFromResponse(response, context) {
|
|
|
3497
3633
|
if (status !== 401 && status !== 403) {
|
|
3498
3634
|
return void 0;
|
|
3499
3635
|
}
|
|
3500
|
-
const wwwAuthenticate = response.headers.get("WWW-Authenticate")
|
|
3636
|
+
const wwwAuthenticate = response.headers.get("WWW-Authenticate") || void 0;
|
|
3637
|
+
if (status === 403 && !wwwAuthenticate) {
|
|
3638
|
+
return void 0;
|
|
3639
|
+
}
|
|
3501
3640
|
const bearer = wwwAuthenticate ? parseWwwAuthenticateBearer(wwwAuthenticate) : {};
|
|
3502
3641
|
const requiredScopes = parseScopeString(bearer.scope);
|
|
3503
3642
|
return {
|
|
@@ -4097,7 +4236,7 @@ function serializeOAuthPersistBlob(snapshot) {
|
|
|
4097
4236
|
import { resolve as resolve5 } from "path";
|
|
4098
4237
|
|
|
4099
4238
|
// ../../core/auth/node/oauth-secrets.ts
|
|
4100
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
4239
|
+
import { createHmac, randomUUID as randomUUID2 } from "crypto";
|
|
4101
4240
|
import { OAuthTokensSchema as OAuthTokensSchema3 } from "@modelcontextprotocol/core";
|
|
4102
4241
|
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
4103
4242
|
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
@@ -4130,6 +4269,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
|
|
|
4130
4269
|
var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
|
|
4131
4270
|
var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
|
|
4132
4271
|
var IDP_SESSION_FIELD = "idp-session";
|
|
4272
|
+
var clientCredentialStampField = (field) => `${field}#owner`;
|
|
4273
|
+
var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
|
|
4274
|
+
function clientCredentialStamp(clientId, value) {
|
|
4275
|
+
return JSON.stringify({
|
|
4276
|
+
client_id: clientId,
|
|
4277
|
+
mac: credentialMac(clientId, value)
|
|
4278
|
+
});
|
|
4279
|
+
}
|
|
4280
|
+
function stampedOwner(stamp, value) {
|
|
4281
|
+
if (stamp === void 0) return void 0;
|
|
4282
|
+
let parsed;
|
|
4283
|
+
try {
|
|
4284
|
+
parsed = JSON.parse(stamp);
|
|
4285
|
+
} catch {
|
|
4286
|
+
return void 0;
|
|
4287
|
+
}
|
|
4288
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
4289
|
+
const { client_id: clientId, mac } = parsed;
|
|
4290
|
+
if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
|
|
4291
|
+
return void 0;
|
|
4292
|
+
}
|
|
4293
|
+
return clientId;
|
|
4294
|
+
}
|
|
4133
4295
|
function applyTokensPolicy(tokens, policy) {
|
|
4134
4296
|
if (policy === "none") return void 0;
|
|
4135
4297
|
if (policy === "access" && tokens.refresh_token !== void 0) {
|
|
@@ -4158,12 +4320,18 @@ function splitClientInformation(info, fields, secrets) {
|
|
|
4158
4320
|
if (client_secret === void 0 && registration_access_token === void 0) {
|
|
4159
4321
|
return info;
|
|
4160
4322
|
}
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4323
|
+
const put = (field, value) => {
|
|
4324
|
+
if (value === void 0) return;
|
|
4325
|
+
secrets[field] = value;
|
|
4326
|
+
if (fields.stamped && typeof info.client_id === "string") {
|
|
4327
|
+
secrets[clientCredentialStampField(field)] = clientCredentialStamp(
|
|
4328
|
+
info.client_id,
|
|
4329
|
+
value
|
|
4330
|
+
);
|
|
4331
|
+
}
|
|
4332
|
+
};
|
|
4333
|
+
put(fields.clientSecret, client_secret);
|
|
4334
|
+
put(fields.registrationToken, registration_access_token);
|
|
4167
4335
|
return publicInfo;
|
|
4168
4336
|
}
|
|
4169
4337
|
function joinClientInformation(info, fields, secrets) {
|
|
@@ -4182,15 +4350,18 @@ function joinClientInformation(info, fields, secrets) {
|
|
|
4182
4350
|
}
|
|
4183
4351
|
var issuerClientInfoFields = (issuer) => ({
|
|
4184
4352
|
clientSecret: issuerClientSecretField(issuer),
|
|
4185
|
-
registrationToken: issuerRegistrationTokenField(issuer)
|
|
4353
|
+
registrationToken: issuerRegistrationTokenField(issuer),
|
|
4354
|
+
stamped: true
|
|
4186
4355
|
});
|
|
4187
4356
|
var LEGACY_CLIENT_INFO_FIELDS = {
|
|
4188
4357
|
clientSecret: LEGACY_CLIENT_SECRET_FIELD,
|
|
4189
|
-
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
|
|
4358
|
+
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
4359
|
+
stamped: true
|
|
4190
4360
|
};
|
|
4191
4361
|
var PREREG_CLIENT_INFO_FIELDS = {
|
|
4192
4362
|
clientSecret: PREREG_CLIENT_SECRET_FIELD,
|
|
4193
|
-
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
|
|
4363
|
+
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
|
|
4364
|
+
stamped: false
|
|
4194
4365
|
};
|
|
4195
4366
|
function splitServerOAuthState(state, policy) {
|
|
4196
4367
|
const secrets = {};
|
|
@@ -4340,20 +4511,54 @@ function joinIdpSession(residue, secrets) {
|
|
|
4340
4511
|
return stored ? { ...residue, ...stored } : { ...residue };
|
|
4341
4512
|
}
|
|
4342
4513
|
function serverSecretFields(state) {
|
|
4514
|
+
const stamped = (field) => [
|
|
4515
|
+
field,
|
|
4516
|
+
clientCredentialStampField(field)
|
|
4517
|
+
];
|
|
4343
4518
|
const fields = /* @__PURE__ */ new Set([
|
|
4344
4519
|
LEGACY_TOKENS_FIELD,
|
|
4345
|
-
LEGACY_CLIENT_SECRET_FIELD,
|
|
4346
|
-
LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
4520
|
+
...stamped(LEGACY_CLIENT_SECRET_FIELD),
|
|
4521
|
+
...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
|
|
4347
4522
|
PREREG_CLIENT_SECRET_FIELD,
|
|
4348
4523
|
PREREG_REGISTRATION_TOKEN_FIELD
|
|
4349
4524
|
]);
|
|
4350
4525
|
for (const issuer of Object.keys(state?.byIssuer ?? {})) {
|
|
4351
4526
|
fields.add(issuerTokensField(issuer));
|
|
4352
|
-
|
|
4353
|
-
|
|
4527
|
+
for (const field of stamped(issuerClientSecretField(issuer))) {
|
|
4528
|
+
fields.add(field);
|
|
4529
|
+
}
|
|
4530
|
+
for (const field of stamped(issuerRegistrationTokenField(issuer))) {
|
|
4531
|
+
fields.add(field);
|
|
4532
|
+
}
|
|
4354
4533
|
}
|
|
4355
4534
|
return [...fields];
|
|
4356
4535
|
}
|
|
4536
|
+
function clientCredentialOwners(state) {
|
|
4537
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4538
|
+
const add = (info, fields) => {
|
|
4539
|
+
if (typeof info?.client_id !== "string") return;
|
|
4540
|
+
owners.set(fields.clientSecret, info.client_id);
|
|
4541
|
+
owners.set(fields.registrationToken, info.client_id);
|
|
4542
|
+
};
|
|
4543
|
+
add(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
|
|
4544
|
+
for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
|
|
4545
|
+
add(slot.clientInformation, issuerClientInfoFields(issuer));
|
|
4546
|
+
}
|
|
4547
|
+
return owners;
|
|
4548
|
+
}
|
|
4549
|
+
function restampClientCredentials(state, secrets) {
|
|
4550
|
+
const out = { ...secrets };
|
|
4551
|
+
for (const [field, clientId] of clientCredentialOwners(state)) {
|
|
4552
|
+
const stampField = clientCredentialStampField(field);
|
|
4553
|
+
const value = Object.hasOwn(out, field) ? out[field] : void 0;
|
|
4554
|
+
if (value === void 0) {
|
|
4555
|
+
delete out[stampField];
|
|
4556
|
+
} else if (stampedOwner(out[stampField], value) === void 0) {
|
|
4557
|
+
setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
return out;
|
|
4561
|
+
}
|
|
4357
4562
|
function clientInfoHasPlaintext(info) {
|
|
4358
4563
|
return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
|
|
4359
4564
|
}
|
|
@@ -4394,6 +4599,44 @@ function coveredLocations(location, byLocation) {
|
|
|
4394
4599
|
([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
|
|
4395
4600
|
).map(([recorded, keys]) => ({ location: recorded, keys }));
|
|
4396
4601
|
}
|
|
4602
|
+
var emptyKeys = () => ({
|
|
4603
|
+
servers: /* @__PURE__ */ new Set(),
|
|
4604
|
+
idpSessions: /* @__PURE__ */ new Set(),
|
|
4605
|
+
owners: /* @__PURE__ */ new Map()
|
|
4606
|
+
});
|
|
4607
|
+
function candidateFields(location, byLocation) {
|
|
4608
|
+
const fields = /* @__PURE__ */ new Map();
|
|
4609
|
+
for (const { keys } of coveredLocations(location, byLocation)) {
|
|
4610
|
+
for (const [url, owned] of keys.owners) {
|
|
4611
|
+
const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
|
|
4612
|
+
for (const field of owned.keys()) kept.add(field);
|
|
4613
|
+
fields.set(url, kept);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
return fields;
|
|
4617
|
+
}
|
|
4618
|
+
function addMissingOwners(target, source) {
|
|
4619
|
+
for (const [url, fields] of source) {
|
|
4620
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
4621
|
+
for (const [field, clientId] of fields) {
|
|
4622
|
+
if (!kept.has(field)) kept.set(field, clientId);
|
|
4623
|
+
}
|
|
4624
|
+
if (kept.size > 0) target.set(url, kept);
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
function mergeOwners(target, source) {
|
|
4628
|
+
let changed = false;
|
|
4629
|
+
for (const [url, fields] of source) {
|
|
4630
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
4631
|
+
for (const [field, clientId] of fields) {
|
|
4632
|
+
if (kept.get(field) === clientId) continue;
|
|
4633
|
+
kept.set(field, clientId);
|
|
4634
|
+
changed = true;
|
|
4635
|
+
}
|
|
4636
|
+
if (kept.size > 0) target.set(url, kept);
|
|
4637
|
+
}
|
|
4638
|
+
return changed;
|
|
4639
|
+
}
|
|
4397
4640
|
var warnedLedgerFailures = /* @__PURE__ */ new Set();
|
|
4398
4641
|
function warnLedgerFailure(what, error) {
|
|
4399
4642
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -4407,6 +4650,19 @@ function warnLedgerFailure(what, error) {
|
|
|
4407
4650
|
function isRecord2(value) {
|
|
4408
4651
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4409
4652
|
}
|
|
4653
|
+
function parseOwners(value) {
|
|
4654
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4655
|
+
if (!isRecord2(value)) return owners;
|
|
4656
|
+
for (const [url, fields] of Object.entries(value)) {
|
|
4657
|
+
if (!isRecord2(fields)) continue;
|
|
4658
|
+
const byField = /* @__PURE__ */ new Map();
|
|
4659
|
+
for (const [field, clientId] of Object.entries(fields)) {
|
|
4660
|
+
if (typeof clientId === "string") byField.set(field, clientId);
|
|
4661
|
+
}
|
|
4662
|
+
if (byField.size > 0) owners.set(url, byField);
|
|
4663
|
+
}
|
|
4664
|
+
return owners;
|
|
4665
|
+
}
|
|
4410
4666
|
function stringsOf(value) {
|
|
4411
4667
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
4412
4668
|
}
|
|
@@ -4428,7 +4684,9 @@ function parseLedger(raw) {
|
|
|
4428
4684
|
if (!isRecord2(keys)) continue;
|
|
4429
4685
|
byLocation.set(location, {
|
|
4430
4686
|
servers: new Set(stringsOf(keys.servers)),
|
|
4431
|
-
idpSessions: new Set(stringsOf(keys.idpSessions))
|
|
4687
|
+
idpSessions: new Set(stringsOf(keys.idpSessions)),
|
|
4688
|
+
owners: parseOwners(keys.owners),
|
|
4689
|
+
...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
|
|
4432
4690
|
});
|
|
4433
4691
|
}
|
|
4434
4692
|
if (byLocation.size > 0) ledger.set(namespace, byLocation);
|
|
@@ -4442,7 +4700,19 @@ function serializeLedger(ledger) {
|
|
|
4442
4700
|
Object.fromEntries(
|
|
4443
4701
|
[...byLocation].map(([location, keys]) => [
|
|
4444
4702
|
location,
|
|
4445
|
-
{
|
|
4703
|
+
{
|
|
4704
|
+
servers: [...keys.servers],
|
|
4705
|
+
idpSessions: [...keys.idpSessions],
|
|
4706
|
+
...keys.owners.size > 0 && {
|
|
4707
|
+
owners: Object.fromEntries(
|
|
4708
|
+
[...keys.owners].map(([url, fields]) => [
|
|
4709
|
+
url,
|
|
4710
|
+
Object.fromEntries(fields)
|
|
4711
|
+
])
|
|
4712
|
+
)
|
|
4713
|
+
},
|
|
4714
|
+
...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
|
|
4715
|
+
}
|
|
4446
4716
|
])
|
|
4447
4717
|
)
|
|
4448
4718
|
])
|
|
@@ -4454,7 +4724,7 @@ async function writeLedger(stateFilePath, ledger) {
|
|
|
4454
4724
|
if (ledger.size === 0) await deleteStoreFile(path7);
|
|
4455
4725
|
else await writeStoreFile(path7, serializeLedger(ledger));
|
|
4456
4726
|
}
|
|
4457
|
-
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
|
|
4727
|
+
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
|
|
4458
4728
|
try {
|
|
4459
4729
|
const ledger = parseLedger(
|
|
4460
4730
|
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
@@ -4468,7 +4738,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
4468
4738
|
let keys = byLocation.get(location);
|
|
4469
4739
|
let changed = false;
|
|
4470
4740
|
if (!keys) {
|
|
4471
|
-
keys =
|
|
4741
|
+
keys = emptyKeys();
|
|
4472
4742
|
byLocation.set(location, keys);
|
|
4473
4743
|
changed = true;
|
|
4474
4744
|
}
|
|
@@ -4482,12 +4752,105 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
4482
4752
|
keys.idpSessions.add(issuer);
|
|
4483
4753
|
changed = true;
|
|
4484
4754
|
}
|
|
4755
|
+
if (mergeOwners(keys.owners, owners)) changed = true;
|
|
4485
4756
|
if (changed) await writeLedger(stateFilePath, ledger);
|
|
4486
4757
|
} catch (error) {
|
|
4487
4758
|
warnLedgerFailure("update", error);
|
|
4488
4759
|
}
|
|
4489
4760
|
}
|
|
4490
|
-
async function
|
|
4761
|
+
async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
|
|
4762
|
+
const ledger = parseLedger(
|
|
4763
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
4764
|
+
);
|
|
4765
|
+
const location = await secretStoreLocation(secretStore);
|
|
4766
|
+
let changed = false;
|
|
4767
|
+
for (const [namespace, byLocation] of ledger) {
|
|
4768
|
+
if (namespace === into) continue;
|
|
4769
|
+
for (const [recorded, keys] of byLocation) {
|
|
4770
|
+
if (recorded === location || keys.owners.size === 0) continue;
|
|
4771
|
+
if (keys.owedTo === into) continue;
|
|
4772
|
+
keys.owedTo = into;
|
|
4773
|
+
changed = true;
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
if (changed) await writeLedger(stateFilePath, ledger);
|
|
4777
|
+
}
|
|
4778
|
+
async function pendingCarries(stateFilePath, secretStore, current) {
|
|
4779
|
+
const ledger = parseLedger(
|
|
4780
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
4781
|
+
);
|
|
4782
|
+
const location = await secretStoreLocation(secretStore);
|
|
4783
|
+
const carries = [];
|
|
4784
|
+
for (const [namespace, byLocation] of ledger) {
|
|
4785
|
+
const owed = coveredLocations(location, byLocation).some(
|
|
4786
|
+
({ keys }) => keys.owedTo === current
|
|
4787
|
+
);
|
|
4788
|
+
if (owed) {
|
|
4789
|
+
carries.push({
|
|
4790
|
+
namespace,
|
|
4791
|
+
fields: candidateFields(location, byLocation)
|
|
4792
|
+
});
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
return carries.reverse();
|
|
4796
|
+
}
|
|
4797
|
+
async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
|
|
4798
|
+
let carried = true;
|
|
4799
|
+
for (const [url, fields] of recorded) {
|
|
4800
|
+
for (const field of fields) {
|
|
4801
|
+
const clientId = now.get(url)?.get(field);
|
|
4802
|
+
if (clientId === void 0) continue;
|
|
4803
|
+
try {
|
|
4804
|
+
const from = oauthSecretServerId(url, namespace);
|
|
4805
|
+
const to = oauthSecretServerId(url, current);
|
|
4806
|
+
const stampField = clientCredentialStampField(field);
|
|
4807
|
+
const value = await secretStoreGetStrict(secretStore, from, field);
|
|
4808
|
+
if (value === null) continue;
|
|
4809
|
+
const stamp = await secretStoreGetStrict(secretStore, from, stampField);
|
|
4810
|
+
if (stamp === null || stampedOwner(stamp, value) !== clientId) {
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
if (await secretStoreGetStrict(secretStore, to, field) !== null) {
|
|
4814
|
+
continue;
|
|
4815
|
+
}
|
|
4816
|
+
let byLocation = ledger.get(current);
|
|
4817
|
+
if (!byLocation) {
|
|
4818
|
+
byLocation = /* @__PURE__ */ new Map();
|
|
4819
|
+
ledger.set(current, byLocation);
|
|
4820
|
+
}
|
|
4821
|
+
const target = byLocation.get(location) ?? emptyKeys();
|
|
4822
|
+
byLocation.set(location, target);
|
|
4823
|
+
const isNew = !target.servers.has(url);
|
|
4824
|
+
target.servers.add(url);
|
|
4825
|
+
const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
|
|
4826
|
+
if (mergeOwners(target.owners, owner) || isNew) {
|
|
4827
|
+
await writeLedger(stateFilePath, ledger);
|
|
4828
|
+
}
|
|
4829
|
+
await secretStore.set(to, stampField, stamp);
|
|
4830
|
+
await secretStore.set(to, field, value);
|
|
4831
|
+
} catch (error) {
|
|
4832
|
+
carried = false;
|
|
4833
|
+
warnLedgerFailure(
|
|
4834
|
+
"carry over a superseded namespace recorded in",
|
|
4835
|
+
error
|
|
4836
|
+
);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
return carried;
|
|
4841
|
+
}
|
|
4842
|
+
async function recordedNamespaces(stateFilePath) {
|
|
4843
|
+
try {
|
|
4844
|
+
const ledger = parseLedger(
|
|
4845
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
4846
|
+
);
|
|
4847
|
+
return [...ledger.keys()].reverse();
|
|
4848
|
+
} catch (error) {
|
|
4849
|
+
warnLedgerFailure("read", error);
|
|
4850
|
+
return [];
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
|
|
4491
4854
|
let ledger;
|
|
4492
4855
|
let location;
|
|
4493
4856
|
try {
|
|
@@ -4500,8 +4863,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
4500
4863
|
return;
|
|
4501
4864
|
}
|
|
4502
4865
|
let changed = false;
|
|
4503
|
-
for (const [namespace, byLocation] of ledger) {
|
|
4866
|
+
for (const [namespace, byLocation] of [...ledger].reverse()) {
|
|
4504
4867
|
if (namespace === current) continue;
|
|
4868
|
+
if (current !== void 0 && coveredLocations(location, byLocation).some(
|
|
4869
|
+
({ keys }) => keys.owedTo === current
|
|
4870
|
+
) && !await carryOwed(
|
|
4871
|
+
stateFilePath,
|
|
4872
|
+
ledger,
|
|
4873
|
+
location,
|
|
4874
|
+
secretStore,
|
|
4875
|
+
namespace,
|
|
4876
|
+
current,
|
|
4877
|
+
candidateFields(location, byLocation),
|
|
4878
|
+
owners
|
|
4879
|
+
)) {
|
|
4880
|
+
break;
|
|
4881
|
+
}
|
|
4505
4882
|
for (const { location: recorded, keys } of coveredLocations(
|
|
4506
4883
|
location,
|
|
4507
4884
|
byLocation
|
|
@@ -4526,12 +4903,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
4526
4903
|
if (!purged || recorded !== location) continue;
|
|
4527
4904
|
byLocation.delete(recorded);
|
|
4528
4905
|
if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
|
|
4529
|
-
const keyring = byLocation.get(KEYRING_LOCATION) ??
|
|
4530
|
-
|
|
4531
|
-
idpSessions: /* @__PURE__ */ new Set()
|
|
4532
|
-
};
|
|
4906
|
+
const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
|
|
4907
|
+
addMissingOwners(keyring.owners, keys.owners);
|
|
4533
4908
|
for (const url of keys.servers) keyring.servers.add(url);
|
|
4534
4909
|
for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
|
|
4910
|
+
keyring.owedTo ??= keys.owedTo;
|
|
4535
4911
|
byLocation.set(KEYRING_LOCATION, keyring);
|
|
4536
4912
|
}
|
|
4537
4913
|
changed = true;
|
|
@@ -4642,7 +5018,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4642
5018
|
const existing = parseSecretsNamespace(raw);
|
|
4643
5019
|
if (existing !== void 0) {
|
|
4644
5020
|
if (locked) {
|
|
4645
|
-
await purgeSupersededNamespaces(
|
|
5021
|
+
await purgeSupersededNamespaces(
|
|
5022
|
+
filePath,
|
|
5023
|
+
secretStore,
|
|
5024
|
+
existing,
|
|
5025
|
+
snapshotClientOwners(parseOAuthPersistBlob(raw))
|
|
5026
|
+
);
|
|
4646
5027
|
}
|
|
4647
5028
|
return existing;
|
|
4648
5029
|
}
|
|
@@ -4650,42 +5031,53 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4650
5031
|
if (raw !== null && snapshot === null) {
|
|
4651
5032
|
throw new OAuthStateFileUnrecognizedError(filePath, "save");
|
|
4652
5033
|
}
|
|
4653
|
-
if (locked) {
|
|
4654
|
-
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
4655
|
-
}
|
|
4656
5034
|
const namespace = newSecretsNamespace();
|
|
4657
|
-
if (snapshot === null)
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
})),
|
|
4664
|
-
...Object.keys(snapshot.idpSessions).map((issuer) => ({
|
|
4665
|
-
legacyId: oauthIdpSecretServerId(issuer),
|
|
4666
|
-
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
4667
|
-
fields: [IDP_SESSION_FIELD]
|
|
4668
|
-
}))
|
|
4669
|
-
];
|
|
4670
|
-
if (moves.length === 0) return namespace;
|
|
5035
|
+
if (snapshot === null || Object.keys(snapshot.servers).length === 0 && Object.keys(snapshot.idpSessions).length === 0) {
|
|
5036
|
+
if (locked) {
|
|
5037
|
+
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
5038
|
+
}
|
|
5039
|
+
return namespace;
|
|
5040
|
+
}
|
|
4671
5041
|
if (!locked) {
|
|
4672
5042
|
throw new SecretStoreUnavailableError(
|
|
4673
5043
|
`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).`
|
|
4674
5044
|
);
|
|
4675
5045
|
}
|
|
5046
|
+
const values = await readEntrySecrets(
|
|
5047
|
+
snapshot,
|
|
5048
|
+
secretStore,
|
|
5049
|
+
void 0,
|
|
5050
|
+
await recordedNamespaces(filePath)
|
|
5051
|
+
);
|
|
5052
|
+
const owners = snapshotClientOwners(snapshot);
|
|
4676
5053
|
await recordNamespaceKeys(
|
|
4677
5054
|
filePath,
|
|
4678
5055
|
secretStore,
|
|
4679
5056
|
namespace,
|
|
4680
5057
|
Object.keys(snapshot.servers),
|
|
4681
|
-
Object.keys(snapshot.idpSessions)
|
|
5058
|
+
Object.keys(snapshot.idpSessions),
|
|
5059
|
+
writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
|
|
4682
5060
|
);
|
|
5061
|
+
await markUncarriedNamespaces(filePath, secretStore, namespace);
|
|
4683
5062
|
const touched = [];
|
|
4684
5063
|
try {
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
5064
|
+
const copies = [
|
|
5065
|
+
// Restamped: a value read from the legacy ids predates stamps, and a
|
|
5066
|
+
// fallback's stamp may belong to a value read from elsewhere (#2734).
|
|
5067
|
+
...[...values.servers].map(([url, secrets]) => ({
|
|
5068
|
+
scopedId: oauthSecretServerId(url, namespace),
|
|
5069
|
+
secrets: restampClientCredentials(
|
|
5070
|
+
getOwnEntry(snapshot.servers, url) ?? {},
|
|
5071
|
+
secrets
|
|
5072
|
+
)
|
|
5073
|
+
})),
|
|
5074
|
+
...[...values.idpSessions].map(([issuer, secrets]) => ({
|
|
5075
|
+
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
5076
|
+
secrets
|
|
5077
|
+
}))
|
|
5078
|
+
];
|
|
5079
|
+
for (const { scopedId, secrets } of copies) {
|
|
5080
|
+
for (const [field, value] of Object.entries(secrets)) {
|
|
4689
5081
|
touched.push({ serverId: scopedId, field, value: null });
|
|
4690
5082
|
await secretStore.set(scopedId, field, value);
|
|
4691
5083
|
}
|
|
@@ -4695,7 +5087,14 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4695
5087
|
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
4696
5088
|
throw error;
|
|
4697
5089
|
}
|
|
4698
|
-
|
|
5090
|
+
await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
|
|
5091
|
+
const legacyIds = [
|
|
5092
|
+
...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
|
|
5093
|
+
...Object.keys(snapshot.idpSessions).map(
|
|
5094
|
+
(issuer) => oauthIdpSecretServerId(issuer)
|
|
5095
|
+
)
|
|
5096
|
+
];
|
|
5097
|
+
for (const legacyId of legacyIds) {
|
|
4699
5098
|
try {
|
|
4700
5099
|
await secretStore.deleteAllForServer(legacyId);
|
|
4701
5100
|
} catch (error) {
|
|
@@ -4748,6 +5147,14 @@ function preserveNonDurableSecrets(diskSecrets, secrets) {
|
|
|
4748
5147
|
return keep;
|
|
4749
5148
|
}
|
|
4750
5149
|
async function writeOAuthSections(filePath, snapshot, sections, secretStore = defaultSecretStore()) {
|
|
5150
|
+
await writeOAuthSectionsFrom(
|
|
5151
|
+
filePath,
|
|
5152
|
+
async () => snapshot,
|
|
5153
|
+
sections,
|
|
5154
|
+
secretStore
|
|
5155
|
+
);
|
|
5156
|
+
}
|
|
5157
|
+
async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secretStore) {
|
|
4751
5158
|
const policy = getPersistTokensPolicy();
|
|
4752
5159
|
const durable = await secretStoreIsDurable(secretStore);
|
|
4753
5160
|
await withOAuthStateLock(filePath, "save", async (locked) => {
|
|
@@ -4831,6 +5238,7 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4831
5238
|
unconfirmed = null;
|
|
4832
5239
|
namespace = diskNamespace;
|
|
4833
5240
|
}
|
|
5241
|
+
const snapshot = await resolveSnapshot(disk, namespace);
|
|
4834
5242
|
const effective = sections ? {
|
|
4835
5243
|
servers: sections.servers && [...new Set(sections.servers)],
|
|
4836
5244
|
idpSessions: sections.idpSessions && [
|
|
@@ -4856,7 +5264,22 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4856
5264
|
secretStore,
|
|
4857
5265
|
namespace,
|
|
4858
5266
|
Object.keys(merged.servers),
|
|
4859
|
-
Object.keys(merged.idpSessions)
|
|
5267
|
+
Object.keys(merged.idpSessions),
|
|
5268
|
+
// Only the entries this save writes: another entry's secret in
|
|
5269
|
+
// this store was written for whatever client it was written for,
|
|
5270
|
+
// and relabeling it from the file would misattribute it (#2672).
|
|
5271
|
+
writtenClientOwners(
|
|
5272
|
+
{
|
|
5273
|
+
servers: Object.fromEntries(
|
|
5274
|
+
(effective.servers ?? []).flatMap((url) => {
|
|
5275
|
+
const entry = getOwnEntry(merged.servers, url);
|
|
5276
|
+
return entry === void 0 ? [] : [[url, entry]];
|
|
5277
|
+
})
|
|
5278
|
+
),
|
|
5279
|
+
idpSessions: {}
|
|
5280
|
+
},
|
|
5281
|
+
splitSecretsOf
|
|
5282
|
+
)
|
|
4860
5283
|
);
|
|
4861
5284
|
for (const url of effective.servers ?? []) {
|
|
4862
5285
|
const serverId = oauthSecretServerId(url, namespace);
|
|
@@ -4864,7 +5287,8 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4864
5287
|
const candidates = [
|
|
4865
5288
|
.../* @__PURE__ */ new Set([
|
|
4866
5289
|
...serverSecretFields(getOwnEntry(disk?.servers, url)),
|
|
4867
|
-
...serverSecretFields(next)
|
|
5290
|
+
...serverSecretFields(next),
|
|
5291
|
+
...[...restoreBaseline.values()].filter((prior) => prior.serverId === serverId).map(({ field }) => field)
|
|
4868
5292
|
])
|
|
4869
5293
|
];
|
|
4870
5294
|
const entryPrior = await snapshotSecretFields(
|
|
@@ -4873,8 +5297,12 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4873
5297
|
candidates
|
|
4874
5298
|
);
|
|
4875
5299
|
priorSecrets.push(...entryPrior);
|
|
5300
|
+
const entryPriorValue = new Map(
|
|
5301
|
+
entryPrior.map(({ field, value }) => [field, value])
|
|
5302
|
+
);
|
|
4876
5303
|
if (next === void 0) {
|
|
4877
5304
|
for (const field of candidates) {
|
|
5305
|
+
if (entryPriorValue.get(field) === null) continue;
|
|
4878
5306
|
ourWrites.set(priorKey({ serverId, field }), null);
|
|
4879
5307
|
}
|
|
4880
5308
|
await secretStore.deleteAllForServer(serverId);
|
|
@@ -4889,10 +5317,9 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4889
5317
|
}
|
|
4890
5318
|
const { residue, secrets } = splitServerOAuthState(next, policy);
|
|
4891
5319
|
for (const field of candidates) {
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
);
|
|
5320
|
+
const value = secrets[field] ?? null;
|
|
5321
|
+
if (value === entryPriorValue.get(field)) continue;
|
|
5322
|
+
ourWrites.set(priorKey({ serverId, field }), value);
|
|
4896
5323
|
}
|
|
4897
5324
|
setOwnEntry(merged.servers, url, residue);
|
|
4898
5325
|
if (!durable) {
|
|
@@ -5035,26 +5462,109 @@ function secretRequestsFor(snapshot, namespace) {
|
|
|
5035
5462
|
}
|
|
5036
5463
|
return requests;
|
|
5037
5464
|
}
|
|
5038
|
-
|
|
5039
|
-
const
|
|
5040
|
-
|
|
5465
|
+
function fillMissingSecrets(target, source) {
|
|
5466
|
+
for (const [field, value] of Object.entries(source ?? {})) {
|
|
5467
|
+
if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
function writtenClientOwners(snapshot, secretsOf) {
|
|
5471
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5472
|
+
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
5473
|
+
const secrets = secretsOf(url, state);
|
|
5474
|
+
const fields = new Map(
|
|
5475
|
+
[...clientCredentialOwners(state)].filter(
|
|
5476
|
+
([field]) => Object.hasOwn(secrets, field)
|
|
5477
|
+
)
|
|
5478
|
+
);
|
|
5479
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
5480
|
+
}
|
|
5481
|
+
return owners;
|
|
5482
|
+
}
|
|
5483
|
+
var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
|
|
5484
|
+
function snapshotClientOwners(snapshot) {
|
|
5485
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5486
|
+
for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
|
|
5487
|
+
const fields = clientCredentialOwners(state);
|
|
5488
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
5489
|
+
}
|
|
5490
|
+
return owners;
|
|
5491
|
+
}
|
|
5492
|
+
async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5493
|
+
const servers = /* @__PURE__ */ new Map();
|
|
5494
|
+
const idpSessions = /* @__PURE__ */ new Map();
|
|
5495
|
+
for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
|
|
5496
|
+
for (const issuer of Object.keys(snapshot.idpSessions)) {
|
|
5497
|
+
idpSessions.set(issuer, {});
|
|
5498
|
+
}
|
|
5499
|
+
for (const source of [namespace, ...fallbacks]) {
|
|
5500
|
+
const requests = secretRequestsFor(snapshot, source);
|
|
5501
|
+
if (requests.length === 0) break;
|
|
5502
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
5503
|
+
for (const [url, target] of servers) {
|
|
5504
|
+
fillMissingSecrets(target, values[oauthSecretServerId(url, source)]);
|
|
5505
|
+
}
|
|
5506
|
+
for (const [issuer, target] of idpSessions) {
|
|
5507
|
+
fillMissingSecrets(
|
|
5508
|
+
target,
|
|
5509
|
+
values[oauthIdpSecretServerId(issuer, source)]
|
|
5510
|
+
);
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
const owners = snapshotClientOwners(snapshot);
|
|
5514
|
+
for (const { namespace: from, fields: recorded } of carries) {
|
|
5515
|
+
const requests = [];
|
|
5516
|
+
for (const [url, fields] of recorded) {
|
|
5517
|
+
const target = servers.get(url);
|
|
5518
|
+
if (!target) continue;
|
|
5519
|
+
const wanted = [...fields].filter(
|
|
5520
|
+
(field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
|
|
5521
|
+
);
|
|
5522
|
+
if (wanted.length > 0) {
|
|
5523
|
+
requests.push({
|
|
5524
|
+
serverId: oauthSecretServerId(url, from),
|
|
5525
|
+
fields: wanted.flatMap((field) => [
|
|
5526
|
+
field,
|
|
5527
|
+
clientCredentialStampField(field)
|
|
5528
|
+
])
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
if (requests.length === 0) continue;
|
|
5533
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
5534
|
+
for (const [url, target] of servers) {
|
|
5535
|
+
const found = values[oauthSecretServerId(url, from)] ?? {};
|
|
5536
|
+
for (const [field, clientId] of owners.get(url) ?? []) {
|
|
5537
|
+
const value = Object.hasOwn(found, field) ? found[field] : void 0;
|
|
5538
|
+
const stampField = clientCredentialStampField(field);
|
|
5539
|
+
if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
|
|
5540
|
+
continue;
|
|
5541
|
+
}
|
|
5542
|
+
setOwnEntry(target, field, value);
|
|
5543
|
+
setOwnEntry(target, stampField, found[stampField]);
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
return { servers, idpSessions };
|
|
5548
|
+
}
|
|
5549
|
+
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5550
|
+
const values = await readEntrySecrets(
|
|
5551
|
+
snapshot,
|
|
5552
|
+
secretStore,
|
|
5553
|
+
namespace,
|
|
5554
|
+
fallbacks,
|
|
5555
|
+
carries
|
|
5556
|
+
);
|
|
5041
5557
|
return {
|
|
5042
5558
|
servers: Object.fromEntries(
|
|
5043
5559
|
Object.entries(snapshot.servers).map(([url, state]) => [
|
|
5044
5560
|
url,
|
|
5045
|
-
joinServerOAuthState(
|
|
5046
|
-
state,
|
|
5047
|
-
values[oauthSecretServerId(url, namespace)] ?? {}
|
|
5048
|
-
)
|
|
5561
|
+
joinServerOAuthState(state, values.servers.get(url) ?? {})
|
|
5049
5562
|
])
|
|
5050
5563
|
),
|
|
5051
5564
|
idpSessions: Object.fromEntries(
|
|
5052
5565
|
Object.entries(snapshot.idpSessions).map(([issuer, session]) => [
|
|
5053
5566
|
issuer,
|
|
5054
|
-
joinIdpSession(
|
|
5055
|
-
session,
|
|
5056
|
-
values[oauthIdpSecretServerId(issuer, namespace)] ?? {}
|
|
5057
|
-
)
|
|
5567
|
+
joinIdpSession(session, values.idpSessions.get(issuer) ?? {})
|
|
5058
5568
|
])
|
|
5059
5569
|
)
|
|
5060
5570
|
};
|
|
@@ -5070,7 +5580,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
5070
5580
|
secretStore,
|
|
5071
5581
|
namespace,
|
|
5072
5582
|
Object.keys(fresh.servers),
|
|
5073
|
-
Object.keys(fresh.idpSessions)
|
|
5583
|
+
Object.keys(fresh.idpSessions),
|
|
5584
|
+
writtenClientOwners(fresh, splitSecretsOf)
|
|
5074
5585
|
);
|
|
5075
5586
|
}
|
|
5076
5587
|
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
@@ -5121,7 +5632,10 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
5121
5632
|
warnMigrationFailure(error);
|
|
5122
5633
|
}
|
|
5123
5634
|
}
|
|
5124
|
-
|
|
5635
|
+
const namespace = parseSecretsNamespace(raw);
|
|
5636
|
+
const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
|
|
5637
|
+
const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
|
|
5638
|
+
return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
|
|
5125
5639
|
});
|
|
5126
5640
|
}
|
|
5127
5641
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
@@ -5748,6 +6262,11 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
5748
6262
|
// rendered by the list panel instead of vanishing into an unhandled
|
|
5749
6263
|
// rejection — which read as "this server has no tools" (#1953).
|
|
5750
6264
|
error = null;
|
|
6265
|
+
// Whether a fetch has committed a list this session. Until it has, the empty
|
|
6266
|
+
// `items` mean "not loaded yet", not "the server has none" — a distinction a
|
|
6267
|
+
// count of the list cannot make from `items` alone (#2607). Set and cleared
|
|
6268
|
+
// just before the `*Change` dispatch, so a reader keyed on that event sees it.
|
|
6269
|
+
loaded = false;
|
|
5751
6270
|
config;
|
|
5752
6271
|
// Debounce a burst of `list_changed` notifications into a single
|
|
5753
6272
|
// refresh (or one indicator light) once it settles.
|
|
@@ -5783,6 +6302,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
5783
6302
|
this.listChangedTimer = null;
|
|
5784
6303
|
}
|
|
5785
6304
|
this.items = [];
|
|
6305
|
+
this.loaded = false;
|
|
5786
6306
|
this.dispatchChange();
|
|
5787
6307
|
this.setListChanged(false);
|
|
5788
6308
|
this.setError(null);
|
|
@@ -5841,6 +6361,14 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
5841
6361
|
getItems() {
|
|
5842
6362
|
return [...this.items];
|
|
5843
6363
|
}
|
|
6364
|
+
/**
|
|
6365
|
+
* Whether a fetch has committed a list since the last connect. False while
|
|
6366
|
+
* the connect-time load is in flight, when it was deferred (paginated mode),
|
|
6367
|
+
* and after a disconnect. Changes only alongside the `*Change` event (#2607).
|
|
6368
|
+
*/
|
|
6369
|
+
isLoaded() {
|
|
6370
|
+
return this.loaded;
|
|
6371
|
+
}
|
|
5844
6372
|
/** Whether a `list_changed` arrived since the last refresh (indicator on). */
|
|
5845
6373
|
getListChanged() {
|
|
5846
6374
|
return this.listChanged;
|
|
@@ -5934,6 +6462,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
5934
6462
|
/** Commit a fetched list as the current one and notify subscribers. */
|
|
5935
6463
|
applyItems(items) {
|
|
5936
6464
|
this.items = items;
|
|
6465
|
+
this.loaded = true;
|
|
5937
6466
|
this.dispatchChange();
|
|
5938
6467
|
}
|
|
5939
6468
|
dispatchChange() {
|
|
@@ -5944,6 +6473,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
5944
6473
|
this.unsubscribe?.();
|
|
5945
6474
|
this.unsubscribe = null;
|
|
5946
6475
|
this.items = [];
|
|
6476
|
+
this.loaded = false;
|
|
5947
6477
|
}
|
|
5948
6478
|
};
|
|
5949
6479
|
|
|
@@ -6027,6 +6557,16 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
6027
6557
|
return;
|
|
6028
6558
|
}
|
|
6029
6559
|
};
|
|
6560
|
+
const onRequestFailed = (event) => {
|
|
6561
|
+
const { id, reason } = event.detail;
|
|
6562
|
+
const requestEntry = this.pendingRequestEntries.get(id);
|
|
6563
|
+
if (!requestEntry) return;
|
|
6564
|
+
this.pendingRequestEntries.delete(id);
|
|
6565
|
+
requestEntry.clientError = reason;
|
|
6566
|
+
requestEntry.duration = Date.now() - requestEntry.timestamp.getTime();
|
|
6567
|
+
this.dispatchTypedEvent("message", requestEntry);
|
|
6568
|
+
this.dispatchTypedEvent("messagesChange", this.getMessages());
|
|
6569
|
+
};
|
|
6030
6570
|
const onStatusChange = () => {
|
|
6031
6571
|
if (isTerminalStatus(this.client?.getStatus())) {
|
|
6032
6572
|
this.messages = [];
|
|
@@ -6036,11 +6576,13 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
6036
6576
|
};
|
|
6037
6577
|
this.client.addEventListener("message", onMessage);
|
|
6038
6578
|
this.client.addEventListener("responseRejected", onResponseRejected);
|
|
6579
|
+
this.client.addEventListener("requestFailed", onRequestFailed);
|
|
6039
6580
|
this.client.addEventListener("statusChange", onStatusChange);
|
|
6040
6581
|
this.unsubscribe = () => {
|
|
6041
6582
|
if (this.client) {
|
|
6042
6583
|
this.client.removeEventListener("message", onMessage);
|
|
6043
6584
|
this.client.removeEventListener("responseRejected", onResponseRejected);
|
|
6585
|
+
this.client.removeEventListener("requestFailed", onRequestFailed);
|
|
6044
6586
|
this.client.removeEventListener("statusChange", onStatusChange);
|
|
6045
6587
|
}
|
|
6046
6588
|
this.client = null;
|
|
@@ -6400,6 +6942,12 @@ var DirectoryReadResultSchema = z2.looseObject({
|
|
|
6400
6942
|
resources: z2.array(DirectoryChildSchema),
|
|
6401
6943
|
nextCursor: z2.string().optional()
|
|
6402
6944
|
});
|
|
6945
|
+
var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
|
|
6946
|
+
{
|
|
6947
|
+
ttlMs: z2.int().min(0),
|
|
6948
|
+
cacheScope: z2.enum(["public", "private"])
|
|
6949
|
+
}
|
|
6950
|
+
);
|
|
6403
6951
|
|
|
6404
6952
|
// ../../core/mcp/state/managedSkillsState.ts
|
|
6405
6953
|
var REPEATED_CURSOR_MESSAGE = "Server repeated a pagination cursor in skills/list; stopped to avoid an infinite walk.";
|
|
@@ -7212,6 +7760,10 @@ function checkSkillNameCollisions(entries) {
|
|
|
7212
7760
|
}
|
|
7213
7761
|
|
|
7214
7762
|
// ../../core/mcp/skillsVerification.ts
|
|
7763
|
+
var DEFAULT_SKILL_BUDGET_HINT = {
|
|
7764
|
+
raise: "with the server's skillCatalogMaxSkills / skillCatalogMaxBytes settings",
|
|
7765
|
+
verifyOne: "by verifying this skill alone"
|
|
7766
|
+
};
|
|
7215
7767
|
function reasonOf(err) {
|
|
7216
7768
|
return err instanceof Error ? err.message : String(err);
|
|
7217
7769
|
}
|
|
@@ -7273,7 +7825,7 @@ function responseBytes(result) {
|
|
|
7273
7825
|
}
|
|
7274
7826
|
return total;
|
|
7275
7827
|
}
|
|
7276
|
-
async function verifySkills(client, entries, metadata) {
|
|
7828
|
+
async function verifySkills(client, entries, metadata, budgetHint = DEFAULT_SKILL_BUDGET_HINT) {
|
|
7277
7829
|
const collisions = checkSkillNameCollisions(entries);
|
|
7278
7830
|
const reports = [];
|
|
7279
7831
|
const budget = resolveSkillCatalogBudget(client.getServerSettings?.());
|
|
@@ -7285,7 +7837,7 @@ async function verifySkills(client, entries, metadata) {
|
|
|
7285
7837
|
const files = [];
|
|
7286
7838
|
const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
|
|
7287
7839
|
const manifest = withinBudget ? boundedManifest(declared) : [];
|
|
7288
|
-
let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it
|
|
7840
|
+
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;
|
|
7289
7841
|
let receivedBytes = 0;
|
|
7290
7842
|
const entryIdentity = skillUriIdentity(entry.uri);
|
|
7291
7843
|
let selfAttempted = false;
|
|
@@ -7423,6 +7975,14 @@ function anySkillUnverifiable(reports) {
|
|
|
7423
7975
|
}
|
|
7424
7976
|
|
|
7425
7977
|
// ../../core/cli/handlers/skills-verify.ts
|
|
7978
|
+
var CLI_SKILL_BUDGET_HINT = {
|
|
7979
|
+
raise: "with --skill-catalog-max-skills / --skill-catalog-max-bytes, or the server's skillCatalogMaxSkills / skillCatalogMaxBytes",
|
|
7980
|
+
verifyOne: "with `--method skills/get --uri <uri> --verify`, where <uri> is this report's uri,"
|
|
7981
|
+
};
|
|
7982
|
+
var MCPDO_SKILL_BUDGET_HINT = {
|
|
7983
|
+
raise: "with skillCatalogMaxSkills / skillCatalogMaxBytes in the connection's catalog entry",
|
|
7984
|
+
verifyOne: "with `mcpdo @<conn> skills/get <uri> --verify`, where <uri> is this report's uri,"
|
|
7985
|
+
};
|
|
7426
7986
|
function skillVerificationExitCode(reports, requireDigests) {
|
|
7427
7987
|
if (allSkillsVerified(reports)) return void 0;
|
|
7428
7988
|
if (anySkillFailed(reports)) return EXIT_CODES.SKILL_NONCONFORMANT;
|
|
@@ -7474,7 +8034,8 @@ function assertSkillsSupported(inspectorClient, method) {
|
|
|
7474
8034
|
}
|
|
7475
8035
|
}
|
|
7476
8036
|
var resourceStreamRefs = /* @__PURE__ */ new WeakMap();
|
|
7477
|
-
async function runMethod(inspectorClient, args) {
|
|
8037
|
+
async function runMethod(inspectorClient, args, options = {}) {
|
|
8038
|
+
const skillBudgetHint = options.skillBudgetHint ?? CLI_SKILL_BUDGET_HINT;
|
|
7478
8039
|
let managedToolsState = null;
|
|
7479
8040
|
let managedResourcesState = null;
|
|
7480
8041
|
let managedResourceTemplatesState = null;
|
|
@@ -7539,7 +8100,8 @@ async function runMethod(inspectorClient, args) {
|
|
|
7539
8100
|
if (args.appInfo) {
|
|
7540
8101
|
result = { ...appInfo };
|
|
7541
8102
|
} else {
|
|
7542
|
-
const
|
|
8103
|
+
const useTask = args.task === true || tool.execution?.taskSupport === "required";
|
|
8104
|
+
const invocation = useTask ? await inspectorClient.callToolStream(
|
|
7543
8105
|
tool,
|
|
7544
8106
|
args.toolArg || {},
|
|
7545
8107
|
args.metadata,
|
|
@@ -7770,7 +8332,8 @@ async function runMethod(inspectorClient, args) {
|
|
|
7770
8332
|
const reports = await verifySkills(
|
|
7771
8333
|
inspectorClient,
|
|
7772
8334
|
skills,
|
|
7773
|
-
args.metadata
|
|
8335
|
+
args.metadata,
|
|
8336
|
+
skillBudgetHint
|
|
7774
8337
|
);
|
|
7775
8338
|
return {
|
|
7776
8339
|
kind: "ndjson",
|
|
@@ -7799,7 +8362,8 @@ async function runMethod(inspectorClient, args) {
|
|
|
7799
8362
|
const reports = await verifySkills(
|
|
7800
8363
|
inspectorClient,
|
|
7801
8364
|
[skill],
|
|
7802
|
-
args.metadata
|
|
8365
|
+
args.metadata,
|
|
8366
|
+
skillBudgetHint
|
|
7803
8367
|
);
|
|
7804
8368
|
return {
|
|
7805
8369
|
kind: "ndjson",
|
|
@@ -9636,6 +10200,23 @@ function subscriptionFailureMessage(error) {
|
|
|
9636
10200
|
return error instanceof Error ? error.message : String(error);
|
|
9637
10201
|
}
|
|
9638
10202
|
|
|
10203
|
+
// ../../core/mcp/subscriptionRefusal.ts
|
|
10204
|
+
import { SdkErrorCode as SdkErrorCode3, SdkHttpError } from "@modelcontextprotocol/client";
|
|
10205
|
+
var REFUSAL_STATUSES = /* @__PURE__ */ new Set([400, 403, 404, 405]);
|
|
10206
|
+
function subscriptionListenRefusal(error) {
|
|
10207
|
+
if (!SdkHttpError.isInstance(error) || error.code !== SdkErrorCode3.ClientHttpNotImplemented || !REFUSAL_STATUSES.has(error.status)) {
|
|
10208
|
+
return void 0;
|
|
10209
|
+
}
|
|
10210
|
+
return {
|
|
10211
|
+
status: error.status,
|
|
10212
|
+
...error.statusText ? { statusText: error.statusText } : {}
|
|
10213
|
+
};
|
|
10214
|
+
}
|
|
10215
|
+
function subscriptionListenRefusalMessage(refusal) {
|
|
10216
|
+
const status = refusal.statusText ? `${refusal.status} ${refusal.statusText}` : String(refusal.status);
|
|
10217
|
+
return `The server refused subscriptions/listen (HTTP ${status}). Not retrying \u2014 the server would answer the same way again.`;
|
|
10218
|
+
}
|
|
10219
|
+
|
|
9639
10220
|
// ../../core/mcp/messageTrackingTransport.ts
|
|
9640
10221
|
var MessageTrackingTransport = class {
|
|
9641
10222
|
baseTransport;
|
|
@@ -9865,7 +10446,7 @@ var TaskProgressRouter = class {
|
|
|
9865
10446
|
};
|
|
9866
10447
|
|
|
9867
10448
|
// ../../core/extension/tasks/rawWireChannel.ts
|
|
9868
|
-
import { SdkError as SdkError3, SdkErrorCode as
|
|
10449
|
+
import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
|
|
9869
10450
|
import { DispatchError as DispatchError2 } from "@modelcontextprotocol/ext-tasks/client";
|
|
9870
10451
|
var RAW_WIRE_ID_PREFIX = "inspector-ext-";
|
|
9871
10452
|
var RawWireChannel = class {
|
|
@@ -9938,8 +10519,8 @@ var RawWireChannel = class {
|
|
|
9938
10519
|
`Raw MCP request "${message.method}" timed out after ${timeoutMs} ms`,
|
|
9939
10520
|
false,
|
|
9940
10521
|
{
|
|
9941
|
-
cause: this.host.
|
|
9942
|
-
new SdkError3(
|
|
10522
|
+
cause: this.host.annotateError(
|
|
10523
|
+
new SdkError3(SdkErrorCode4.RequestTimeout, "Request timed out", {
|
|
9943
10524
|
timeout: timeoutMs
|
|
9944
10525
|
}),
|
|
9945
10526
|
message.method
|
|
@@ -9979,7 +10560,7 @@ var RawWireChannel = class {
|
|
|
9979
10560
|
if (!pending) return;
|
|
9980
10561
|
pending.cleanup();
|
|
9981
10562
|
reject(
|
|
9982
|
-
this.host.
|
|
10563
|
+
this.host.annotateError(
|
|
9983
10564
|
error instanceof Error ? error : new Error(String(error)),
|
|
9984
10565
|
message.method
|
|
9985
10566
|
)
|
|
@@ -10712,12 +11293,33 @@ var ToolCallCancelledError = class extends Error {
|
|
|
10712
11293
|
};
|
|
10713
11294
|
|
|
10714
11295
|
// ../../core/mcp/connectionDiagnostics.ts
|
|
10715
|
-
import { SdkError as SdkError4, SdkErrorCode as
|
|
11296
|
+
import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
|
|
10716
11297
|
function isRequestTimeoutData(value) {
|
|
10717
11298
|
return typeof value === "object" && value !== null && typeof value.timeout === "number";
|
|
10718
11299
|
}
|
|
10719
11300
|
function isRequestTimeoutError(err) {
|
|
10720
|
-
return SdkError4.isInstance(err) && err.code ===
|
|
11301
|
+
return SdkError4.isInstance(err) && err.code === SdkErrorCode5.RequestTimeout && isRequestTimeoutData(err.data);
|
|
11302
|
+
}
|
|
11303
|
+
function describeSendFailure(err) {
|
|
11304
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11305
|
+
const status = httpStatusOf(err);
|
|
11306
|
+
if (status === void 0 || message.includes(`HTTP ${status}`)) {
|
|
11307
|
+
return message;
|
|
11308
|
+
}
|
|
11309
|
+
const statusText = httpStatusTextOf(err);
|
|
11310
|
+
return `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ${message}`;
|
|
11311
|
+
}
|
|
11312
|
+
function httpStatusOf(err) {
|
|
11313
|
+
if (typeof err !== "object" || err === null || !("status" in err)) {
|
|
11314
|
+
return void 0;
|
|
11315
|
+
}
|
|
11316
|
+
return typeof err.status === "number" ? err.status : void 0;
|
|
11317
|
+
}
|
|
11318
|
+
function httpStatusTextOf(err) {
|
|
11319
|
+
if (typeof err !== "object" || err === null || !("statusText" in err)) {
|
|
11320
|
+
return void 0;
|
|
11321
|
+
}
|
|
11322
|
+
return typeof err.statusText === "string" ? err.statusText : void 0;
|
|
10721
11323
|
}
|
|
10722
11324
|
function formatDuration(ms) {
|
|
10723
11325
|
const clamped = Math.max(0, ms);
|
|
@@ -10779,11 +11381,108 @@ function annotateRequestTimeout(err, method, diagnostics) {
|
|
|
10779
11381
|
diagnostics
|
|
10780
11382
|
};
|
|
10781
11383
|
const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
|
|
10782
|
-
const annotated = new SdkError4(
|
|
11384
|
+
const annotated = new SdkError4(SdkErrorCode5.RequestTimeout, message, data);
|
|
10783
11385
|
annotated.cause = err;
|
|
10784
11386
|
return annotated;
|
|
10785
11387
|
}
|
|
10786
11388
|
|
|
11389
|
+
// ../../core/mcp/rateLimit.ts
|
|
11390
|
+
import {
|
|
11391
|
+
SdkHttpError as SdkHttpError2
|
|
11392
|
+
} from "@modelcontextprotocol/client";
|
|
11393
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
11394
|
+
function requestIdsOf(body) {
|
|
11395
|
+
if (body === void 0) return [];
|
|
11396
|
+
let parsed;
|
|
11397
|
+
try {
|
|
11398
|
+
parsed = JSON.parse(body);
|
|
11399
|
+
} catch {
|
|
11400
|
+
return [];
|
|
11401
|
+
}
|
|
11402
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
11403
|
+
const ids = [];
|
|
11404
|
+
for (const message of messages) {
|
|
11405
|
+
if (typeof message !== "object" || message === null) continue;
|
|
11406
|
+
if (!("method" in message) || !("id" in message)) continue;
|
|
11407
|
+
const { id } = message;
|
|
11408
|
+
if (typeof id === "string" || typeof id === "number") ids.push(id);
|
|
11409
|
+
}
|
|
11410
|
+
return ids;
|
|
11411
|
+
}
|
|
11412
|
+
var MAX_UNCLAIMED_RATE_LIMITS = 100;
|
|
11413
|
+
var RateLimitLedger = class {
|
|
11414
|
+
// `undefined` is a real value here: a 429 that carried no header.
|
|
11415
|
+
byRequestId = /* @__PURE__ */ new Map();
|
|
11416
|
+
/** File a tracked fetch, if it was a 429. Anything else is ignored. */
|
|
11417
|
+
record(entry) {
|
|
11418
|
+
if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
|
|
11419
|
+
const retryAfter = findHeader(entry.responseHeaders, "retry-after");
|
|
11420
|
+
for (const id of requestIdsOf(entry.requestBody)) {
|
|
11421
|
+
this.byRequestId.delete(id);
|
|
11422
|
+
this.byRequestId.set(id, retryAfter);
|
|
11423
|
+
if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
|
|
11424
|
+
const [oldest] = this.byRequestId.keys();
|
|
11425
|
+
if (oldest !== void 0) this.byRequestId.delete(oldest);
|
|
11426
|
+
}
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
/** The `Retry-After` filed for `id`, removed as it is read. */
|
|
11430
|
+
take(id) {
|
|
11431
|
+
const retryAfter = this.byRequestId.get(id);
|
|
11432
|
+
this.byRequestId.delete(id);
|
|
11433
|
+
return retryAfter;
|
|
11434
|
+
}
|
|
11435
|
+
clear() {
|
|
11436
|
+
this.byRequestId.clear();
|
|
11437
|
+
}
|
|
11438
|
+
};
|
|
11439
|
+
var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
11440
|
+
function parseRetryAfter(value, now) {
|
|
11441
|
+
if (value === void 0) return void 0;
|
|
11442
|
+
const trimmed = value.trim();
|
|
11443
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
11444
|
+
if (!IMF_FIXDATE.test(trimmed)) return void 0;
|
|
11445
|
+
const at = Date.parse(trimmed);
|
|
11446
|
+
if (Number.isNaN(at) || new Date(at).toUTCString() !== trimmed) {
|
|
11447
|
+
return void 0;
|
|
11448
|
+
}
|
|
11449
|
+
return Math.max(0, at - now);
|
|
11450
|
+
}
|
|
11451
|
+
function isRateLimitError(err) {
|
|
11452
|
+
return SdkHttpError2.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
|
|
11453
|
+
}
|
|
11454
|
+
function retryAfterOn(err) {
|
|
11455
|
+
const value = err.data.retryAfter;
|
|
11456
|
+
return typeof value === "string" ? value : void 0;
|
|
11457
|
+
}
|
|
11458
|
+
function attachRetryAfter(err, retryAfter) {
|
|
11459
|
+
if (!isRateLimitError(err) || retryAfter === void 0) return;
|
|
11460
|
+
if (retryAfterOn(err) !== void 0) return;
|
|
11461
|
+
err.data.retryAfter = retryAfter;
|
|
11462
|
+
}
|
|
11463
|
+
function describeRetry(retryAfter, retryAfterMs) {
|
|
11464
|
+
if (retryAfterMs !== void 0) {
|
|
11465
|
+
return `The server asked to retry after ${formatDuration(retryAfterMs)}.`;
|
|
11466
|
+
}
|
|
11467
|
+
if (retryAfter !== void 0) {
|
|
11468
|
+
return `The server sent an unparseable Retry-After (${retryAfter}).`;
|
|
11469
|
+
}
|
|
11470
|
+
return "The server sent no Retry-After header.";
|
|
11471
|
+
}
|
|
11472
|
+
function annotateRateLimit(err, method, now = Date.now()) {
|
|
11473
|
+
if (!isRateLimitError(err)) return err;
|
|
11474
|
+
const header = retryAfterOn(err);
|
|
11475
|
+
const retryAfterMs = parseRetryAfter(header, now);
|
|
11476
|
+
const data = {
|
|
11477
|
+
...err.data,
|
|
11478
|
+
method,
|
|
11479
|
+
...header !== void 0 && { retryAfter: header },
|
|
11480
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
11481
|
+
};
|
|
11482
|
+
const message = `Rate limited by the server (HTTP 429) on ${method}. ${describeRetry(header, retryAfterMs)} ${err.message}`;
|
|
11483
|
+
return new SdkHttpError2(err.code, message, data, { cause: err });
|
|
11484
|
+
}
|
|
11485
|
+
|
|
10787
11486
|
// ../../core/auth/ema/clientConfigError.ts
|
|
10788
11487
|
function emaClientNotConfiguredMessage(reason) {
|
|
10789
11488
|
if (reason === "disabled") {
|
|
@@ -10881,10 +11580,10 @@ async function resolveIdpMetadata(issuer, storage, fetchFn) {
|
|
|
10881
11580
|
}
|
|
10882
11581
|
return discoverIdpMetadata(issuer, fetchFn);
|
|
10883
11582
|
}
|
|
10884
|
-
var
|
|
11583
|
+
var OIDC_WELL_KNOWN3 = "/.well-known/openid-configuration";
|
|
10885
11584
|
async function fetchOpenIdConfiguration(issuer, issuerUrl, fetchFn) {
|
|
10886
11585
|
const url = new URL(
|
|
10887
|
-
`${issuerUrl.pathname.replace(/\/$/, "")}${
|
|
11586
|
+
`${issuerUrl.pathname.replace(/\/$/, "")}${OIDC_WELL_KNOWN3}`,
|
|
10888
11587
|
issuerUrl.origin
|
|
10889
11588
|
);
|
|
10890
11589
|
try {
|
|
@@ -11219,11 +11918,6 @@ async function mintEmaResourceTokens(config, resourceContext) {
|
|
|
11219
11918
|
"EMA requires resource authorization server clientId (per-server oauth.clientId)"
|
|
11220
11919
|
);
|
|
11221
11920
|
}
|
|
11222
|
-
if (!config.resourceClientSecret?.trim()) {
|
|
11223
|
-
throw new Error(
|
|
11224
|
-
"EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
|
|
11225
|
-
);
|
|
11226
|
-
}
|
|
11227
11921
|
const idToken = await getValidIdToken({
|
|
11228
11922
|
idp: config.idp,
|
|
11229
11923
|
storage: config.storage,
|
|
@@ -11489,7 +12183,9 @@ var OAuthManager = class {
|
|
|
11489
12183
|
requestRefreshToken: this.oauthConfig.requestRefreshToken,
|
|
11490
12184
|
// #2068: lets the provider tell an `offline_access` the user asked for
|
|
11491
12185
|
// from one inherited from a previous grant's persisted scope.
|
|
11492
|
-
configuredScope: this.oauthConfig.scope
|
|
12186
|
+
configuredScope: this.oauthConfig.scope,
|
|
12187
|
+
// #2726: in-flight PKCE verifiers keyed by flow, not by server URL.
|
|
12188
|
+
flowStore: this.oauthConfig.flowStore
|
|
11493
12189
|
});
|
|
11494
12190
|
provider.setEventTarget(this.params.getEventTarget());
|
|
11495
12191
|
await provider.prepareForAuth();
|
|
@@ -11653,7 +12349,12 @@ var OAuthManager = class {
|
|
|
11653
12349
|
recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
|
|
11654
12350
|
this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
|
|
11655
12351
|
}
|
|
11656
|
-
|
|
12352
|
+
/**
|
|
12353
|
+
* `state` is the OAuth `state` the callback was redirected back with. It
|
|
12354
|
+
* selects the flow whose PKCE verifier the exchange uses (#2726); without it
|
|
12355
|
+
* a provider with a flow store has no verifier to send.
|
|
12356
|
+
*/
|
|
12357
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
11657
12358
|
try {
|
|
11658
12359
|
if (this.isEnterpriseManaged()) {
|
|
11659
12360
|
const emaConfig = await this.getEmaFlowConfig();
|
|
@@ -11677,6 +12378,9 @@ var OAuthManager = class {
|
|
|
11677
12378
|
}
|
|
11678
12379
|
const provider = await this.createOAuthProvider();
|
|
11679
12380
|
const serverUrl = this.getServerUrl();
|
|
12381
|
+
if (state !== void 0) {
|
|
12382
|
+
provider.resumeAuthorizationFlow(state);
|
|
12383
|
+
}
|
|
11680
12384
|
const result = await mcpAuth(provider, {
|
|
11681
12385
|
serverUrl,
|
|
11682
12386
|
authorizationCode,
|
|
@@ -11688,6 +12392,7 @@ var OAuthManager = class {
|
|
|
11688
12392
|
`Expected AUTHORIZED after providing authorization code, got: ${result}`
|
|
11689
12393
|
);
|
|
11690
12394
|
}
|
|
12395
|
+
await provider.clearAuthorizationFlow();
|
|
11691
12396
|
const tokens = await provider.tokens();
|
|
11692
12397
|
if (!tokens) {
|
|
11693
12398
|
throw new Error("Failed to retrieve tokens after authorization");
|
|
@@ -11819,15 +12524,21 @@ var OAuthManager = class {
|
|
|
11819
12524
|
* Re-read persisted OAuth state and determine whether `challenge` is already
|
|
11820
12525
|
* satisfied without an authorization-server round-trip.
|
|
11821
12526
|
*
|
|
11822
|
-
*
|
|
11823
|
-
*
|
|
11824
|
-
*
|
|
11825
|
-
*
|
|
11826
|
-
*
|
|
11827
|
-
*
|
|
11828
|
-
*
|
|
12527
|
+
* For `token_expired` and `insufficient_scope`, returns `true` only when
|
|
12528
|
+
* storage holds a *provably* unexpired token — a JWT whose `exp` is still in
|
|
12529
|
+
* the future. An opaque token carries no local expiry evidence, so the
|
|
12530
|
+
* resource server's verdict stands and re-authorization proceeds.
|
|
12531
|
+
* `insufficient_scope` additionally requires stored + token scope to cover
|
|
12532
|
+
* the SEP-2350 union: some resource servers answer an *expired* token with
|
|
12533
|
+
* `insufficient_scope`, and a scope-only check would replay that dead token
|
|
12534
|
+
* instead of refreshing it (#2724, the same gap #2051 closed for
|
|
12535
|
+
* `token_expired`). `invalid_token` and `unauthorized` always return
|
|
12536
|
+
* `false` — the resource server explicitly rejected the credential.
|
|
12537
|
+
*
|
|
12538
|
+
* A caller that has just completed a grant passes `scopeOnly` — see
|
|
12539
|
+
* {@link CheckAuthChallengeSatisfiedOptions}.
|
|
11829
12540
|
*/
|
|
11830
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
12541
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
11831
12542
|
const storage = this.oauthConfig.storage;
|
|
11832
12543
|
if (!storage) {
|
|
11833
12544
|
return false;
|
|
@@ -11840,6 +12551,14 @@ var OAuthManager = class {
|
|
|
11840
12551
|
if (challenge.reason !== "insufficient_scope") {
|
|
11841
12552
|
return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
|
|
11842
12553
|
}
|
|
12554
|
+
if (!options?.scopeOnly && !isAccessTokenProvablyUnexpired(tokens)) {
|
|
12555
|
+
return false;
|
|
12556
|
+
}
|
|
12557
|
+
return this.isStepUpScopeCovered(challenge, storage, tokens);
|
|
12558
|
+
}
|
|
12559
|
+
/** Stored + token scope cover the challenge's SEP-2350 union. */
|
|
12560
|
+
async isStepUpScopeCovered(challenge, storage, tokens) {
|
|
12561
|
+
const serverUrl = this.getServerUrl();
|
|
11843
12562
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(
|
|
11844
12563
|
challenge,
|
|
11845
12564
|
tokens.scope
|
|
@@ -11943,14 +12662,14 @@ var OAuthManager = class {
|
|
|
11943
12662
|
}
|
|
11944
12663
|
async handleEnterpriseManagedAuthChallenge(challenge, options) {
|
|
11945
12664
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
|
|
11946
|
-
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
|
|
12665
|
+
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp && !await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
11947
12666
|
return { kind: "step_up_confirm", challenge: enriched };
|
|
11948
12667
|
}
|
|
11949
12668
|
const config = await this.emaFlowConfigForChallenge(enriched);
|
|
11950
12669
|
if (enriched.reason === "insufficient_scope") {
|
|
11951
12670
|
const silent = await trySilentEmaAuth(config);
|
|
11952
12671
|
if (silent.status === "success") {
|
|
11953
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
12672
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
11954
12673
|
const minted = await this.requireStorage().getTokens(
|
|
11955
12674
|
this.getServerUrl()
|
|
11956
12675
|
);
|
|
@@ -12028,9 +12747,16 @@ var OAuthManager = class {
|
|
|
12028
12747
|
fetchFn: this.params.effectiveAuthFetch,
|
|
12029
12748
|
resourceMetadataUrl,
|
|
12030
12749
|
...enriched.reason === "insufficient_scope" && {
|
|
12750
|
+
// Judge coverage the way checkAuthChallengeSatisfied does: a token
|
|
12751
|
+
// response that omitted `scope` granted what was stored, so a
|
|
12752
|
+
// covered challenge on an expired token refreshes rather than
|
|
12753
|
+
// forcing interactive authorization (#2724).
|
|
12031
12754
|
forceReauthorization: isStrictScopeSuperset(
|
|
12032
12755
|
scopeForAuth,
|
|
12033
|
-
|
|
12756
|
+
resolveEffectiveGrantedScope(
|
|
12757
|
+
await this.oauthConfig.storage?.getScope(serverUrl),
|
|
12758
|
+
tokens?.scope
|
|
12759
|
+
)
|
|
12034
12760
|
)
|
|
12035
12761
|
}
|
|
12036
12762
|
});
|
|
@@ -12039,7 +12765,7 @@ var OAuthManager = class {
|
|
|
12039
12765
|
}
|
|
12040
12766
|
if (result === "AUTHORIZED") {
|
|
12041
12767
|
if (enriched.reason === "insufficient_scope") {
|
|
12042
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
12768
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
12043
12769
|
const freshTokens = await provider.tokens();
|
|
12044
12770
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
12045
12771
|
freshTokens?.scope,
|
|
@@ -12141,7 +12867,7 @@ var OAuthManager = class {
|
|
|
12141
12867
|
if (result !== "AUTHORIZED") {
|
|
12142
12868
|
return null;
|
|
12143
12869
|
}
|
|
12144
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
12870
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
12145
12871
|
const freshTokens = await provider.tokens();
|
|
12146
12872
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
12147
12873
|
freshTokens?.scope,
|
|
@@ -12171,7 +12897,11 @@ var OAuthManager = class {
|
|
|
12171
12897
|
};
|
|
12172
12898
|
|
|
12173
12899
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
12174
|
-
import {
|
|
12900
|
+
import {
|
|
12901
|
+
SdkError as SdkError5,
|
|
12902
|
+
SdkErrorCode as SdkErrorCode6,
|
|
12903
|
+
SdkHttpError as SdkHttpError3
|
|
12904
|
+
} from "@modelcontextprotocol/client";
|
|
12175
12905
|
|
|
12176
12906
|
// ../../core/mcp/remote/types.ts
|
|
12177
12907
|
function oauthTokensToRemoteAuthState(tokens) {
|
|
@@ -12226,6 +12956,18 @@ function isConnectAuthChallenge(json) {
|
|
|
12226
12956
|
function isConnectTransportError(json) {
|
|
12227
12957
|
return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
|
|
12228
12958
|
}
|
|
12959
|
+
var SDK_ERROR_CODES = new Set(
|
|
12960
|
+
Object.values(SdkErrorCode6)
|
|
12961
|
+
);
|
|
12962
|
+
function toSdkHttpError(message, http) {
|
|
12963
|
+
const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode6.ClientHttpNotImplemented;
|
|
12964
|
+
return new SdkHttpError3(code, message, {
|
|
12965
|
+
status: http.status,
|
|
12966
|
+
...http.statusText !== void 0 && { statusText: http.statusText },
|
|
12967
|
+
...http.text !== void 0 && { text: http.text },
|
|
12968
|
+
...http.retryAfter !== void 0 && { retryAfter: http.retryAfter }
|
|
12969
|
+
});
|
|
12970
|
+
}
|
|
12229
12971
|
function legacySessionId(json) {
|
|
12230
12972
|
if (typeof json === "object" && json !== null && "sessionId" in json && typeof json.sessionId === "string" && !("ok" in json)) {
|
|
12231
12973
|
return json.sessionId;
|
|
@@ -12657,7 +13399,7 @@ var RemoteClientTransport = class {
|
|
|
12657
13399
|
timer = setTimeout(() => {
|
|
12658
13400
|
this.sseResponseWaits.delete(requestId);
|
|
12659
13401
|
reject(
|
|
12660
|
-
new SdkError5(
|
|
13402
|
+
new SdkError5(SdkErrorCode6.RequestTimeout, "Request timed out", {
|
|
12661
13403
|
timeout: this.sseResponseTimeoutMs
|
|
12662
13404
|
})
|
|
12663
13405
|
);
|
|
@@ -12726,6 +13468,9 @@ var RemoteClientTransport = class {
|
|
|
12726
13468
|
});
|
|
12727
13469
|
return;
|
|
12728
13470
|
}
|
|
13471
|
+
if (json.http) {
|
|
13472
|
+
throw toSdkHttpError(json.error, json.http);
|
|
13473
|
+
}
|
|
12729
13474
|
throw new Error(json.error);
|
|
12730
13475
|
}
|
|
12731
13476
|
async requestSend(message, options) {
|
|
@@ -12798,8 +13543,9 @@ var corePackageJson = {
|
|
|
12798
13543
|
version: "0.0.0"
|
|
12799
13544
|
};
|
|
12800
13545
|
var MAX_URL_ELICITATION_RETRIES = 5;
|
|
12801
|
-
|
|
12802
|
-
|
|
13546
|
+
var WEB_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, you may increase the timeout value in Server Settings.";
|
|
13547
|
+
function connectionTimeoutMessage(timeoutMs, remedy = WEB_CONNECTION_TIMEOUT_REMEDY) {
|
|
13548
|
+
return `Connection timed out after ${timeoutMs} ms. ${remedy}`;
|
|
12803
13549
|
}
|
|
12804
13550
|
function createPendingAbortError() {
|
|
12805
13551
|
return new Error("Pending request aborted");
|
|
@@ -12865,7 +13611,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12865
13611
|
transport: () => this.transport,
|
|
12866
13612
|
defaultTimeoutMs: () => this.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC,
|
|
12867
13613
|
resetTimeoutOnProgress: () => this.resetTimeoutOnProgress,
|
|
12868
|
-
|
|
13614
|
+
annotateError: (error, method) => this.annotateRequestError(error, method)
|
|
12869
13615
|
});
|
|
12870
13616
|
dispatchTaskRequest = (request, options) => this.rawWire.dispatch(request, options);
|
|
12871
13617
|
// Every outbound request still awaiting a response, keyed by JSON-RPC id.
|
|
@@ -12885,11 +13631,16 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12885
13631
|
// `GET` on Streamable HTTP). Both per-session, reset with the maps above.
|
|
12886
13632
|
lastResponse;
|
|
12887
13633
|
notificationStream;
|
|
13634
|
+
// Each tracked `429`'s `Retry-After`, by the JSON-RPC id that drew it, until
|
|
13635
|
+
// that id's failed send claims it (#2442). Per-session, reset with the
|
|
13636
|
+
// diagnostics above.
|
|
13637
|
+
rateLimits = new RateLimitLedger();
|
|
12888
13638
|
/** True when the cached transport was built with an OAuth authProvider attached. */
|
|
12889
13639
|
transportHasAuthProvider = false;
|
|
12890
13640
|
/** Dedupes concurrent ambient auth challenges (reason + scopes). */
|
|
12891
13641
|
ambientAuthChallengeInFlight = /* @__PURE__ */ new Map();
|
|
12892
13642
|
pipeStderr;
|
|
13643
|
+
connectionTimeoutRemedy;
|
|
12893
13644
|
initialLoggingLevel;
|
|
12894
13645
|
// Modern-era per-request log level (#1629). On 2026-07-28 servers
|
|
12895
13646
|
// `logging/setLevel` is gone; the client opts into logs per request via the
|
|
@@ -13028,6 +13779,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13028
13779
|
// site to the two failure handlers. Cleared by any user-initiated refresh (a
|
|
13029
13780
|
// subscribe/unsubscribe is a fresh attempt, and the server may have changed).
|
|
13030
13781
|
modernNeverAcknowledged = false;
|
|
13782
|
+
// Set when the last `listen()` was refused at the HTTP layer with a status a
|
|
13783
|
+
// retry cannot change (#2722) — the sibling of `modernNeverAcknowledged`,
|
|
13784
|
+
// carried and cleared the same way, holding the HTTP answer the UI reports.
|
|
13785
|
+
modernListenRefusal = null;
|
|
13031
13786
|
/** Which in-flight task calls own each progress token (#2316). */
|
|
13032
13787
|
taskProgress = new TaskProgressRouter();
|
|
13033
13788
|
// Abort controller for the in-flight ordinary (non-task) tool call. Aborting
|
|
@@ -13127,6 +13882,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13127
13882
|
this.fetchFn = options.environment.fetch;
|
|
13128
13883
|
this.logger = options.environment.logger ?? silentLogger;
|
|
13129
13884
|
this.pipeStderr = options.pipeStderr ?? false;
|
|
13885
|
+
this.connectionTimeoutRemedy = options.connectionTimeoutRemedy ?? WEB_CONNECTION_TIMEOUT_REMEDY;
|
|
13130
13886
|
this.initialLoggingLevel = options.initialLoggingLevel;
|
|
13131
13887
|
this.sample = options.sample ?? true;
|
|
13132
13888
|
this.elicit = options.elicit ?? true;
|
|
@@ -13284,7 +14040,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13284
14040
|
this.clientInfo,
|
|
13285
14041
|
Object.keys(clientOptions).length > 0 ? clientOptions : void 0
|
|
13286
14042
|
);
|
|
13287
|
-
this.
|
|
14043
|
+
this.annotateSdkRequestErrors(this.client);
|
|
13288
14044
|
if (this.tasksCapabilityAdvertised) {
|
|
13289
14045
|
this.bindReceiverTasks();
|
|
13290
14046
|
}
|
|
@@ -13292,7 +14048,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13292
14048
|
/**
|
|
13293
14049
|
* Decorate the SDK Client's `request` so a per-request timeout rejects with
|
|
13294
14050
|
* the connection's state at that moment instead of a bare `Request timed
|
|
13295
|
-
* out` (#2318)
|
|
14051
|
+
* out` (#2318), and a `429` rejects as rate limiting with the server's
|
|
14052
|
+
* `Retry-After` instead of a bare `Error POSTing to endpoint` (#2442).
|
|
13296
14053
|
*
|
|
13297
14054
|
* `Protocol.request` is the one funnel every SDK verb goes through —
|
|
13298
14055
|
* `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
|
|
@@ -13310,21 +14067,30 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13310
14067
|
* implementation signature serves both at runtime; the single cast on the
|
|
13311
14068
|
* way back is what the overloads cost.
|
|
13312
14069
|
*/
|
|
13313
|
-
|
|
14070
|
+
annotateSdkRequestErrors(client) {
|
|
13314
14071
|
const sdkRequest = client.request.bind(client);
|
|
13315
14072
|
const decorated = async (...args) => {
|
|
13316
14073
|
try {
|
|
13317
14074
|
return await sdkRequest(...args);
|
|
13318
14075
|
} catch (err) {
|
|
13319
|
-
throw
|
|
13320
|
-
err,
|
|
13321
|
-
args[0].method,
|
|
13322
|
-
this.getConnectionDiagnostics()
|
|
13323
|
-
);
|
|
14076
|
+
throw this.annotateRequestError(err, args[0].method);
|
|
13324
14077
|
}
|
|
13325
14078
|
};
|
|
13326
14079
|
client.request = decorated;
|
|
13327
14080
|
}
|
|
14081
|
+
/**
|
|
14082
|
+
* The one place a failed request's error is decorated, shared by the SDK
|
|
14083
|
+
* funnel above and the raw-wire channel that bypasses it (modern-era tool
|
|
14084
|
+
* calls and task operations), so a caller sees the same error whichever
|
|
14085
|
+
* path its request took: a timeout with the connection's state (#2318), a
|
|
14086
|
+
* `429` named as rate limiting (#2442), anything else untouched.
|
|
14087
|
+
*/
|
|
14088
|
+
annotateRequestError(err, method) {
|
|
14089
|
+
return annotateRateLimit(
|
|
14090
|
+
annotateRequestTimeout(err, method, this.getConnectionDiagnostics()),
|
|
14091
|
+
method
|
|
14092
|
+
);
|
|
14093
|
+
}
|
|
13328
14094
|
/**
|
|
13329
14095
|
* @param base - the composed OAuth-path fetch, deadline innermost (#2319).
|
|
13330
14096
|
*
|
|
@@ -13368,9 +14134,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13368
14134
|
},
|
|
13369
14135
|
trackSendFailure: (message, error) => {
|
|
13370
14136
|
if (isRequestTimeoutError(error)) return;
|
|
14137
|
+
attachRetryAfter(error, this.rateLimits.take(message.id));
|
|
13371
14138
|
if (this.outstandingRequests.delete(message.id)) {
|
|
13372
14139
|
this.dispatchConnectionDiagnosticsChange();
|
|
13373
14140
|
}
|
|
14141
|
+
this.dispatchTypedEvent("requestFailed", {
|
|
14142
|
+
id: message.id,
|
|
14143
|
+
reason: describeSendFailure(error)
|
|
14144
|
+
});
|
|
13374
14145
|
},
|
|
13375
14146
|
trackResponse: (message, origin) => {
|
|
13376
14147
|
const responseId = message.id;
|
|
@@ -13436,6 +14207,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13436
14207
|
};
|
|
13437
14208
|
baseTransport.onerror = (error) => {
|
|
13438
14209
|
if (this.status === "connecting") return;
|
|
14210
|
+
if (subscriptionListenRefusal(error) !== void 0) return;
|
|
13439
14211
|
this.status = "error";
|
|
13440
14212
|
this.dispatchTypedEvent("statusChange", this.status);
|
|
13441
14213
|
this.dispatchTypedEvent("error", error);
|
|
@@ -13681,6 +14453,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13681
14453
|
this.clearModernReconnectTimer();
|
|
13682
14454
|
this.modernReconnectAttempts = 0;
|
|
13683
14455
|
this.modernNeverAcknowledged = false;
|
|
14456
|
+
this.modernListenRefusal = null;
|
|
13684
14457
|
this.modernSubscription = null;
|
|
13685
14458
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
13686
14459
|
this.dispatchSubscriptionsChange();
|
|
@@ -13719,6 +14492,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13719
14492
|
this.taskProgress.clear();
|
|
13720
14493
|
this.lastResponse = void 0;
|
|
13721
14494
|
this.notificationStream = void 0;
|
|
14495
|
+
this.rateLimits.clear();
|
|
13722
14496
|
this.dispatchConnectionDiagnosticsChange();
|
|
13723
14497
|
if (this.malformedListItems.size > 0) {
|
|
13724
14498
|
this.malformedListItems.clear();
|
|
@@ -13821,6 +14595,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13821
14595
|
onFetchRequest: (entry) => {
|
|
13822
14596
|
this.dispatchFetchRequest({ ...entry, category: "transport" });
|
|
13823
14597
|
this.noteTransportStream(entry);
|
|
14598
|
+
this.rateLimits.record(entry);
|
|
13824
14599
|
},
|
|
13825
14600
|
onFetchResponseBody: (id, body) => {
|
|
13826
14601
|
this.dispatchFetchRequestBodyUpdate(id, body);
|
|
@@ -13868,9 +14643,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13868
14643
|
// for that upstream gap and should be deleted with it.
|
|
13869
14644
|
//
|
|
13870
14645
|
// Known, accepted side effect of turning intercept on with no stored
|
|
13871
|
-
// tokens: `parseAuthChallengeFromResponse` treats 403
|
|
13872
|
-
// too, so a probe answered
|
|
13873
|
-
//
|
|
14646
|
+
// tokens: `parseAuthChallengeFromResponse` treats a 403 carrying
|
|
14647
|
+
// `WWW-Authenticate` as a challenge too, so a probe answered that way
|
|
14648
|
+
// for a *non-auth* reason (a gateway rejecting the unknown
|
|
14649
|
+
// `server/discover` method, say) now starts OAuth
|
|
13874
14650
|
// discovery instead of letting "auto" fall back to the legacy
|
|
13875
14651
|
// `initialize`. The outcome is a surfaced `oauthError`, not a hang, and
|
|
13876
14652
|
// it goes away with this clause.
|
|
@@ -13937,7 +14713,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13937
14713
|
const timeoutPromise = new Promise((_, reject) => {
|
|
13938
14714
|
timer = setTimeout(() => {
|
|
13939
14715
|
connectTimedOut = true;
|
|
13940
|
-
reject(
|
|
14716
|
+
reject(
|
|
14717
|
+
new Error(
|
|
14718
|
+
connectionTimeoutMessage(
|
|
14719
|
+
connectTimeoutMs,
|
|
14720
|
+
this.connectionTimeoutRemedy
|
|
14721
|
+
)
|
|
14722
|
+
)
|
|
14723
|
+
);
|
|
13941
14724
|
}, connectTimeoutMs);
|
|
13942
14725
|
});
|
|
13943
14726
|
try {
|
|
@@ -16152,11 +16935,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16152
16935
|
// for page one.
|
|
16153
16936
|
...cursor !== void 0 ? { cursor } : {}
|
|
16154
16937
|
};
|
|
16938
|
+
const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
|
|
16155
16939
|
try {
|
|
16156
16940
|
return await this.invokeMcpClient(
|
|
16157
16941
|
() => this.client.request(
|
|
16158
16942
|
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
|
|
16159
|
-
|
|
16943
|
+
resultSchema,
|
|
16160
16944
|
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
16161
16945
|
),
|
|
16162
16946
|
{ method: RESOURCES_DIRECTORY_READ_METHOD }
|
|
@@ -16611,6 +17395,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16611
17395
|
this.clearModernReconnectTimer();
|
|
16612
17396
|
this.modernReconnectAttempts = 0;
|
|
16613
17397
|
this.modernNeverAcknowledged = false;
|
|
17398
|
+
this.modernListenRefusal = null;
|
|
16614
17399
|
}
|
|
16615
17400
|
const generation = ++this.modernListenGeneration;
|
|
16616
17401
|
const previous = this.modernSubscription;
|
|
@@ -16629,8 +17414,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16629
17414
|
this.getRequestOptions()
|
|
16630
17415
|
);
|
|
16631
17416
|
} catch (error) {
|
|
16632
|
-
if (generation === this.modernListenGeneration
|
|
16633
|
-
|
|
17417
|
+
if (generation === this.modernListenGeneration) {
|
|
17418
|
+
if (isNeverAcknowledgedSubscriptionClose(error)) {
|
|
17419
|
+
this.modernNeverAcknowledged = true;
|
|
17420
|
+
}
|
|
17421
|
+
this.modernListenRefusal = subscriptionListenRefusal(error) ?? null;
|
|
16634
17422
|
}
|
|
16635
17423
|
throw error;
|
|
16636
17424
|
}
|
|
@@ -16711,6 +17499,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16711
17499
|
* its error either way — the retry is about the subscriptions, not the call.
|
|
16712
17500
|
*/
|
|
16713
17501
|
reconcileModernStreamStateAfterFailedRefresh() {
|
|
17502
|
+
if (this.modernListenRefusal) {
|
|
17503
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
17504
|
+
return;
|
|
17505
|
+
}
|
|
16714
17506
|
if (!this.wantsModernStream()) {
|
|
16715
17507
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
16716
17508
|
return;
|
|
@@ -16743,6 +17535,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16743
17535
|
honoredUris: []
|
|
16744
17536
|
});
|
|
16745
17537
|
}
|
|
17538
|
+
/**
|
|
17539
|
+
* Settle the stream on an HTTP refusal of the listen (#2722): the
|
|
17540
|
+
* `"refused"` status carrying the HTTP answer, no reconnect, and a log line
|
|
17541
|
+
* with the sentence the UI shows. Reported whatever `active` says — see
|
|
17542
|
+
* `ResourceSubscriptionStreamState`.
|
|
17543
|
+
*/
|
|
17544
|
+
endModernStreamRefused(refusal) {
|
|
17545
|
+
this.clearModernReconnectTimer();
|
|
17546
|
+
const message = subscriptionListenRefusalMessage(refusal);
|
|
17547
|
+
this.logger.warn({ message }, "subscriptions/listen refused over HTTP");
|
|
17548
|
+
this.setModernStreamState({
|
|
17549
|
+
active: this.modernStreamActive(),
|
|
17550
|
+
status: "refused",
|
|
17551
|
+
honoredUris: [],
|
|
17552
|
+
refusal
|
|
17553
|
+
});
|
|
17554
|
+
}
|
|
16746
17555
|
/**
|
|
16747
17556
|
* Schedule a reconnect re-listen after the current backoff delay (#1630).
|
|
16748
17557
|
* `modernReconnectAttempts` reflects the number of *consecutive failed*
|
|
@@ -16780,6 +17589,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16780
17589
|
this.endModernStreamNeverAcknowledged();
|
|
16781
17590
|
return;
|
|
16782
17591
|
}
|
|
17592
|
+
if (this.modernListenRefusal && !isTerminalStatus(this.status)) {
|
|
17593
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
17594
|
+
return;
|
|
17595
|
+
}
|
|
16783
17596
|
this.modernReconnectAttempts += 1;
|
|
16784
17597
|
if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
|
|
16785
17598
|
this.setModernStreamState({
|
|
@@ -16934,8 +17747,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16934
17747
|
* Re-read OAuth storage and test whether a challenge is already satisfied.
|
|
16935
17748
|
* See {@link OAuthManager.checkAuthChallengeSatisfied}.
|
|
16936
17749
|
*/
|
|
16937
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
16938
|
-
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
17750
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
17751
|
+
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
17752
|
+
challenge,
|
|
17753
|
+
options
|
|
17754
|
+
);
|
|
16939
17755
|
}
|
|
16940
17756
|
/**
|
|
16941
17757
|
* Push recovered OAuth auth state to the remote backend (same MCP session).
|
|
@@ -17099,8 +17915,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17099
17915
|
* Direct transports reconnect after token exchange so the live MCP session
|
|
17100
17916
|
* picks up the new Bearer token (mirrors silent recovery reconnect).
|
|
17101
17917
|
*/
|
|
17102
|
-
async completeOAuthFlow(authorizationCode, iss) {
|
|
17103
|
-
await this.ensureOAuthManager().completeOAuthFlow(
|
|
17918
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
17919
|
+
await this.ensureOAuthManager().completeOAuthFlow(
|
|
17920
|
+
authorizationCode,
|
|
17921
|
+
iss,
|
|
17922
|
+
state
|
|
17923
|
+
);
|
|
17104
17924
|
if (this.usesDirectAuthRecovery()) {
|
|
17105
17925
|
await this.reconnectAfterAuthRecovery();
|
|
17106
17926
|
}
|
|
@@ -17124,7 +17944,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17124
17944
|
* Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
|
|
17125
17945
|
*/
|
|
17126
17946
|
async resumeAfterOAuth(authorizationCode, options) {
|
|
17127
|
-
await this.completeOAuthFlow(
|
|
17947
|
+
await this.completeOAuthFlow(
|
|
17948
|
+
authorizationCode,
|
|
17949
|
+
options?.iss,
|
|
17950
|
+
options?.state
|
|
17951
|
+
);
|
|
17128
17952
|
const remoteSessionId = options?.remoteSessionId;
|
|
17129
17953
|
const transport = this.baseTransport;
|
|
17130
17954
|
if (remoteSessionId && transport instanceof RemoteClientTransport) {
|
|
@@ -17287,16 +18111,30 @@ function buildConfigFromOptions(options) {
|
|
|
17287
18111
|
const target = options.target ?? [];
|
|
17288
18112
|
const first = target[0];
|
|
17289
18113
|
const rest = target.slice(1);
|
|
18114
|
+
if (first && options.serverUrl) {
|
|
18115
|
+
throw new Error(
|
|
18116
|
+
`Cannot combine a positional target (${first}) with --server-url (${options.serverUrl}). Pass one or the other.`
|
|
18117
|
+
);
|
|
18118
|
+
}
|
|
17290
18119
|
const urlFromTarget = first && (first.startsWith("http://") || first.startsWith("https://")) ? first : null;
|
|
17291
18120
|
const url = urlFromTarget ?? options.serverUrl ?? null;
|
|
17292
18121
|
if (url) {
|
|
17293
|
-
if (rest.length > 0
|
|
18122
|
+
if (rest.length > 0) {
|
|
17294
18123
|
throw new Error("Arguments cannot be passed to a URL-based MCP server.");
|
|
17295
18124
|
}
|
|
18125
|
+
if (options.transport === "stdio") {
|
|
18126
|
+
throw new Error(
|
|
18127
|
+
`stdio transport cannot be used with a URL (${url}). Use --transport http or --transport sse, or pass a local command instead.`
|
|
18128
|
+
);
|
|
18129
|
+
}
|
|
17296
18130
|
let transportType;
|
|
17297
18131
|
const t = options.transport === "http" ? "streamable-http" : options.transport;
|
|
17298
18132
|
if (t === "sse" || t === "streamable-http") {
|
|
17299
18133
|
transportType = t;
|
|
18134
|
+
} else if (t) {
|
|
18135
|
+
throw new Error(
|
|
18136
|
+
`Invalid transport type: ${t}. Valid types are: sse, http, stdio`
|
|
18137
|
+
);
|
|
17300
18138
|
} else {
|
|
17301
18139
|
const u = new URL(url);
|
|
17302
18140
|
if (u.pathname.endsWith("/mcp")) {
|
|
@@ -18665,7 +19503,11 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18665
19503
|
path: options.callbackListen.pathname,
|
|
18666
19504
|
onCallback: async (params) => {
|
|
18667
19505
|
try {
|
|
18668
|
-
await options.client.completeOAuthFlow(
|
|
19506
|
+
await options.client.completeOAuthFlow(
|
|
19507
|
+
params.code,
|
|
19508
|
+
params.iss,
|
|
19509
|
+
params.state
|
|
19510
|
+
);
|
|
18669
19511
|
flowResolve();
|
|
18670
19512
|
} catch (err) {
|
|
18671
19513
|
flowReject(toRunnerOAuthError(err));
|
|
@@ -18717,7 +19559,10 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18717
19559
|
}
|
|
18718
19560
|
if (options.authChallenge) {
|
|
18719
19561
|
const satisfied = await racingSignals(
|
|
18720
|
-
|
|
19562
|
+
// The token exchange just completed, so only scope is in question.
|
|
19563
|
+
options.client.checkAuthChallengeSatisfied(options.authChallenge, {
|
|
19564
|
+
scopeOnly: true
|
|
19565
|
+
})
|
|
18721
19566
|
);
|
|
18722
19567
|
if (!satisfied) {
|
|
18723
19568
|
return {
|
|
@@ -19078,6 +19923,26 @@ function readInspectorVersion(callerUrl) {
|
|
|
19078
19923
|
);
|
|
19079
19924
|
}
|
|
19080
19925
|
|
|
19926
|
+
// ../../core/cli/handlers/connect-timeout.ts
|
|
19927
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
19928
|
+
var CLI_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, pass a larger --connect-timeout <ms>, or set connectionTimeout on the server's catalog or config entry.";
|
|
19929
|
+
function withConnectTimeout(settings, connectionTimeout) {
|
|
19930
|
+
if (connectionTimeout === void 0) return settings;
|
|
19931
|
+
if (settings) return { ...settings, connectionTimeout };
|
|
19932
|
+
return {
|
|
19933
|
+
headers: [],
|
|
19934
|
+
metadata: {},
|
|
19935
|
+
env: [],
|
|
19936
|
+
connectionTimeout,
|
|
19937
|
+
requestTimeout: 0,
|
|
19938
|
+
taskTtl: DEFAULT_TASK_TTL_MS,
|
|
19939
|
+
maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS,
|
|
19940
|
+
autoRefreshOnListChanged: false,
|
|
19941
|
+
paginatedLists: false,
|
|
19942
|
+
roots: []
|
|
19943
|
+
};
|
|
19944
|
+
}
|
|
19945
|
+
|
|
19081
19946
|
// src/connection/auth-helper.ts
|
|
19082
19947
|
import { spawn } from "child_process";
|
|
19083
19948
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -19087,14 +19952,25 @@ import * as path6 from "path";
|
|
|
19087
19952
|
// ../../core/node/openUrl.ts
|
|
19088
19953
|
import open from "open";
|
|
19089
19954
|
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19955
|
+
var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
19956
|
+
function toOpenableHref(href) {
|
|
19957
|
+
let parsed;
|
|
19958
|
+
try {
|
|
19959
|
+
parsed = new URL(href);
|
|
19960
|
+
} catch {
|
|
19961
|
+
throw new Error("refusing to open an invalid URL");
|
|
19962
|
+
}
|
|
19963
|
+
if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
|
|
19964
|
+
throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
|
|
19965
|
+
}
|
|
19966
|
+
return parsed.href;
|
|
19967
|
+
}
|
|
19968
|
+
function absorbLateErrors(child) {
|
|
19969
|
+
child.on("error", () => {
|
|
19094
19970
|
});
|
|
19095
19971
|
}
|
|
19096
19972
|
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
19097
|
-
const href = typeof url === "string" ? url : url.href;
|
|
19973
|
+
const href = toOpenableHref(typeof url === "string" ? url : url.href);
|
|
19098
19974
|
let timer;
|
|
19099
19975
|
const timeout = new Promise((_, reject) => {
|
|
19100
19976
|
timer = setTimeout(
|
|
@@ -19103,8 +19979,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
|
19103
19979
|
);
|
|
19104
19980
|
});
|
|
19105
19981
|
try {
|
|
19106
|
-
|
|
19107
|
-
await Promise.race([opened, timeout]);
|
|
19982
|
+
await Promise.race([open(href).then(absorbLateErrors), timeout]);
|
|
19108
19983
|
} finally {
|
|
19109
19984
|
clearTimeout(timer);
|
|
19110
19985
|
}
|
|
@@ -19471,13 +20346,15 @@ async function runEmaIdpInteractiveFlow(active, storage, navigation) {
|
|
|
19471
20346
|
);
|
|
19472
20347
|
const redirectUrlProvider = new MutableRedirectUrlProvider();
|
|
19473
20348
|
redirectUrlProvider.redirectUrl = formatRunnerOAuthRedirectUrl(callbackUrlConfig);
|
|
20349
|
+
const fetchFn = createProxyFetch();
|
|
19474
20350
|
await runRunnerInteractiveOAuth({
|
|
19475
20351
|
client: {
|
|
19476
20352
|
authenticate: async () => {
|
|
19477
20353
|
const { authorizationUrl } = await startIdpOidcAuthorization({
|
|
19478
20354
|
idp: active,
|
|
19479
20355
|
redirectUrl: redirectUrlProvider.redirectUrl,
|
|
19480
|
-
storage
|
|
20356
|
+
storage,
|
|
20357
|
+
fetchFn
|
|
19481
20358
|
});
|
|
19482
20359
|
navigation.navigateToAuthorization(authorizationUrl);
|
|
19483
20360
|
return authorizationUrl;
|
|
@@ -19491,7 +20368,8 @@ async function runEmaIdpInteractiveFlow(active, storage, navigation) {
|
|
|
19491
20368
|
authorizationCode,
|
|
19492
20369
|
iss,
|
|
19493
20370
|
redirectUrl: redirectUrlProvider.redirectUrl,
|
|
19494
|
-
storage
|
|
20371
|
+
storage,
|
|
20372
|
+
fetchFn
|
|
19495
20373
|
});
|
|
19496
20374
|
},
|
|
19497
20375
|
/* v8 ignore next 2 -- only reached when options.authChallenge is set, which this flow never does */
|
|
@@ -19511,7 +20389,13 @@ async function authorizeInFrontend(serverConfig, serverSettings, options) {
|
|
|
19511
20389
|
return;
|
|
19512
20390
|
}
|
|
19513
20391
|
const environment = {
|
|
19514
|
-
transport: createTransportNode
|
|
20392
|
+
transport: createTransportNode,
|
|
20393
|
+
// HTTPS_PROXY / HTTP_PROXY / NO_PROXY, as in the one-shot CLI (#2642):
|
|
20394
|
+
// `InspectorClient` always hands the transport a defined fetch, so the
|
|
20395
|
+
// transport's own proxy fallback never fires — the proxy has to sit here,
|
|
20396
|
+
// at the bottom of the stack, for OAuth discovery and token requests to
|
|
20397
|
+
// reach it. Undefined when no proxy variable is set.
|
|
20398
|
+
fetch: createProxyFetch()
|
|
19515
20399
|
};
|
|
19516
20400
|
const redirectUrlProvider = new MutableRedirectUrlProvider();
|
|
19517
20401
|
const callbackUrlConfig = parseRunnerOAuthCallbackUrl(
|
|
@@ -19545,6 +20429,8 @@ async function authorizeInFrontend(serverConfig, serverSettings, options) {
|
|
|
19545
20429
|
name: "inspector-cli",
|
|
19546
20430
|
version: readInspectorVersion(import.meta.url)
|
|
19547
20431
|
},
|
|
20432
|
+
// Same knobs as the daemon's connect: --connect-timeout or mcp.json (#2615).
|
|
20433
|
+
connectionTimeoutRemedy: CLI_CONNECTION_TIMEOUT_REMEDY,
|
|
19548
20434
|
initialLoggingLevel: "debug",
|
|
19549
20435
|
progress: false,
|
|
19550
20436
|
sample: false,
|
|
@@ -19754,9 +20640,15 @@ function tryReserveAuthFlow(lockPath) {
|
|
|
19754
20640
|
if (!isStale(fs8.statSync(lockPath).mtimeMs)) return false;
|
|
19755
20641
|
const claimPath = `${lockPath}.claim-${process.pid}`;
|
|
19756
20642
|
fs8.renameSync(lockPath, claimPath);
|
|
19757
|
-
|
|
20643
|
+
if (!isStale(fs8.statSync(claimPath).mtimeMs)) {
|
|
20644
|
+
try {
|
|
20645
|
+
fs8.linkSync(claimPath, lockPath);
|
|
20646
|
+
} finally {
|
|
20647
|
+
fs8.rmSync(claimPath, { force: true });
|
|
20648
|
+
}
|
|
20649
|
+
return false;
|
|
20650
|
+
}
|
|
19758
20651
|
fs8.rmSync(claimPath, { force: true });
|
|
19759
|
-
if (claimedFresh) return false;
|
|
19760
20652
|
create();
|
|
19761
20653
|
return true;
|
|
19762
20654
|
} catch {
|
|
@@ -20411,7 +21303,13 @@ async function getLiveConnectionAuthInfo(connection) {
|
|
|
20411
21303
|
}
|
|
20412
21304
|
async function createConnectionClient(serverConfig, serverSettings) {
|
|
20413
21305
|
const environment = {
|
|
20414
|
-
transport: createTransportNode
|
|
21306
|
+
transport: createTransportNode,
|
|
21307
|
+
// HTTPS_PROXY / HTTP_PROXY / NO_PROXY, as in the one-shot CLI (#2642).
|
|
21308
|
+
// `InspectorClient` builds its fetch chain on `environment.fetch ?? fetch`
|
|
21309
|
+
// and always hands the transport the result, so the transport's own
|
|
21310
|
+
// `createProxyFetch()` fallback is never reached — leave this out and the
|
|
21311
|
+
// daemon connects direct. Undefined when no proxy variable is set.
|
|
21312
|
+
fetch: createProxyFetch()
|
|
20415
21313
|
};
|
|
20416
21314
|
const redirectUrlProvider = new MutableRedirectUrlProvider();
|
|
20417
21315
|
if (isOAuthCapableServerConfig(serverConfig)) {
|
|
@@ -20437,6 +21335,9 @@ async function createConnectionClient(serverConfig, serverSettings) {
|
|
|
20437
21335
|
name: CONNECTION_CLIENT_NAME,
|
|
20438
21336
|
version: readInspectorVersion(import.meta.url)
|
|
20439
21337
|
},
|
|
21338
|
+
// `mcpdo connect` takes the same --connect-timeout as the one-shot CLI, so
|
|
21339
|
+
// a timeout names that rather than the web's Server Settings (#2615).
|
|
21340
|
+
connectionTimeoutRemedy: CLI_CONNECTION_TIMEOUT_REMEDY,
|
|
20440
21341
|
initialLoggingLevel: "debug",
|
|
20441
21342
|
progress: false,
|
|
20442
21343
|
sample: false,
|
|
@@ -20493,6 +21394,9 @@ function connectCancelledError() {
|
|
|
20493
21394
|
}
|
|
20494
21395
|
|
|
20495
21396
|
// src/daemon/server.ts
|
|
21397
|
+
var MCPDO_RUN_OPTIONS = {
|
|
21398
|
+
skillBudgetHint: MCPDO_SKILL_BUDGET_HINT
|
|
21399
|
+
};
|
|
20496
21400
|
var autoCancelElicitationChannel = {
|
|
20497
21401
|
request(frame) {
|
|
20498
21402
|
return Promise.resolve({
|
|
@@ -20924,7 +21828,7 @@ var DaemonServer = class _DaemonServer {
|
|
|
20924
21828
|
const clearAmbientSignal = client.setAmbientRequestSignal(signal);
|
|
20925
21829
|
let outcome;
|
|
20926
21830
|
try {
|
|
20927
|
-
outcome = await runMethod(client, methodArgs);
|
|
21831
|
+
outcome = await runMethod(client, methodArgs, MCPDO_RUN_OPTIONS);
|
|
20928
21832
|
} finally {
|
|
20929
21833
|
clearAmbientSignal();
|
|
20930
21834
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -20968,7 +21872,10 @@ var DaemonServer = class _DaemonServer {
|
|
|
20968
21872
|
const unwire = wireElicitationBridge(client, channel, requestId);
|
|
20969
21873
|
const outcome = (async () => {
|
|
20970
21874
|
try {
|
|
20971
|
-
return toRpcResult(
|
|
21875
|
+
return toRpcResult(
|
|
21876
|
+
await runMethod(client, methodArgs, MCPDO_RUN_OPTIONS),
|
|
21877
|
+
params.method
|
|
21878
|
+
);
|
|
20972
21879
|
} finally {
|
|
20973
21880
|
unwire();
|
|
20974
21881
|
}
|
|
@@ -21087,7 +21994,7 @@ var DaemonServer = class _DaemonServer {
|
|
|
21087
21994
|
params.interactive
|
|
21088
21995
|
);
|
|
21089
21996
|
const methodArgs = stripConnectionFields(params);
|
|
21090
|
-
const outcome = await runMethod(client, methodArgs);
|
|
21997
|
+
const outcome = await runMethod(client, methodArgs, MCPDO_RUN_OPTIONS);
|
|
21091
21998
|
if (outcome.kind !== "stream") {
|
|
21092
21999
|
throw new CliExitCodeError(
|
|
21093
22000
|
EXIT_CODES.USAGE,
|
|
@@ -21336,6 +22243,8 @@ export {
|
|
|
21336
22243
|
resetNodeOAuthStorageCache,
|
|
21337
22244
|
clearAllOAuthClientState,
|
|
21338
22245
|
NodeOAuthStorage,
|
|
22246
|
+
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
22247
|
+
withConnectTimeout,
|
|
21339
22248
|
PLAIN,
|
|
21340
22249
|
styleFromOpts,
|
|
21341
22250
|
isCliAutoOpenForced,
|