@modelcontextprotocol/inspector 2.11.0 → 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 +1149 -190
- package/clients/mcpdo/build/{chunk-O6RN6WMD.js → chunk-LBQDT5VV.js} +726 -101
- package/clients/mcpdo/build/mcp-bin.js +14 -1
- package/clients/mcpdo/build/mcpdod.js +1 -1
- package/clients/tui/build/index.js +734 -106
- package/clients/web/build/index.js +738 -89
- package/clients/web/dist/assets/{index-B79AqubU.js → index-qtX15-Bz.js} +111 -111
- package/clients/web/dist/assets/{prism-light-J_CKjs7N.js → prism-light-DfUe_Xlh.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +2 -2
|
@@ -177,6 +177,12 @@ var DirectoryReadResultSchema = z.looseObject({
|
|
|
177
177
|
resources: z.array(DirectoryChildSchema),
|
|
178
178
|
nextCursor: z.string().optional()
|
|
179
179
|
});
|
|
180
|
+
var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
|
|
181
|
+
{
|
|
182
|
+
ttlMs: z.int().min(0),
|
|
183
|
+
cacheScope: z.enum(["public", "private"])
|
|
184
|
+
}
|
|
185
|
+
);
|
|
180
186
|
|
|
181
187
|
// ../../core/mcp/sha256.ts
|
|
182
188
|
var K = new Uint32Array([
|
|
@@ -5576,6 +5582,23 @@ function subscriptionFailureMessage(error) {
|
|
|
5576
5582
|
return error instanceof Error ? error.message : String(error);
|
|
5577
5583
|
}
|
|
5578
5584
|
|
|
5585
|
+
// ../../core/mcp/subscriptionRefusal.ts
|
|
5586
|
+
import { SdkErrorCode as SdkErrorCode2, SdkHttpError } from "@modelcontextprotocol/client";
|
|
5587
|
+
var REFUSAL_STATUSES = /* @__PURE__ */ new Set([400, 403, 404, 405]);
|
|
5588
|
+
function subscriptionListenRefusal(error) {
|
|
5589
|
+
if (!SdkHttpError.isInstance(error) || error.code !== SdkErrorCode2.ClientHttpNotImplemented || !REFUSAL_STATUSES.has(error.status)) {
|
|
5590
|
+
return void 0;
|
|
5591
|
+
}
|
|
5592
|
+
return {
|
|
5593
|
+
status: error.status,
|
|
5594
|
+
...error.statusText ? { statusText: error.statusText } : {}
|
|
5595
|
+
};
|
|
5596
|
+
}
|
|
5597
|
+
function subscriptionListenRefusalMessage(refusal) {
|
|
5598
|
+
const status = refusal.statusText ? `${refusal.status} ${refusal.statusText}` : String(refusal.status);
|
|
5599
|
+
return `The server refused subscriptions/listen (HTTP ${status}). Not retrying \u2014 the server would answer the same way again.`;
|
|
5600
|
+
}
|
|
5601
|
+
|
|
5579
5602
|
// ../../core/mcp/messageTrackingTransport.ts
|
|
5580
5603
|
var MessageTrackingTransport = class {
|
|
5581
5604
|
baseTransport;
|
|
@@ -5805,7 +5828,7 @@ var TaskProgressRouter = class {
|
|
|
5805
5828
|
};
|
|
5806
5829
|
|
|
5807
5830
|
// ../../core/extension/tasks/rawWireChannel.ts
|
|
5808
|
-
import { SdkError as SdkError2, SdkErrorCode as
|
|
5831
|
+
import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode3 } from "@modelcontextprotocol/client";
|
|
5809
5832
|
import { DispatchError as DispatchError2 } from "@modelcontextprotocol/ext-tasks/client";
|
|
5810
5833
|
var RAW_WIRE_ID_PREFIX = "inspector-ext-";
|
|
5811
5834
|
var RawWireChannel = class {
|
|
@@ -5878,8 +5901,8 @@ var RawWireChannel = class {
|
|
|
5878
5901
|
`Raw MCP request "${message.method}" timed out after ${timeoutMs} ms`,
|
|
5879
5902
|
false,
|
|
5880
5903
|
{
|
|
5881
|
-
cause: this.host.
|
|
5882
|
-
new SdkError2(
|
|
5904
|
+
cause: this.host.annotateError(
|
|
5905
|
+
new SdkError2(SdkErrorCode3.RequestTimeout, "Request timed out", {
|
|
5883
5906
|
timeout: timeoutMs
|
|
5884
5907
|
}),
|
|
5885
5908
|
message.method
|
|
@@ -5919,7 +5942,7 @@ var RawWireChannel = class {
|
|
|
5919
5942
|
if (!pending) return;
|
|
5920
5943
|
pending.cleanup();
|
|
5921
5944
|
reject(
|
|
5922
|
-
this.host.
|
|
5945
|
+
this.host.annotateError(
|
|
5923
5946
|
error instanceof Error ? error : new Error(String(error)),
|
|
5924
5947
|
message.method
|
|
5925
5948
|
)
|
|
@@ -6219,7 +6242,7 @@ function validateToolOutput(provider, tool, result) {
|
|
|
6219
6242
|
|
|
6220
6243
|
// ../../core/mcp/listSalvage.ts
|
|
6221
6244
|
import { z as z4 } from "zod/v4";
|
|
6222
|
-
import { SdkError as SdkError3, SdkErrorCode as
|
|
6245
|
+
import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
|
|
6223
6246
|
import { ToolSchema } from "@modelcontextprotocol/core";
|
|
6224
6247
|
var LIST_MAX_PAGES = 64;
|
|
6225
6248
|
function listPaginationExceeded(method) {
|
|
@@ -6228,10 +6251,10 @@ function listPaginationExceeded(method) {
|
|
|
6228
6251
|
);
|
|
6229
6252
|
}
|
|
6230
6253
|
function isClientDecodeRejection(err) {
|
|
6231
|
-
return SdkError3.isInstance(err) && (err.code ===
|
|
6254
|
+
return SdkError3.isInstance(err) && (err.code === SdkErrorCode4.InvalidResult || err.code === SdkErrorCode4.UnsupportedResultType);
|
|
6232
6255
|
}
|
|
6233
6256
|
function isSalvageableRejection(err) {
|
|
6234
|
-
return SdkError3.isInstance(err) && err.code ===
|
|
6257
|
+
return SdkError3.isInstance(err) && err.code === SdkErrorCode4.InvalidResult;
|
|
6235
6258
|
}
|
|
6236
6259
|
function lenientListPageSchema(resultSchema, itemsKey) {
|
|
6237
6260
|
return resultSchema.extend({ [itemsKey]: z4.array(z4.unknown()) });
|
|
@@ -7409,12 +7432,33 @@ function createSilentLogger() {
|
|
|
7409
7432
|
var silentLogger = createSilentLogger();
|
|
7410
7433
|
|
|
7411
7434
|
// ../../core/mcp/connectionDiagnostics.ts
|
|
7412
|
-
import { SdkError as SdkError4, SdkErrorCode as
|
|
7435
|
+
import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
|
|
7413
7436
|
function isRequestTimeoutData(value) {
|
|
7414
7437
|
return typeof value === "object" && value !== null && typeof value.timeout === "number";
|
|
7415
7438
|
}
|
|
7416
7439
|
function isRequestTimeoutError(err) {
|
|
7417
|
-
return SdkError4.isInstance(err) && err.code ===
|
|
7440
|
+
return SdkError4.isInstance(err) && err.code === SdkErrorCode5.RequestTimeout && isRequestTimeoutData(err.data);
|
|
7441
|
+
}
|
|
7442
|
+
function describeSendFailure(err) {
|
|
7443
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7444
|
+
const status = httpStatusOf(err);
|
|
7445
|
+
if (status === void 0 || message.includes(`HTTP ${status}`)) {
|
|
7446
|
+
return message;
|
|
7447
|
+
}
|
|
7448
|
+
const statusText = httpStatusTextOf(err);
|
|
7449
|
+
return `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ${message}`;
|
|
7450
|
+
}
|
|
7451
|
+
function httpStatusOf(err) {
|
|
7452
|
+
if (typeof err !== "object" || err === null || !("status" in err)) {
|
|
7453
|
+
return void 0;
|
|
7454
|
+
}
|
|
7455
|
+
return typeof err.status === "number" ? err.status : void 0;
|
|
7456
|
+
}
|
|
7457
|
+
function httpStatusTextOf(err) {
|
|
7458
|
+
if (typeof err !== "object" || err === null || !("statusText" in err)) {
|
|
7459
|
+
return void 0;
|
|
7460
|
+
}
|
|
7461
|
+
return typeof err.statusText === "string" ? err.statusText : void 0;
|
|
7418
7462
|
}
|
|
7419
7463
|
function formatDuration(ms) {
|
|
7420
7464
|
const clamped = Math.max(0, ms);
|
|
@@ -7476,11 +7520,108 @@ function annotateRequestTimeout(err, method, diagnostics) {
|
|
|
7476
7520
|
diagnostics
|
|
7477
7521
|
};
|
|
7478
7522
|
const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
|
|
7479
|
-
const annotated = new SdkError4(
|
|
7523
|
+
const annotated = new SdkError4(SdkErrorCode5.RequestTimeout, message, data);
|
|
7480
7524
|
annotated.cause = err;
|
|
7481
7525
|
return annotated;
|
|
7482
7526
|
}
|
|
7483
7527
|
|
|
7528
|
+
// ../../core/mcp/rateLimit.ts
|
|
7529
|
+
import {
|
|
7530
|
+
SdkHttpError as SdkHttpError2
|
|
7531
|
+
} from "@modelcontextprotocol/client";
|
|
7532
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
7533
|
+
function requestIdsOf(body) {
|
|
7534
|
+
if (body === void 0) return [];
|
|
7535
|
+
let parsed;
|
|
7536
|
+
try {
|
|
7537
|
+
parsed = JSON.parse(body);
|
|
7538
|
+
} catch {
|
|
7539
|
+
return [];
|
|
7540
|
+
}
|
|
7541
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
7542
|
+
const ids = [];
|
|
7543
|
+
for (const message of messages) {
|
|
7544
|
+
if (typeof message !== "object" || message === null) continue;
|
|
7545
|
+
if (!("method" in message) || !("id" in message)) continue;
|
|
7546
|
+
const { id } = message;
|
|
7547
|
+
if (typeof id === "string" || typeof id === "number") ids.push(id);
|
|
7548
|
+
}
|
|
7549
|
+
return ids;
|
|
7550
|
+
}
|
|
7551
|
+
var MAX_UNCLAIMED_RATE_LIMITS = 100;
|
|
7552
|
+
var RateLimitLedger = class {
|
|
7553
|
+
// `undefined` is a real value here: a 429 that carried no header.
|
|
7554
|
+
byRequestId = /* @__PURE__ */ new Map();
|
|
7555
|
+
/** File a tracked fetch, if it was a 429. Anything else is ignored. */
|
|
7556
|
+
record(entry) {
|
|
7557
|
+
if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
|
|
7558
|
+
const retryAfter = findHeader(entry.responseHeaders, "retry-after");
|
|
7559
|
+
for (const id of requestIdsOf(entry.requestBody)) {
|
|
7560
|
+
this.byRequestId.delete(id);
|
|
7561
|
+
this.byRequestId.set(id, retryAfter);
|
|
7562
|
+
if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
|
|
7563
|
+
const [oldest] = this.byRequestId.keys();
|
|
7564
|
+
if (oldest !== void 0) this.byRequestId.delete(oldest);
|
|
7565
|
+
}
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
/** The `Retry-After` filed for `id`, removed as it is read. */
|
|
7569
|
+
take(id) {
|
|
7570
|
+
const retryAfter = this.byRequestId.get(id);
|
|
7571
|
+
this.byRequestId.delete(id);
|
|
7572
|
+
return retryAfter;
|
|
7573
|
+
}
|
|
7574
|
+
clear() {
|
|
7575
|
+
this.byRequestId.clear();
|
|
7576
|
+
}
|
|
7577
|
+
};
|
|
7578
|
+
var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
7579
|
+
function parseRetryAfter(value, now) {
|
|
7580
|
+
if (value === void 0) return void 0;
|
|
7581
|
+
const trimmed = value.trim();
|
|
7582
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
7583
|
+
if (!IMF_FIXDATE.test(trimmed)) return void 0;
|
|
7584
|
+
const at = Date.parse(trimmed);
|
|
7585
|
+
if (Number.isNaN(at) || new Date(at).toUTCString() !== trimmed) {
|
|
7586
|
+
return void 0;
|
|
7587
|
+
}
|
|
7588
|
+
return Math.max(0, at - now);
|
|
7589
|
+
}
|
|
7590
|
+
function isRateLimitError(err) {
|
|
7591
|
+
return SdkHttpError2.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
|
|
7592
|
+
}
|
|
7593
|
+
function retryAfterOn(err) {
|
|
7594
|
+
const value = err.data.retryAfter;
|
|
7595
|
+
return typeof value === "string" ? value : void 0;
|
|
7596
|
+
}
|
|
7597
|
+
function attachRetryAfter(err, retryAfter) {
|
|
7598
|
+
if (!isRateLimitError(err) || retryAfter === void 0) return;
|
|
7599
|
+
if (retryAfterOn(err) !== void 0) return;
|
|
7600
|
+
err.data.retryAfter = retryAfter;
|
|
7601
|
+
}
|
|
7602
|
+
function describeRetry(retryAfter, retryAfterMs) {
|
|
7603
|
+
if (retryAfterMs !== void 0) {
|
|
7604
|
+
return `The server asked to retry after ${formatDuration(retryAfterMs)}.`;
|
|
7605
|
+
}
|
|
7606
|
+
if (retryAfter !== void 0) {
|
|
7607
|
+
return `The server sent an unparseable Retry-After (${retryAfter}).`;
|
|
7608
|
+
}
|
|
7609
|
+
return "The server sent no Retry-After header.";
|
|
7610
|
+
}
|
|
7611
|
+
function annotateRateLimit(err, method, now = Date.now()) {
|
|
7612
|
+
if (!isRateLimitError(err)) return err;
|
|
7613
|
+
const header = retryAfterOn(err);
|
|
7614
|
+
const retryAfterMs = parseRetryAfter(header, now);
|
|
7615
|
+
const data = {
|
|
7616
|
+
...err.data,
|
|
7617
|
+
method,
|
|
7618
|
+
...header !== void 0 && { retryAfter: header },
|
|
7619
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
7620
|
+
};
|
|
7621
|
+
const message = `Rate limited by the server (HTTP 429) on ${method}. ${describeRetry(header, retryAfterMs)} ${err.message}`;
|
|
7622
|
+
return new SdkHttpError2(err.code, message, data, { cause: err });
|
|
7623
|
+
}
|
|
7624
|
+
|
|
7484
7625
|
// ../../core/auth/authorizationParams.ts
|
|
7485
7626
|
var RESERVED_AUTHORIZATION_PARAMS = [
|
|
7486
7627
|
"client_id",
|
|
@@ -7598,6 +7739,13 @@ var BaseOAuthClientProvider = class {
|
|
|
7598
7739
|
requestRefreshToken;
|
|
7599
7740
|
/** Server-configured scope, for the #2068 `offline_access` distinction. */
|
|
7600
7741
|
configuredScope;
|
|
7742
|
+
/** Per-flow verifier store (#2726); absent → the shared storage slot. */
|
|
7743
|
+
flowStore;
|
|
7744
|
+
/**
|
|
7745
|
+
* The authId of the flow this provider is driving: set by {@link state} on the
|
|
7746
|
+
* redirect leg and by {@link resumeAuthorizationFlow} on the callback leg.
|
|
7747
|
+
*/
|
|
7748
|
+
flowId;
|
|
7601
7749
|
constructor(serverUrl, oauthConfig) {
|
|
7602
7750
|
this.serverUrl = serverUrl;
|
|
7603
7751
|
this.storage = oauthConfig.storage;
|
|
@@ -7607,6 +7755,7 @@ var BaseOAuthClientProvider = class {
|
|
|
7607
7755
|
this.authorizationParams = oauthConfig.authorizationParams;
|
|
7608
7756
|
this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
|
|
7609
7757
|
this.configuredScope = oauthConfig.configuredScope;
|
|
7758
|
+
this.flowStore = oauthConfig.flowStore;
|
|
7610
7759
|
}
|
|
7611
7760
|
/**
|
|
7612
7761
|
* Load persisted scope into {@link cachedScope} before SDK `auth()` (which
|
|
@@ -7677,7 +7826,24 @@ var BaseOAuthClientProvider = class {
|
|
|
7677
7826
|
return metadata;
|
|
7678
7827
|
}
|
|
7679
7828
|
state() {
|
|
7680
|
-
|
|
7829
|
+
const state = generateOAuthState();
|
|
7830
|
+
this.flowId = state;
|
|
7831
|
+
return state;
|
|
7832
|
+
}
|
|
7833
|
+
/**
|
|
7834
|
+
* Bind this provider to the flow a callback's `state` names, so
|
|
7835
|
+
* {@link codeVerifier} reads that flow's verifier and no other (#2726). A
|
|
7836
|
+
* `state` that is not one of ours binds nothing — and, with a flow store, a
|
|
7837
|
+
* flow-less read then fails rather than reaching for the shared slot.
|
|
7838
|
+
*/
|
|
7839
|
+
resumeAuthorizationFlow(state) {
|
|
7840
|
+
this.flowId = parseOAuthState(state)?.authId;
|
|
7841
|
+
}
|
|
7842
|
+
/** Drop the bound flow's verifier once its exchange has completed. */
|
|
7843
|
+
async clearAuthorizationFlow() {
|
|
7844
|
+
if (this.flowStore && this.flowId) {
|
|
7845
|
+
await this.flowStore.clearCodeVerifier(this.flowId);
|
|
7846
|
+
}
|
|
7681
7847
|
}
|
|
7682
7848
|
async clientInformation(ctx) {
|
|
7683
7849
|
const preregistered = await this.storage.getClientInformation(
|
|
@@ -7822,9 +7988,26 @@ var BaseOAuthClientProvider = class {
|
|
|
7822
7988
|
}
|
|
7823
7989
|
}
|
|
7824
7990
|
async saveCodeVerifier(codeVerifier) {
|
|
7991
|
+
if (this.flowStore && this.flowId) {
|
|
7992
|
+
await this.flowStore.saveCodeVerifier(
|
|
7993
|
+
this.flowId,
|
|
7994
|
+
this.serverUrl,
|
|
7995
|
+
codeVerifier
|
|
7996
|
+
);
|
|
7997
|
+
return;
|
|
7998
|
+
}
|
|
7825
7999
|
await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
|
|
7826
8000
|
}
|
|
7827
8001
|
async codeVerifier() {
|
|
8002
|
+
if (this.flowStore) {
|
|
8003
|
+
const verifier2 = this.flowId ? await this.flowStore.getCodeVerifier(this.flowId, this.serverUrl) : void 0;
|
|
8004
|
+
if (!verifier2) {
|
|
8005
|
+
throw new Error(
|
|
8006
|
+
"No code verifier saved for this authorization flow. It may have been started in another browser tab or already completed; please re-authenticate."
|
|
8007
|
+
);
|
|
8008
|
+
}
|
|
8009
|
+
return verifier2;
|
|
8010
|
+
}
|
|
7828
8011
|
const verifier = await this.storage.getCodeVerifier(this.serverUrl);
|
|
7829
8012
|
if (!verifier) {
|
|
7830
8013
|
throw new Error("No code verifier saved for session");
|
|
@@ -7871,6 +8054,10 @@ var BaseOAuthClientProvider = class {
|
|
|
7871
8054
|
await this.storage.clearTokens(this.serverUrl);
|
|
7872
8055
|
return;
|
|
7873
8056
|
case "verifier":
|
|
8057
|
+
if (this.flowStore) {
|
|
8058
|
+
await this.clearAuthorizationFlow();
|
|
8059
|
+
return;
|
|
8060
|
+
}
|
|
7874
8061
|
await this.storage.clearCodeVerifier(this.serverUrl);
|
|
7875
8062
|
return;
|
|
7876
8063
|
case "discovery":
|
|
@@ -8741,11 +8928,6 @@ async function mintEmaResourceTokens(config, resourceContext) {
|
|
|
8741
8928
|
"EMA requires resource authorization server clientId (per-server oauth.clientId)"
|
|
8742
8929
|
);
|
|
8743
8930
|
}
|
|
8744
|
-
if (!config.resourceClientSecret?.trim()) {
|
|
8745
|
-
throw new Error(
|
|
8746
|
-
"EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
|
|
8747
|
-
);
|
|
8748
|
-
}
|
|
8749
8931
|
const idToken = await getValidIdToken({
|
|
8750
8932
|
idp: config.idp,
|
|
8751
8933
|
storage: config.storage,
|
|
@@ -9252,7 +9434,9 @@ var OAuthManager = class {
|
|
|
9252
9434
|
requestRefreshToken: this.oauthConfig.requestRefreshToken,
|
|
9253
9435
|
// #2068: lets the provider tell an `offline_access` the user asked for
|
|
9254
9436
|
// from one inherited from a previous grant's persisted scope.
|
|
9255
|
-
configuredScope: this.oauthConfig.scope
|
|
9437
|
+
configuredScope: this.oauthConfig.scope,
|
|
9438
|
+
// #2726: in-flight PKCE verifiers keyed by flow, not by server URL.
|
|
9439
|
+
flowStore: this.oauthConfig.flowStore
|
|
9256
9440
|
});
|
|
9257
9441
|
provider.setEventTarget(this.params.getEventTarget());
|
|
9258
9442
|
await provider.prepareForAuth();
|
|
@@ -9416,7 +9600,12 @@ var OAuthManager = class {
|
|
|
9416
9600
|
recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
|
|
9417
9601
|
this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
|
|
9418
9602
|
}
|
|
9419
|
-
|
|
9603
|
+
/**
|
|
9604
|
+
* `state` is the OAuth `state` the callback was redirected back with. It
|
|
9605
|
+
* selects the flow whose PKCE verifier the exchange uses (#2726); without it
|
|
9606
|
+
* a provider with a flow store has no verifier to send.
|
|
9607
|
+
*/
|
|
9608
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
9420
9609
|
try {
|
|
9421
9610
|
if (this.isEnterpriseManaged()) {
|
|
9422
9611
|
const emaConfig = await this.getEmaFlowConfig();
|
|
@@ -9440,6 +9629,9 @@ var OAuthManager = class {
|
|
|
9440
9629
|
}
|
|
9441
9630
|
const provider = await this.createOAuthProvider();
|
|
9442
9631
|
const serverUrl = this.getServerUrl();
|
|
9632
|
+
if (state !== void 0) {
|
|
9633
|
+
provider.resumeAuthorizationFlow(state);
|
|
9634
|
+
}
|
|
9443
9635
|
const result = await mcpAuth(provider, {
|
|
9444
9636
|
serverUrl,
|
|
9445
9637
|
authorizationCode,
|
|
@@ -9451,6 +9643,7 @@ var OAuthManager = class {
|
|
|
9451
9643
|
`Expected AUTHORIZED after providing authorization code, got: ${result}`
|
|
9452
9644
|
);
|
|
9453
9645
|
}
|
|
9646
|
+
await provider.clearAuthorizationFlow();
|
|
9454
9647
|
const tokens = await provider.tokens();
|
|
9455
9648
|
if (!tokens) {
|
|
9456
9649
|
throw new Error("Failed to retrieve tokens after authorization");
|
|
@@ -9582,15 +9775,21 @@ var OAuthManager = class {
|
|
|
9582
9775
|
* Re-read persisted OAuth state and determine whether `challenge` is already
|
|
9583
9776
|
* satisfied without an authorization-server round-trip.
|
|
9584
9777
|
*
|
|
9585
|
-
*
|
|
9586
|
-
*
|
|
9587
|
-
*
|
|
9588
|
-
*
|
|
9589
|
-
*
|
|
9590
|
-
*
|
|
9591
|
-
*
|
|
9778
|
+
* For `token_expired` and `insufficient_scope`, returns `true` only when
|
|
9779
|
+
* storage holds a *provably* unexpired token — a JWT whose `exp` is still in
|
|
9780
|
+
* the future. An opaque token carries no local expiry evidence, so the
|
|
9781
|
+
* resource server's verdict stands and re-authorization proceeds.
|
|
9782
|
+
* `insufficient_scope` additionally requires stored + token scope to cover
|
|
9783
|
+
* the SEP-2350 union: some resource servers answer an *expired* token with
|
|
9784
|
+
* `insufficient_scope`, and a scope-only check would replay that dead token
|
|
9785
|
+
* instead of refreshing it (#2724, the same gap #2051 closed for
|
|
9786
|
+
* `token_expired`). `invalid_token` and `unauthorized` always return
|
|
9787
|
+
* `false` — the resource server explicitly rejected the credential.
|
|
9788
|
+
*
|
|
9789
|
+
* A caller that has just completed a grant passes `scopeOnly` — see
|
|
9790
|
+
* {@link CheckAuthChallengeSatisfiedOptions}.
|
|
9592
9791
|
*/
|
|
9593
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
9792
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
9594
9793
|
const storage = this.oauthConfig.storage;
|
|
9595
9794
|
if (!storage) {
|
|
9596
9795
|
return false;
|
|
@@ -9603,6 +9802,14 @@ var OAuthManager = class {
|
|
|
9603
9802
|
if (challenge.reason !== "insufficient_scope") {
|
|
9604
9803
|
return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
|
|
9605
9804
|
}
|
|
9805
|
+
if (!options?.scopeOnly && !isAccessTokenProvablyUnexpired(tokens)) {
|
|
9806
|
+
return false;
|
|
9807
|
+
}
|
|
9808
|
+
return this.isStepUpScopeCovered(challenge, storage, tokens);
|
|
9809
|
+
}
|
|
9810
|
+
/** Stored + token scope cover the challenge's SEP-2350 union. */
|
|
9811
|
+
async isStepUpScopeCovered(challenge, storage, tokens) {
|
|
9812
|
+
const serverUrl = this.getServerUrl();
|
|
9606
9813
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(
|
|
9607
9814
|
challenge,
|
|
9608
9815
|
tokens.scope
|
|
@@ -9706,14 +9913,14 @@ var OAuthManager = class {
|
|
|
9706
9913
|
}
|
|
9707
9914
|
async handleEnterpriseManagedAuthChallenge(challenge, options) {
|
|
9708
9915
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
|
|
9709
|
-
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
|
|
9916
|
+
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp && !await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9710
9917
|
return { kind: "step_up_confirm", challenge: enriched };
|
|
9711
9918
|
}
|
|
9712
9919
|
const config = await this.emaFlowConfigForChallenge(enriched);
|
|
9713
9920
|
if (enriched.reason === "insufficient_scope") {
|
|
9714
9921
|
const silent = await trySilentEmaAuth(config);
|
|
9715
9922
|
if (silent.status === "success") {
|
|
9716
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
9923
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9717
9924
|
const minted = await this.requireStorage().getTokens(
|
|
9718
9925
|
this.getServerUrl()
|
|
9719
9926
|
);
|
|
@@ -9791,9 +9998,16 @@ var OAuthManager = class {
|
|
|
9791
9998
|
fetchFn: this.params.effectiveAuthFetch,
|
|
9792
9999
|
resourceMetadataUrl,
|
|
9793
10000
|
...enriched.reason === "insufficient_scope" && {
|
|
10001
|
+
// Judge coverage the way checkAuthChallengeSatisfied does: a token
|
|
10002
|
+
// response that omitted `scope` granted what was stored, so a
|
|
10003
|
+
// covered challenge on an expired token refreshes rather than
|
|
10004
|
+
// forcing interactive authorization (#2724).
|
|
9794
10005
|
forceReauthorization: isStrictScopeSuperset(
|
|
9795
10006
|
scopeForAuth,
|
|
9796
|
-
|
|
10007
|
+
resolveEffectiveGrantedScope(
|
|
10008
|
+
await this.oauthConfig.storage?.getScope(serverUrl),
|
|
10009
|
+
tokens?.scope
|
|
10010
|
+
)
|
|
9797
10011
|
)
|
|
9798
10012
|
}
|
|
9799
10013
|
});
|
|
@@ -9802,7 +10016,7 @@ var OAuthManager = class {
|
|
|
9802
10016
|
}
|
|
9803
10017
|
if (result === "AUTHORIZED") {
|
|
9804
10018
|
if (enriched.reason === "insufficient_scope") {
|
|
9805
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
10019
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9806
10020
|
const freshTokens = await provider.tokens();
|
|
9807
10021
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
9808
10022
|
freshTokens?.scope,
|
|
@@ -9904,7 +10118,7 @@ var OAuthManager = class {
|
|
|
9904
10118
|
if (result !== "AUTHORIZED") {
|
|
9905
10119
|
return null;
|
|
9906
10120
|
}
|
|
9907
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
10121
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
9908
10122
|
const freshTokens = await provider.tokens();
|
|
9909
10123
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
9910
10124
|
freshTokens?.scope,
|
|
@@ -9936,8 +10150,8 @@ var OAuthManager = class {
|
|
|
9936
10150
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
9937
10151
|
import {
|
|
9938
10152
|
SdkError as SdkError5,
|
|
9939
|
-
SdkErrorCode as
|
|
9940
|
-
SdkHttpError
|
|
10153
|
+
SdkErrorCode as SdkErrorCode6,
|
|
10154
|
+
SdkHttpError as SdkHttpError3
|
|
9941
10155
|
} from "@modelcontextprotocol/client";
|
|
9942
10156
|
|
|
9943
10157
|
// ../../core/mcp/remote/types.ts
|
|
@@ -9994,14 +10208,15 @@ function isConnectTransportError(json) {
|
|
|
9994
10208
|
return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
|
|
9995
10209
|
}
|
|
9996
10210
|
var SDK_ERROR_CODES = new Set(
|
|
9997
|
-
Object.values(
|
|
10211
|
+
Object.values(SdkErrorCode6)
|
|
9998
10212
|
);
|
|
9999
10213
|
function toSdkHttpError(message, http) {
|
|
10000
|
-
const code = SDK_ERROR_CODES.has(http.code) ? http.code :
|
|
10001
|
-
return new
|
|
10214
|
+
const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode6.ClientHttpNotImplemented;
|
|
10215
|
+
return new SdkHttpError3(code, message, {
|
|
10002
10216
|
status: http.status,
|
|
10003
10217
|
...http.statusText !== void 0 && { statusText: http.statusText },
|
|
10004
|
-
...http.text !== void 0 && { text: http.text }
|
|
10218
|
+
...http.text !== void 0 && { text: http.text },
|
|
10219
|
+
...http.retryAfter !== void 0 && { retryAfter: http.retryAfter }
|
|
10005
10220
|
});
|
|
10006
10221
|
}
|
|
10007
10222
|
function legacySessionId(json) {
|
|
@@ -10435,7 +10650,7 @@ var RemoteClientTransport = class {
|
|
|
10435
10650
|
timer = setTimeout(() => {
|
|
10436
10651
|
this.sseResponseWaits.delete(requestId);
|
|
10437
10652
|
reject(
|
|
10438
|
-
new SdkError5(
|
|
10653
|
+
new SdkError5(SdkErrorCode6.RequestTimeout, "Request timed out", {
|
|
10439
10654
|
timeout: this.sseResponseTimeoutMs
|
|
10440
10655
|
})
|
|
10441
10656
|
);
|
|
@@ -10647,7 +10862,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10647
10862
|
transport: () => this.transport,
|
|
10648
10863
|
defaultTimeoutMs: () => this.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC,
|
|
10649
10864
|
resetTimeoutOnProgress: () => this.resetTimeoutOnProgress,
|
|
10650
|
-
|
|
10865
|
+
annotateError: (error, method) => this.annotateRequestError(error, method)
|
|
10651
10866
|
});
|
|
10652
10867
|
dispatchTaskRequest = (request, options) => this.rawWire.dispatch(request, options);
|
|
10653
10868
|
// Every outbound request still awaiting a response, keyed by JSON-RPC id.
|
|
@@ -10667,6 +10882,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10667
10882
|
// `GET` on Streamable HTTP). Both per-session, reset with the maps above.
|
|
10668
10883
|
lastResponse;
|
|
10669
10884
|
notificationStream;
|
|
10885
|
+
// Each tracked `429`'s `Retry-After`, by the JSON-RPC id that drew it, until
|
|
10886
|
+
// that id's failed send claims it (#2442). Per-session, reset with the
|
|
10887
|
+
// diagnostics above.
|
|
10888
|
+
rateLimits = new RateLimitLedger();
|
|
10670
10889
|
/** True when the cached transport was built with an OAuth authProvider attached. */
|
|
10671
10890
|
transportHasAuthProvider = false;
|
|
10672
10891
|
/** Dedupes concurrent ambient auth challenges (reason + scopes). */
|
|
@@ -10811,6 +11030,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10811
11030
|
// site to the two failure handlers. Cleared by any user-initiated refresh (a
|
|
10812
11031
|
// subscribe/unsubscribe is a fresh attempt, and the server may have changed).
|
|
10813
11032
|
modernNeverAcknowledged = false;
|
|
11033
|
+
// Set when the last `listen()` was refused at the HTTP layer with a status a
|
|
11034
|
+
// retry cannot change (#2722) — the sibling of `modernNeverAcknowledged`,
|
|
11035
|
+
// carried and cleared the same way, holding the HTTP answer the UI reports.
|
|
11036
|
+
modernListenRefusal = null;
|
|
10814
11037
|
/** Which in-flight task calls own each progress token (#2316). */
|
|
10815
11038
|
taskProgress = new TaskProgressRouter();
|
|
10816
11039
|
// Abort controller for the in-flight ordinary (non-task) tool call. Aborting
|
|
@@ -11068,7 +11291,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11068
11291
|
this.clientInfo,
|
|
11069
11292
|
Object.keys(clientOptions).length > 0 ? clientOptions : void 0
|
|
11070
11293
|
);
|
|
11071
|
-
this.
|
|
11294
|
+
this.annotateSdkRequestErrors(this.client);
|
|
11072
11295
|
if (this.tasksCapabilityAdvertised) {
|
|
11073
11296
|
this.bindReceiverTasks();
|
|
11074
11297
|
}
|
|
@@ -11076,7 +11299,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11076
11299
|
/**
|
|
11077
11300
|
* Decorate the SDK Client's `request` so a per-request timeout rejects with
|
|
11078
11301
|
* the connection's state at that moment instead of a bare `Request timed
|
|
11079
|
-
* out` (#2318)
|
|
11302
|
+
* out` (#2318), and a `429` rejects as rate limiting with the server's
|
|
11303
|
+
* `Retry-After` instead of a bare `Error POSTing to endpoint` (#2442).
|
|
11080
11304
|
*
|
|
11081
11305
|
* `Protocol.request` is the one funnel every SDK verb goes through —
|
|
11082
11306
|
* `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
|
|
@@ -11094,21 +11318,30 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11094
11318
|
* implementation signature serves both at runtime; the single cast on the
|
|
11095
11319
|
* way back is what the overloads cost.
|
|
11096
11320
|
*/
|
|
11097
|
-
|
|
11321
|
+
annotateSdkRequestErrors(client) {
|
|
11098
11322
|
const sdkRequest = client.request.bind(client);
|
|
11099
11323
|
const decorated = async (...args) => {
|
|
11100
11324
|
try {
|
|
11101
11325
|
return await sdkRequest(...args);
|
|
11102
11326
|
} catch (err) {
|
|
11103
|
-
throw
|
|
11104
|
-
err,
|
|
11105
|
-
args[0].method,
|
|
11106
|
-
this.getConnectionDiagnostics()
|
|
11107
|
-
);
|
|
11327
|
+
throw this.annotateRequestError(err, args[0].method);
|
|
11108
11328
|
}
|
|
11109
11329
|
};
|
|
11110
11330
|
client.request = decorated;
|
|
11111
11331
|
}
|
|
11332
|
+
/**
|
|
11333
|
+
* The one place a failed request's error is decorated, shared by the SDK
|
|
11334
|
+
* funnel above and the raw-wire channel that bypasses it (modern-era tool
|
|
11335
|
+
* calls and task operations), so a caller sees the same error whichever
|
|
11336
|
+
* path its request took: a timeout with the connection's state (#2318), a
|
|
11337
|
+
* `429` named as rate limiting (#2442), anything else untouched.
|
|
11338
|
+
*/
|
|
11339
|
+
annotateRequestError(err, method) {
|
|
11340
|
+
return annotateRateLimit(
|
|
11341
|
+
annotateRequestTimeout(err, method, this.getConnectionDiagnostics()),
|
|
11342
|
+
method
|
|
11343
|
+
);
|
|
11344
|
+
}
|
|
11112
11345
|
/**
|
|
11113
11346
|
* @param base - the composed OAuth-path fetch, deadline innermost (#2319).
|
|
11114
11347
|
*
|
|
@@ -11152,9 +11385,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11152
11385
|
},
|
|
11153
11386
|
trackSendFailure: (message, error) => {
|
|
11154
11387
|
if (isRequestTimeoutError(error)) return;
|
|
11388
|
+
attachRetryAfter(error, this.rateLimits.take(message.id));
|
|
11155
11389
|
if (this.outstandingRequests.delete(message.id)) {
|
|
11156
11390
|
this.dispatchConnectionDiagnosticsChange();
|
|
11157
11391
|
}
|
|
11392
|
+
this.dispatchTypedEvent("requestFailed", {
|
|
11393
|
+
id: message.id,
|
|
11394
|
+
reason: describeSendFailure(error)
|
|
11395
|
+
});
|
|
11158
11396
|
},
|
|
11159
11397
|
trackResponse: (message, origin) => {
|
|
11160
11398
|
const responseId = message.id;
|
|
@@ -11220,6 +11458,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11220
11458
|
};
|
|
11221
11459
|
baseTransport.onerror = (error) => {
|
|
11222
11460
|
if (this.status === "connecting") return;
|
|
11461
|
+
if (subscriptionListenRefusal(error) !== void 0) return;
|
|
11223
11462
|
this.status = "error";
|
|
11224
11463
|
this.dispatchTypedEvent("statusChange", this.status);
|
|
11225
11464
|
this.dispatchTypedEvent("error", error);
|
|
@@ -11465,6 +11704,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11465
11704
|
this.clearModernReconnectTimer();
|
|
11466
11705
|
this.modernReconnectAttempts = 0;
|
|
11467
11706
|
this.modernNeverAcknowledged = false;
|
|
11707
|
+
this.modernListenRefusal = null;
|
|
11468
11708
|
this.modernSubscription = null;
|
|
11469
11709
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
11470
11710
|
this.dispatchSubscriptionsChange();
|
|
@@ -11503,6 +11743,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11503
11743
|
this.taskProgress.clear();
|
|
11504
11744
|
this.lastResponse = void 0;
|
|
11505
11745
|
this.notificationStream = void 0;
|
|
11746
|
+
this.rateLimits.clear();
|
|
11506
11747
|
this.dispatchConnectionDiagnosticsChange();
|
|
11507
11748
|
if (this.malformedListItems.size > 0) {
|
|
11508
11749
|
this.malformedListItems.clear();
|
|
@@ -11605,6 +11846,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11605
11846
|
onFetchRequest: (entry) => {
|
|
11606
11847
|
this.dispatchFetchRequest({ ...entry, category: "transport" });
|
|
11607
11848
|
this.noteTransportStream(entry);
|
|
11849
|
+
this.rateLimits.record(entry);
|
|
11608
11850
|
},
|
|
11609
11851
|
onFetchResponseBody: (id, body) => {
|
|
11610
11852
|
this.dispatchFetchRequestBodyUpdate(id, body);
|
|
@@ -13944,11 +14186,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13944
14186
|
// for page one.
|
|
13945
14187
|
...cursor !== void 0 ? { cursor } : {}
|
|
13946
14188
|
};
|
|
14189
|
+
const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
|
|
13947
14190
|
try {
|
|
13948
14191
|
return await this.invokeMcpClient(
|
|
13949
14192
|
() => this.client.request(
|
|
13950
14193
|
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
|
|
13951
|
-
|
|
14194
|
+
resultSchema,
|
|
13952
14195
|
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13953
14196
|
),
|
|
13954
14197
|
{ method: RESOURCES_DIRECTORY_READ_METHOD }
|
|
@@ -14403,6 +14646,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14403
14646
|
this.clearModernReconnectTimer();
|
|
14404
14647
|
this.modernReconnectAttempts = 0;
|
|
14405
14648
|
this.modernNeverAcknowledged = false;
|
|
14649
|
+
this.modernListenRefusal = null;
|
|
14406
14650
|
}
|
|
14407
14651
|
const generation = ++this.modernListenGeneration;
|
|
14408
14652
|
const previous = this.modernSubscription;
|
|
@@ -14421,8 +14665,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14421
14665
|
this.getRequestOptions()
|
|
14422
14666
|
);
|
|
14423
14667
|
} catch (error) {
|
|
14424
|
-
if (generation === this.modernListenGeneration
|
|
14425
|
-
|
|
14668
|
+
if (generation === this.modernListenGeneration) {
|
|
14669
|
+
if (isNeverAcknowledgedSubscriptionClose(error)) {
|
|
14670
|
+
this.modernNeverAcknowledged = true;
|
|
14671
|
+
}
|
|
14672
|
+
this.modernListenRefusal = subscriptionListenRefusal(error) ?? null;
|
|
14426
14673
|
}
|
|
14427
14674
|
throw error;
|
|
14428
14675
|
}
|
|
@@ -14503,6 +14750,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14503
14750
|
* its error either way — the retry is about the subscriptions, not the call.
|
|
14504
14751
|
*/
|
|
14505
14752
|
reconcileModernStreamStateAfterFailedRefresh() {
|
|
14753
|
+
if (this.modernListenRefusal) {
|
|
14754
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
14755
|
+
return;
|
|
14756
|
+
}
|
|
14506
14757
|
if (!this.wantsModernStream()) {
|
|
14507
14758
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
14508
14759
|
return;
|
|
@@ -14535,6 +14786,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14535
14786
|
honoredUris: []
|
|
14536
14787
|
});
|
|
14537
14788
|
}
|
|
14789
|
+
/**
|
|
14790
|
+
* Settle the stream on an HTTP refusal of the listen (#2722): the
|
|
14791
|
+
* `"refused"` status carrying the HTTP answer, no reconnect, and a log line
|
|
14792
|
+
* with the sentence the UI shows. Reported whatever `active` says — see
|
|
14793
|
+
* `ResourceSubscriptionStreamState`.
|
|
14794
|
+
*/
|
|
14795
|
+
endModernStreamRefused(refusal) {
|
|
14796
|
+
this.clearModernReconnectTimer();
|
|
14797
|
+
const message = subscriptionListenRefusalMessage(refusal);
|
|
14798
|
+
this.logger.warn({ message }, "subscriptions/listen refused over HTTP");
|
|
14799
|
+
this.setModernStreamState({
|
|
14800
|
+
active: this.modernStreamActive(),
|
|
14801
|
+
status: "refused",
|
|
14802
|
+
honoredUris: [],
|
|
14803
|
+
refusal
|
|
14804
|
+
});
|
|
14805
|
+
}
|
|
14538
14806
|
/**
|
|
14539
14807
|
* Schedule a reconnect re-listen after the current backoff delay (#1630).
|
|
14540
14808
|
* `modernReconnectAttempts` reflects the number of *consecutive failed*
|
|
@@ -14572,6 +14840,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14572
14840
|
this.endModernStreamNeverAcknowledged();
|
|
14573
14841
|
return;
|
|
14574
14842
|
}
|
|
14843
|
+
if (this.modernListenRefusal && !isTerminalStatus(this.status)) {
|
|
14844
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
14845
|
+
return;
|
|
14846
|
+
}
|
|
14575
14847
|
this.modernReconnectAttempts += 1;
|
|
14576
14848
|
if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
|
|
14577
14849
|
this.setModernStreamState({
|
|
@@ -14726,8 +14998,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14726
14998
|
* Re-read OAuth storage and test whether a challenge is already satisfied.
|
|
14727
14999
|
* See {@link OAuthManager.checkAuthChallengeSatisfied}.
|
|
14728
15000
|
*/
|
|
14729
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
14730
|
-
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
15001
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
15002
|
+
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
15003
|
+
challenge,
|
|
15004
|
+
options
|
|
15005
|
+
);
|
|
14731
15006
|
}
|
|
14732
15007
|
/**
|
|
14733
15008
|
* Push recovered OAuth auth state to the remote backend (same MCP session).
|
|
@@ -14891,8 +15166,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14891
15166
|
* Direct transports reconnect after token exchange so the live MCP session
|
|
14892
15167
|
* picks up the new Bearer token (mirrors silent recovery reconnect).
|
|
14893
15168
|
*/
|
|
14894
|
-
async completeOAuthFlow(authorizationCode, iss) {
|
|
14895
|
-
await this.ensureOAuthManager().completeOAuthFlow(
|
|
15169
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
15170
|
+
await this.ensureOAuthManager().completeOAuthFlow(
|
|
15171
|
+
authorizationCode,
|
|
15172
|
+
iss,
|
|
15173
|
+
state
|
|
15174
|
+
);
|
|
14896
15175
|
if (this.usesDirectAuthRecovery()) {
|
|
14897
15176
|
await this.reconnectAfterAuthRecovery();
|
|
14898
15177
|
}
|
|
@@ -14916,7 +15195,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14916
15195
|
* Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
|
|
14917
15196
|
*/
|
|
14918
15197
|
async resumeAfterOAuth(authorizationCode, options) {
|
|
14919
|
-
await this.completeOAuthFlow(
|
|
15198
|
+
await this.completeOAuthFlow(
|
|
15199
|
+
authorizationCode,
|
|
15200
|
+
options?.iss,
|
|
15201
|
+
options?.state
|
|
15202
|
+
);
|
|
14920
15203
|
const remoteSessionId = options?.remoteSessionId;
|
|
14921
15204
|
const transport = this.baseTransport;
|
|
14922
15205
|
if (remoteSessionId && transport instanceof RemoteClientTransport) {
|
|
@@ -15302,6 +15585,16 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
15302
15585
|
return;
|
|
15303
15586
|
}
|
|
15304
15587
|
};
|
|
15588
|
+
const onRequestFailed = (event) => {
|
|
15589
|
+
const { id, reason } = event.detail;
|
|
15590
|
+
const requestEntry = this.pendingRequestEntries.get(id);
|
|
15591
|
+
if (!requestEntry) return;
|
|
15592
|
+
this.pendingRequestEntries.delete(id);
|
|
15593
|
+
requestEntry.clientError = reason;
|
|
15594
|
+
requestEntry.duration = Date.now() - requestEntry.timestamp.getTime();
|
|
15595
|
+
this.dispatchTypedEvent("message", requestEntry);
|
|
15596
|
+
this.dispatchTypedEvent("messagesChange", this.getMessages());
|
|
15597
|
+
};
|
|
15305
15598
|
const onStatusChange = () => {
|
|
15306
15599
|
if (isTerminalStatus(this.client?.getStatus())) {
|
|
15307
15600
|
this.messages = [];
|
|
@@ -15311,11 +15604,13 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
15311
15604
|
};
|
|
15312
15605
|
this.client.addEventListener("message", onMessage);
|
|
15313
15606
|
this.client.addEventListener("responseRejected", onResponseRejected);
|
|
15607
|
+
this.client.addEventListener("requestFailed", onRequestFailed);
|
|
15314
15608
|
this.client.addEventListener("statusChange", onStatusChange);
|
|
15315
15609
|
this.unsubscribe = () => {
|
|
15316
15610
|
if (this.client) {
|
|
15317
15611
|
this.client.removeEventListener("message", onMessage);
|
|
15318
15612
|
this.client.removeEventListener("responseRejected", onResponseRejected);
|
|
15613
|
+
this.client.removeEventListener("requestFailed", onRequestFailed);
|
|
15319
15614
|
this.client.removeEventListener("statusChange", onStatusChange);
|
|
15320
15615
|
}
|
|
15321
15616
|
this.client = null;
|
|
@@ -17083,7 +17378,7 @@ function serializeOAuthPersistBlob(snapshot) {
|
|
|
17083
17378
|
import { resolve as resolve6 } from "path";
|
|
17084
17379
|
|
|
17085
17380
|
// ../../core/auth/node/oauth-secrets.ts
|
|
17086
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
17381
|
+
import { createHmac, randomUUID as randomUUID2 } from "crypto";
|
|
17087
17382
|
import { OAuthTokensSchema as OAuthTokensSchema5 } from "@modelcontextprotocol/core";
|
|
17088
17383
|
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
17089
17384
|
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
@@ -17116,6 +17411,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
|
|
|
17116
17411
|
var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
|
|
17117
17412
|
var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
|
|
17118
17413
|
var IDP_SESSION_FIELD = "idp-session";
|
|
17414
|
+
var clientCredentialStampField = (field) => `${field}#owner`;
|
|
17415
|
+
var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
|
|
17416
|
+
function clientCredentialStamp(clientId, value) {
|
|
17417
|
+
return JSON.stringify({
|
|
17418
|
+
client_id: clientId,
|
|
17419
|
+
mac: credentialMac(clientId, value)
|
|
17420
|
+
});
|
|
17421
|
+
}
|
|
17422
|
+
function stampedOwner(stamp, value) {
|
|
17423
|
+
if (stamp === void 0) return void 0;
|
|
17424
|
+
let parsed;
|
|
17425
|
+
try {
|
|
17426
|
+
parsed = JSON.parse(stamp);
|
|
17427
|
+
} catch {
|
|
17428
|
+
return void 0;
|
|
17429
|
+
}
|
|
17430
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
17431
|
+
const { client_id: clientId, mac } = parsed;
|
|
17432
|
+
if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
|
|
17433
|
+
return void 0;
|
|
17434
|
+
}
|
|
17435
|
+
return clientId;
|
|
17436
|
+
}
|
|
17119
17437
|
function applyTokensPolicy(tokens, policy) {
|
|
17120
17438
|
if (policy === "none") return void 0;
|
|
17121
17439
|
if (policy === "access" && tokens.refresh_token !== void 0) {
|
|
@@ -17144,12 +17462,18 @@ function splitClientInformation(info, fields, secrets) {
|
|
|
17144
17462
|
if (client_secret === void 0 && registration_access_token === void 0) {
|
|
17145
17463
|
return info;
|
|
17146
17464
|
}
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17465
|
+
const put = (field, value) => {
|
|
17466
|
+
if (value === void 0) return;
|
|
17467
|
+
secrets[field] = value;
|
|
17468
|
+
if (fields.stamped && typeof info.client_id === "string") {
|
|
17469
|
+
secrets[clientCredentialStampField(field)] = clientCredentialStamp(
|
|
17470
|
+
info.client_id,
|
|
17471
|
+
value
|
|
17472
|
+
);
|
|
17473
|
+
}
|
|
17474
|
+
};
|
|
17475
|
+
put(fields.clientSecret, client_secret);
|
|
17476
|
+
put(fields.registrationToken, registration_access_token);
|
|
17153
17477
|
return publicInfo;
|
|
17154
17478
|
}
|
|
17155
17479
|
function joinClientInformation(info, fields, secrets) {
|
|
@@ -17168,15 +17492,18 @@ function joinClientInformation(info, fields, secrets) {
|
|
|
17168
17492
|
}
|
|
17169
17493
|
var issuerClientInfoFields = (issuer) => ({
|
|
17170
17494
|
clientSecret: issuerClientSecretField(issuer),
|
|
17171
|
-
registrationToken: issuerRegistrationTokenField(issuer)
|
|
17495
|
+
registrationToken: issuerRegistrationTokenField(issuer),
|
|
17496
|
+
stamped: true
|
|
17172
17497
|
});
|
|
17173
17498
|
var LEGACY_CLIENT_INFO_FIELDS = {
|
|
17174
17499
|
clientSecret: LEGACY_CLIENT_SECRET_FIELD,
|
|
17175
|
-
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
|
|
17500
|
+
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
17501
|
+
stamped: true
|
|
17176
17502
|
};
|
|
17177
17503
|
var PREREG_CLIENT_INFO_FIELDS = {
|
|
17178
17504
|
clientSecret: PREREG_CLIENT_SECRET_FIELD,
|
|
17179
|
-
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
|
|
17505
|
+
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
|
|
17506
|
+
stamped: false
|
|
17180
17507
|
};
|
|
17181
17508
|
function splitServerOAuthState(state, policy) {
|
|
17182
17509
|
const secrets = {};
|
|
@@ -17326,20 +17653,54 @@ function joinIdpSession(residue, secrets) {
|
|
|
17326
17653
|
return stored ? { ...residue, ...stored } : { ...residue };
|
|
17327
17654
|
}
|
|
17328
17655
|
function serverSecretFields(state) {
|
|
17656
|
+
const stamped = (field) => [
|
|
17657
|
+
field,
|
|
17658
|
+
clientCredentialStampField(field)
|
|
17659
|
+
];
|
|
17329
17660
|
const fields = /* @__PURE__ */ new Set([
|
|
17330
17661
|
LEGACY_TOKENS_FIELD,
|
|
17331
|
-
LEGACY_CLIENT_SECRET_FIELD,
|
|
17332
|
-
LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
17662
|
+
...stamped(LEGACY_CLIENT_SECRET_FIELD),
|
|
17663
|
+
...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
|
|
17333
17664
|
PREREG_CLIENT_SECRET_FIELD,
|
|
17334
17665
|
PREREG_REGISTRATION_TOKEN_FIELD
|
|
17335
17666
|
]);
|
|
17336
17667
|
for (const issuer of Object.keys(state?.byIssuer ?? {})) {
|
|
17337
17668
|
fields.add(issuerTokensField(issuer));
|
|
17338
|
-
|
|
17339
|
-
|
|
17669
|
+
for (const field of stamped(issuerClientSecretField(issuer))) {
|
|
17670
|
+
fields.add(field);
|
|
17671
|
+
}
|
|
17672
|
+
for (const field of stamped(issuerRegistrationTokenField(issuer))) {
|
|
17673
|
+
fields.add(field);
|
|
17674
|
+
}
|
|
17340
17675
|
}
|
|
17341
17676
|
return [...fields];
|
|
17342
17677
|
}
|
|
17678
|
+
function clientCredentialOwners(state) {
|
|
17679
|
+
const owners = /* @__PURE__ */ new Map();
|
|
17680
|
+
const add2 = (info, fields) => {
|
|
17681
|
+
if (typeof info?.client_id !== "string") return;
|
|
17682
|
+
owners.set(fields.clientSecret, info.client_id);
|
|
17683
|
+
owners.set(fields.registrationToken, info.client_id);
|
|
17684
|
+
};
|
|
17685
|
+
add2(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
|
|
17686
|
+
for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
|
|
17687
|
+
add2(slot.clientInformation, issuerClientInfoFields(issuer));
|
|
17688
|
+
}
|
|
17689
|
+
return owners;
|
|
17690
|
+
}
|
|
17691
|
+
function restampClientCredentials(state, secrets) {
|
|
17692
|
+
const out = { ...secrets };
|
|
17693
|
+
for (const [field, clientId] of clientCredentialOwners(state)) {
|
|
17694
|
+
const stampField = clientCredentialStampField(field);
|
|
17695
|
+
const value = Object.hasOwn(out, field) ? out[field] : void 0;
|
|
17696
|
+
if (value === void 0) {
|
|
17697
|
+
delete out[stampField];
|
|
17698
|
+
} else if (stampedOwner(out[stampField], value) === void 0) {
|
|
17699
|
+
setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
|
|
17700
|
+
}
|
|
17701
|
+
}
|
|
17702
|
+
return out;
|
|
17703
|
+
}
|
|
17343
17704
|
function clientInfoHasPlaintext(info) {
|
|
17344
17705
|
return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
|
|
17345
17706
|
}
|
|
@@ -17380,6 +17741,44 @@ function coveredLocations(location, byLocation) {
|
|
|
17380
17741
|
([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
|
|
17381
17742
|
).map(([recorded, keys]) => ({ location: recorded, keys }));
|
|
17382
17743
|
}
|
|
17744
|
+
var emptyKeys = () => ({
|
|
17745
|
+
servers: /* @__PURE__ */ new Set(),
|
|
17746
|
+
idpSessions: /* @__PURE__ */ new Set(),
|
|
17747
|
+
owners: /* @__PURE__ */ new Map()
|
|
17748
|
+
});
|
|
17749
|
+
function candidateFields(location, byLocation) {
|
|
17750
|
+
const fields = /* @__PURE__ */ new Map();
|
|
17751
|
+
for (const { keys } of coveredLocations(location, byLocation)) {
|
|
17752
|
+
for (const [url, owned] of keys.owners) {
|
|
17753
|
+
const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
|
|
17754
|
+
for (const field of owned.keys()) kept.add(field);
|
|
17755
|
+
fields.set(url, kept);
|
|
17756
|
+
}
|
|
17757
|
+
}
|
|
17758
|
+
return fields;
|
|
17759
|
+
}
|
|
17760
|
+
function addMissingOwners(target, source) {
|
|
17761
|
+
for (const [url, fields] of source) {
|
|
17762
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
17763
|
+
for (const [field, clientId] of fields) {
|
|
17764
|
+
if (!kept.has(field)) kept.set(field, clientId);
|
|
17765
|
+
}
|
|
17766
|
+
if (kept.size > 0) target.set(url, kept);
|
|
17767
|
+
}
|
|
17768
|
+
}
|
|
17769
|
+
function mergeOwners(target, source) {
|
|
17770
|
+
let changed = false;
|
|
17771
|
+
for (const [url, fields] of source) {
|
|
17772
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
17773
|
+
for (const [field, clientId] of fields) {
|
|
17774
|
+
if (kept.get(field) === clientId) continue;
|
|
17775
|
+
kept.set(field, clientId);
|
|
17776
|
+
changed = true;
|
|
17777
|
+
}
|
|
17778
|
+
if (kept.size > 0) target.set(url, kept);
|
|
17779
|
+
}
|
|
17780
|
+
return changed;
|
|
17781
|
+
}
|
|
17383
17782
|
var warnedLedgerFailures = /* @__PURE__ */ new Set();
|
|
17384
17783
|
function warnLedgerFailure(what, error) {
|
|
17385
17784
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -17393,6 +17792,19 @@ function warnLedgerFailure(what, error) {
|
|
|
17393
17792
|
function isRecord5(value) {
|
|
17394
17793
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17395
17794
|
}
|
|
17795
|
+
function parseOwners(value) {
|
|
17796
|
+
const owners = /* @__PURE__ */ new Map();
|
|
17797
|
+
if (!isRecord5(value)) return owners;
|
|
17798
|
+
for (const [url, fields] of Object.entries(value)) {
|
|
17799
|
+
if (!isRecord5(fields)) continue;
|
|
17800
|
+
const byField = /* @__PURE__ */ new Map();
|
|
17801
|
+
for (const [field, clientId] of Object.entries(fields)) {
|
|
17802
|
+
if (typeof clientId === "string") byField.set(field, clientId);
|
|
17803
|
+
}
|
|
17804
|
+
if (byField.size > 0) owners.set(url, byField);
|
|
17805
|
+
}
|
|
17806
|
+
return owners;
|
|
17807
|
+
}
|
|
17396
17808
|
function stringsOf(value) {
|
|
17397
17809
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
17398
17810
|
}
|
|
@@ -17414,7 +17826,9 @@ function parseLedger(raw) {
|
|
|
17414
17826
|
if (!isRecord5(keys)) continue;
|
|
17415
17827
|
byLocation.set(location, {
|
|
17416
17828
|
servers: new Set(stringsOf(keys.servers)),
|
|
17417
|
-
idpSessions: new Set(stringsOf(keys.idpSessions))
|
|
17829
|
+
idpSessions: new Set(stringsOf(keys.idpSessions)),
|
|
17830
|
+
owners: parseOwners(keys.owners),
|
|
17831
|
+
...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
|
|
17418
17832
|
});
|
|
17419
17833
|
}
|
|
17420
17834
|
if (byLocation.size > 0) ledger.set(namespace, byLocation);
|
|
@@ -17428,7 +17842,19 @@ function serializeLedger(ledger) {
|
|
|
17428
17842
|
Object.fromEntries(
|
|
17429
17843
|
[...byLocation].map(([location, keys]) => [
|
|
17430
17844
|
location,
|
|
17431
|
-
{
|
|
17845
|
+
{
|
|
17846
|
+
servers: [...keys.servers],
|
|
17847
|
+
idpSessions: [...keys.idpSessions],
|
|
17848
|
+
...keys.owners.size > 0 && {
|
|
17849
|
+
owners: Object.fromEntries(
|
|
17850
|
+
[...keys.owners].map(([url, fields]) => [
|
|
17851
|
+
url,
|
|
17852
|
+
Object.fromEntries(fields)
|
|
17853
|
+
])
|
|
17854
|
+
)
|
|
17855
|
+
},
|
|
17856
|
+
...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
|
|
17857
|
+
}
|
|
17432
17858
|
])
|
|
17433
17859
|
)
|
|
17434
17860
|
])
|
|
@@ -17440,7 +17866,7 @@ async function writeLedger(stateFilePath, ledger) {
|
|
|
17440
17866
|
if (ledger.size === 0) await deleteStoreFile(path7);
|
|
17441
17867
|
else await writeStoreFile(path7, serializeLedger(ledger));
|
|
17442
17868
|
}
|
|
17443
|
-
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
|
|
17869
|
+
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
|
|
17444
17870
|
try {
|
|
17445
17871
|
const ledger = parseLedger(
|
|
17446
17872
|
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
@@ -17454,7 +17880,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
17454
17880
|
let keys = byLocation.get(location);
|
|
17455
17881
|
let changed = false;
|
|
17456
17882
|
if (!keys) {
|
|
17457
|
-
keys =
|
|
17883
|
+
keys = emptyKeys();
|
|
17458
17884
|
byLocation.set(location, keys);
|
|
17459
17885
|
changed = true;
|
|
17460
17886
|
}
|
|
@@ -17468,11 +17894,93 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
17468
17894
|
keys.idpSessions.add(issuer);
|
|
17469
17895
|
changed = true;
|
|
17470
17896
|
}
|
|
17897
|
+
if (mergeOwners(keys.owners, owners)) changed = true;
|
|
17471
17898
|
if (changed) await writeLedger(stateFilePath, ledger);
|
|
17472
17899
|
} catch (error) {
|
|
17473
17900
|
warnLedgerFailure("update", error);
|
|
17474
17901
|
}
|
|
17475
17902
|
}
|
|
17903
|
+
async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
|
|
17904
|
+
const ledger = parseLedger(
|
|
17905
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
17906
|
+
);
|
|
17907
|
+
const location = await secretStoreLocation(secretStore);
|
|
17908
|
+
let changed = false;
|
|
17909
|
+
for (const [namespace, byLocation] of ledger) {
|
|
17910
|
+
if (namespace === into) continue;
|
|
17911
|
+
for (const [recorded, keys] of byLocation) {
|
|
17912
|
+
if (recorded === location || keys.owners.size === 0) continue;
|
|
17913
|
+
if (keys.owedTo === into) continue;
|
|
17914
|
+
keys.owedTo = into;
|
|
17915
|
+
changed = true;
|
|
17916
|
+
}
|
|
17917
|
+
}
|
|
17918
|
+
if (changed) await writeLedger(stateFilePath, ledger);
|
|
17919
|
+
}
|
|
17920
|
+
async function pendingCarries(stateFilePath, secretStore, current) {
|
|
17921
|
+
const ledger = parseLedger(
|
|
17922
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
17923
|
+
);
|
|
17924
|
+
const location = await secretStoreLocation(secretStore);
|
|
17925
|
+
const carries = [];
|
|
17926
|
+
for (const [namespace, byLocation] of ledger) {
|
|
17927
|
+
const owed = coveredLocations(location, byLocation).some(
|
|
17928
|
+
({ keys }) => keys.owedTo === current
|
|
17929
|
+
);
|
|
17930
|
+
if (owed) {
|
|
17931
|
+
carries.push({
|
|
17932
|
+
namespace,
|
|
17933
|
+
fields: candidateFields(location, byLocation)
|
|
17934
|
+
});
|
|
17935
|
+
}
|
|
17936
|
+
}
|
|
17937
|
+
return carries.reverse();
|
|
17938
|
+
}
|
|
17939
|
+
async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
|
|
17940
|
+
let carried = true;
|
|
17941
|
+
for (const [url, fields] of recorded) {
|
|
17942
|
+
for (const field of fields) {
|
|
17943
|
+
const clientId = now.get(url)?.get(field);
|
|
17944
|
+
if (clientId === void 0) continue;
|
|
17945
|
+
try {
|
|
17946
|
+
const from = oauthSecretServerId(url, namespace);
|
|
17947
|
+
const to = oauthSecretServerId(url, current);
|
|
17948
|
+
const stampField = clientCredentialStampField(field);
|
|
17949
|
+
const value = await secretStoreGetStrict(secretStore, from, field);
|
|
17950
|
+
if (value === null) continue;
|
|
17951
|
+
const stamp = await secretStoreGetStrict(secretStore, from, stampField);
|
|
17952
|
+
if (stamp === null || stampedOwner(stamp, value) !== clientId) {
|
|
17953
|
+
continue;
|
|
17954
|
+
}
|
|
17955
|
+
if (await secretStoreGetStrict(secretStore, to, field) !== null) {
|
|
17956
|
+
continue;
|
|
17957
|
+
}
|
|
17958
|
+
let byLocation = ledger.get(current);
|
|
17959
|
+
if (!byLocation) {
|
|
17960
|
+
byLocation = /* @__PURE__ */ new Map();
|
|
17961
|
+
ledger.set(current, byLocation);
|
|
17962
|
+
}
|
|
17963
|
+
const target = byLocation.get(location) ?? emptyKeys();
|
|
17964
|
+
byLocation.set(location, target);
|
|
17965
|
+
const isNew = !target.servers.has(url);
|
|
17966
|
+
target.servers.add(url);
|
|
17967
|
+
const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
|
|
17968
|
+
if (mergeOwners(target.owners, owner) || isNew) {
|
|
17969
|
+
await writeLedger(stateFilePath, ledger);
|
|
17970
|
+
}
|
|
17971
|
+
await secretStore.set(to, stampField, stamp);
|
|
17972
|
+
await secretStore.set(to, field, value);
|
|
17973
|
+
} catch (error) {
|
|
17974
|
+
carried = false;
|
|
17975
|
+
warnLedgerFailure(
|
|
17976
|
+
"carry over a superseded namespace recorded in",
|
|
17977
|
+
error
|
|
17978
|
+
);
|
|
17979
|
+
}
|
|
17980
|
+
}
|
|
17981
|
+
}
|
|
17982
|
+
return carried;
|
|
17983
|
+
}
|
|
17476
17984
|
async function recordedNamespaces(stateFilePath) {
|
|
17477
17985
|
try {
|
|
17478
17986
|
const ledger = parseLedger(
|
|
@@ -17484,7 +17992,7 @@ async function recordedNamespaces(stateFilePath) {
|
|
|
17484
17992
|
return [];
|
|
17485
17993
|
}
|
|
17486
17994
|
}
|
|
17487
|
-
async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
17995
|
+
async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
|
|
17488
17996
|
let ledger;
|
|
17489
17997
|
let location;
|
|
17490
17998
|
try {
|
|
@@ -17497,8 +18005,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
17497
18005
|
return;
|
|
17498
18006
|
}
|
|
17499
18007
|
let changed = false;
|
|
17500
|
-
for (const [namespace, byLocation] of ledger) {
|
|
18008
|
+
for (const [namespace, byLocation] of [...ledger].reverse()) {
|
|
17501
18009
|
if (namespace === current) continue;
|
|
18010
|
+
if (current !== void 0 && coveredLocations(location, byLocation).some(
|
|
18011
|
+
({ keys }) => keys.owedTo === current
|
|
18012
|
+
) && !await carryOwed(
|
|
18013
|
+
stateFilePath,
|
|
18014
|
+
ledger,
|
|
18015
|
+
location,
|
|
18016
|
+
secretStore,
|
|
18017
|
+
namespace,
|
|
18018
|
+
current,
|
|
18019
|
+
candidateFields(location, byLocation),
|
|
18020
|
+
owners
|
|
18021
|
+
)) {
|
|
18022
|
+
break;
|
|
18023
|
+
}
|
|
17502
18024
|
for (const { location: recorded, keys } of coveredLocations(
|
|
17503
18025
|
location,
|
|
17504
18026
|
byLocation
|
|
@@ -17523,12 +18045,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
17523
18045
|
if (!purged || recorded !== location) continue;
|
|
17524
18046
|
byLocation.delete(recorded);
|
|
17525
18047
|
if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
|
|
17526
|
-
const keyring = byLocation.get(KEYRING_LOCATION) ??
|
|
17527
|
-
|
|
17528
|
-
idpSessions: /* @__PURE__ */ new Set()
|
|
17529
|
-
};
|
|
18048
|
+
const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
|
|
18049
|
+
addMissingOwners(keyring.owners, keys.owners);
|
|
17530
18050
|
for (const url of keys.servers) keyring.servers.add(url);
|
|
17531
18051
|
for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
|
|
18052
|
+
keyring.owedTo ??= keys.owedTo;
|
|
17532
18053
|
byLocation.set(KEYRING_LOCATION, keyring);
|
|
17533
18054
|
}
|
|
17534
18055
|
changed = true;
|
|
@@ -17639,7 +18160,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17639
18160
|
const existing = parseSecretsNamespace(raw);
|
|
17640
18161
|
if (existing !== void 0) {
|
|
17641
18162
|
if (locked) {
|
|
17642
|
-
await purgeSupersededNamespaces(
|
|
18163
|
+
await purgeSupersededNamespaces(
|
|
18164
|
+
filePath,
|
|
18165
|
+
secretStore,
|
|
18166
|
+
existing,
|
|
18167
|
+
snapshotClientOwners(parseOAuthPersistBlob(raw))
|
|
18168
|
+
);
|
|
17643
18169
|
}
|
|
17644
18170
|
return existing;
|
|
17645
18171
|
}
|
|
@@ -17659,25 +18185,33 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17659
18185
|
`Could not save OAuth state: ${filePath} predates per-state-file secret namespaces, and migrating its secret-store entries needs the file lock, which is unavailable here (see the lock warning above). Migrating without it could lose credentials if two processes migrate at once. Nothing was changed; make the lock directory writable and retry \u2014 or, if you cannot, clear this file's stored OAuth state and re-authorize (clearing does not migrate, and the fresh state file mints its namespace without the lock).`
|
|
17660
18186
|
);
|
|
17661
18187
|
}
|
|
18188
|
+
const values = await readEntrySecrets(
|
|
18189
|
+
snapshot,
|
|
18190
|
+
secretStore,
|
|
18191
|
+
void 0,
|
|
18192
|
+
await recordedNamespaces(filePath)
|
|
18193
|
+
);
|
|
18194
|
+
const owners = snapshotClientOwners(snapshot);
|
|
17662
18195
|
await recordNamespaceKeys(
|
|
17663
18196
|
filePath,
|
|
17664
18197
|
secretStore,
|
|
17665
18198
|
namespace,
|
|
17666
18199
|
Object.keys(snapshot.servers),
|
|
17667
|
-
Object.keys(snapshot.idpSessions)
|
|
18200
|
+
Object.keys(snapshot.idpSessions),
|
|
18201
|
+
writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
|
|
17668
18202
|
);
|
|
18203
|
+
await markUncarriedNamespaces(filePath, secretStore, namespace);
|
|
17669
18204
|
const touched = [];
|
|
17670
18205
|
try {
|
|
17671
|
-
const values = await readEntrySecrets(
|
|
17672
|
-
snapshot,
|
|
17673
|
-
secretStore,
|
|
17674
|
-
void 0,
|
|
17675
|
-
await recordedNamespaces(filePath)
|
|
17676
|
-
);
|
|
17677
18206
|
const copies = [
|
|
18207
|
+
// Restamped: a value read from the legacy ids predates stamps, and a
|
|
18208
|
+
// fallback's stamp may belong to a value read from elsewhere (#2734).
|
|
17678
18209
|
...[...values.servers].map(([url, secrets]) => ({
|
|
17679
18210
|
scopedId: oauthSecretServerId(url, namespace),
|
|
17680
|
-
secrets
|
|
18211
|
+
secrets: restampClientCredentials(
|
|
18212
|
+
getOwnEntry(snapshot.servers, url) ?? {},
|
|
18213
|
+
secrets
|
|
18214
|
+
)
|
|
17681
18215
|
})),
|
|
17682
18216
|
...[...values.idpSessions].map(([issuer, secrets]) => ({
|
|
17683
18217
|
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
@@ -17695,7 +18229,7 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17695
18229
|
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
17696
18230
|
throw error;
|
|
17697
18231
|
}
|
|
17698
|
-
await purgeSupersededNamespaces(filePath, secretStore, namespace);
|
|
18232
|
+
await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
|
|
17699
18233
|
const legacyIds = [
|
|
17700
18234
|
...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
|
|
17701
18235
|
...Object.keys(snapshot.idpSessions).map(
|
|
@@ -17872,7 +18406,22 @@ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secre
|
|
|
17872
18406
|
secretStore,
|
|
17873
18407
|
namespace,
|
|
17874
18408
|
Object.keys(merged.servers),
|
|
17875
|
-
Object.keys(merged.idpSessions)
|
|
18409
|
+
Object.keys(merged.idpSessions),
|
|
18410
|
+
// Only the entries this save writes: another entry's secret in
|
|
18411
|
+
// this store was written for whatever client it was written for,
|
|
18412
|
+
// and relabeling it from the file would misattribute it (#2672).
|
|
18413
|
+
writtenClientOwners(
|
|
18414
|
+
{
|
|
18415
|
+
servers: Object.fromEntries(
|
|
18416
|
+
(effective.servers ?? []).flatMap((url) => {
|
|
18417
|
+
const entry = getOwnEntry(merged.servers, url);
|
|
18418
|
+
return entry === void 0 ? [] : [[url, entry]];
|
|
18419
|
+
})
|
|
18420
|
+
),
|
|
18421
|
+
idpSessions: {}
|
|
18422
|
+
},
|
|
18423
|
+
splitSecretsOf
|
|
18424
|
+
)
|
|
17876
18425
|
);
|
|
17877
18426
|
for (const url of effective.servers ?? []) {
|
|
17878
18427
|
const serverId = oauthSecretServerId(url, namespace);
|
|
@@ -18060,7 +18609,29 @@ function fillMissingSecrets(target, source) {
|
|
|
18060
18609
|
if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
|
|
18061
18610
|
}
|
|
18062
18611
|
}
|
|
18063
|
-
|
|
18612
|
+
function writtenClientOwners(snapshot, secretsOf) {
|
|
18613
|
+
const owners = /* @__PURE__ */ new Map();
|
|
18614
|
+
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
18615
|
+
const secrets = secretsOf(url, state);
|
|
18616
|
+
const fields = new Map(
|
|
18617
|
+
[...clientCredentialOwners(state)].filter(
|
|
18618
|
+
([field]) => Object.hasOwn(secrets, field)
|
|
18619
|
+
)
|
|
18620
|
+
);
|
|
18621
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
18622
|
+
}
|
|
18623
|
+
return owners;
|
|
18624
|
+
}
|
|
18625
|
+
var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
|
|
18626
|
+
function snapshotClientOwners(snapshot) {
|
|
18627
|
+
const owners = /* @__PURE__ */ new Map();
|
|
18628
|
+
for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
|
|
18629
|
+
const fields = clientCredentialOwners(state);
|
|
18630
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
18631
|
+
}
|
|
18632
|
+
return owners;
|
|
18633
|
+
}
|
|
18634
|
+
async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
18064
18635
|
const servers = /* @__PURE__ */ new Map();
|
|
18065
18636
|
const idpSessions = /* @__PURE__ */ new Map();
|
|
18066
18637
|
for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
|
|
@@ -18081,14 +18652,49 @@ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
|
|
|
18081
18652
|
);
|
|
18082
18653
|
}
|
|
18083
18654
|
}
|
|
18655
|
+
const owners = snapshotClientOwners(snapshot);
|
|
18656
|
+
for (const { namespace: from, fields: recorded } of carries) {
|
|
18657
|
+
const requests = [];
|
|
18658
|
+
for (const [url, fields] of recorded) {
|
|
18659
|
+
const target = servers.get(url);
|
|
18660
|
+
if (!target) continue;
|
|
18661
|
+
const wanted = [...fields].filter(
|
|
18662
|
+
(field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
|
|
18663
|
+
);
|
|
18664
|
+
if (wanted.length > 0) {
|
|
18665
|
+
requests.push({
|
|
18666
|
+
serverId: oauthSecretServerId(url, from),
|
|
18667
|
+
fields: wanted.flatMap((field) => [
|
|
18668
|
+
field,
|
|
18669
|
+
clientCredentialStampField(field)
|
|
18670
|
+
])
|
|
18671
|
+
});
|
|
18672
|
+
}
|
|
18673
|
+
}
|
|
18674
|
+
if (requests.length === 0) continue;
|
|
18675
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
18676
|
+
for (const [url, target] of servers) {
|
|
18677
|
+
const found = values[oauthSecretServerId(url, from)] ?? {};
|
|
18678
|
+
for (const [field, clientId] of owners.get(url) ?? []) {
|
|
18679
|
+
const value = Object.hasOwn(found, field) ? found[field] : void 0;
|
|
18680
|
+
const stampField = clientCredentialStampField(field);
|
|
18681
|
+
if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
|
|
18682
|
+
continue;
|
|
18683
|
+
}
|
|
18684
|
+
setOwnEntry(target, field, value);
|
|
18685
|
+
setOwnEntry(target, stampField, found[stampField]);
|
|
18686
|
+
}
|
|
18687
|
+
}
|
|
18688
|
+
}
|
|
18084
18689
|
return { servers, idpSessions };
|
|
18085
18690
|
}
|
|
18086
|
-
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
|
|
18691
|
+
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
18087
18692
|
const values = await readEntrySecrets(
|
|
18088
18693
|
snapshot,
|
|
18089
18694
|
secretStore,
|
|
18090
18695
|
namespace,
|
|
18091
|
-
fallbacks
|
|
18696
|
+
fallbacks,
|
|
18697
|
+
carries
|
|
18092
18698
|
);
|
|
18093
18699
|
return {
|
|
18094
18700
|
servers: Object.fromEntries(
|
|
@@ -18116,7 +18722,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
18116
18722
|
secretStore,
|
|
18117
18723
|
namespace,
|
|
18118
18724
|
Object.keys(fresh.servers),
|
|
18119
|
-
Object.keys(fresh.idpSessions)
|
|
18725
|
+
Object.keys(fresh.idpSessions),
|
|
18726
|
+
writtenClientOwners(fresh, splitSecretsOf)
|
|
18120
18727
|
);
|
|
18121
18728
|
}
|
|
18122
18729
|
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
@@ -18169,7 +18776,8 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
18169
18776
|
}
|
|
18170
18777
|
const namespace = parseSecretsNamespace(raw);
|
|
18171
18778
|
const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
|
|
18172
|
-
|
|
18779
|
+
const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
|
|
18780
|
+
return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
|
|
18173
18781
|
});
|
|
18174
18782
|
}
|
|
18175
18783
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
@@ -18479,7 +19087,11 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18479
19087
|
path: options.callbackListen.pathname,
|
|
18480
19088
|
onCallback: async (params) => {
|
|
18481
19089
|
try {
|
|
18482
|
-
await options.client.completeOAuthFlow(
|
|
19090
|
+
await options.client.completeOAuthFlow(
|
|
19091
|
+
params.code,
|
|
19092
|
+
params.iss,
|
|
19093
|
+
params.state
|
|
19094
|
+
);
|
|
18483
19095
|
flowResolve();
|
|
18484
19096
|
} catch (err) {
|
|
18485
19097
|
flowReject(toRunnerOAuthError(err));
|
|
@@ -18531,7 +19143,10 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18531
19143
|
}
|
|
18532
19144
|
if (options.authChallenge) {
|
|
18533
19145
|
const satisfied = await racingSignals(
|
|
18534
|
-
|
|
19146
|
+
// The token exchange just completed, so only scope is in question.
|
|
19147
|
+
options.client.checkAuthChallengeSatisfied(options.authChallenge, {
|
|
19148
|
+
scopeOnly: true
|
|
19149
|
+
})
|
|
18535
19150
|
);
|
|
18536
19151
|
if (!satisfied) {
|
|
18537
19152
|
return {
|
|
@@ -18584,14 +19199,25 @@ function getTuiLogger() {
|
|
|
18584
19199
|
// ../../core/node/openUrl.ts
|
|
18585
19200
|
import open from "open";
|
|
18586
19201
|
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
19202
|
+
var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
19203
|
+
function toOpenableHref(href) {
|
|
19204
|
+
let parsed;
|
|
19205
|
+
try {
|
|
19206
|
+
parsed = new URL(href);
|
|
19207
|
+
} catch {
|
|
19208
|
+
throw new Error("refusing to open an invalid URL");
|
|
19209
|
+
}
|
|
19210
|
+
if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
|
|
19211
|
+
throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
|
|
19212
|
+
}
|
|
19213
|
+
return parsed.href;
|
|
19214
|
+
}
|
|
19215
|
+
function absorbLateErrors(child) {
|
|
19216
|
+
child.on("error", () => {
|
|
18591
19217
|
});
|
|
18592
19218
|
}
|
|
18593
19219
|
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
18594
|
-
const href = typeof url === "string" ? url : url.href;
|
|
19220
|
+
const href = toOpenableHref(typeof url === "string" ? url : url.href);
|
|
18595
19221
|
let timer;
|
|
18596
19222
|
const timeout = new Promise((_, reject) => {
|
|
18597
19223
|
timer = setTimeout(
|
|
@@ -18600,8 +19226,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
|
18600
19226
|
);
|
|
18601
19227
|
});
|
|
18602
19228
|
try {
|
|
18603
|
-
|
|
18604
|
-
await Promise.race([opened, timeout]);
|
|
19229
|
+
await Promise.race([open(href).then(absorbLateErrors), timeout]);
|
|
18605
19230
|
} finally {
|
|
18606
19231
|
clearTimeout(timer);
|
|
18607
19232
|
}
|
|
@@ -25352,7 +25977,8 @@ var STREAM_STATUS_TEXT = {
|
|
|
25352
25977
|
"never-acknowledged": {
|
|
25353
25978
|
text: "closed without acknowledging (server answered listen with a result)",
|
|
25354
25979
|
color: "red"
|
|
25355
|
-
}
|
|
25980
|
+
},
|
|
25981
|
+
refused: { text: "refused by the server", color: "red" }
|
|
25356
25982
|
};
|
|
25357
25983
|
function SubscriptionsTab({
|
|
25358
25984
|
resources,
|
|
@@ -25441,6 +26067,7 @@ function SubscriptionsTab({
|
|
|
25441
26067
|
scrollViewRef.current?.scrollTo(0);
|
|
25442
26068
|
}, [selectedIndex]);
|
|
25443
26069
|
const streamText = STREAM_STATUS_TEXT[streamState.status];
|
|
26070
|
+
const streamRefusal = streamState.status === "refused" ? streamState.refusal : void 0;
|
|
25444
26071
|
const notHonored = !!selected && selected.subscribed && streamState.active && streamState.status === "acknowledged" && !streamState.honoredUris.includes(selected.resource.uri);
|
|
25445
26072
|
return /* @__PURE__ */ jsxs21(Box33, { flexDirection: "row", width, height, children: [
|
|
25446
26073
|
/* @__PURE__ */ jsxs21(
|
|
@@ -25527,11 +26154,12 @@ function SubscriptionsTab({
|
|
|
25527
26154
|
] }) }),
|
|
25528
26155
|
notHonored && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "yellow", children: "Server did not honor this URI in its listen filter" }) })
|
|
25529
26156
|
] }),
|
|
25530
|
-
streamState.active && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { children: [
|
|
26157
|
+
(streamState.active || streamRefusal) && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { children: [
|
|
25531
26158
|
"Listen stream:",
|
|
25532
26159
|
" ",
|
|
25533
26160
|
/* @__PURE__ */ jsx23(Text30, { color: streamText.color, children: streamText.text })
|
|
25534
26161
|
] }) }),
|
|
26162
|
+
streamRefusal && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "red", children: subscriptionListenRefusalMessage(streamRefusal) }) }),
|
|
25535
26163
|
error && /* @__PURE__ */ jsx23(Box33, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "red", children: error }) }),
|
|
25536
26164
|
/* @__PURE__ */ jsx23(Box33, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { bold: true, children: [
|
|
25537
26165
|
"Updates (",
|