@modelcontextprotocol/inspector 2.2.0 → 2.3.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 +105 -11
- package/clients/cli/build/index.js +1397 -442
- package/clients/tui/build/index.js +1282 -76
- package/clients/tui/build/{undici-HXPKCIY3.js → undici-KNXAVD2F.js} +1225 -370
- package/clients/web/build/index.js +152 -52
- package/clients/web/build/{undici-HXPKCIY3.js → undici-KNXAVD2F.js} +1225 -370
- package/clients/web/dist/assets/{index-BrCIxo3Q.js → index-CwhDusR3.js} +46 -51
- package/clients/web/dist/assets/{prism-light-CWdjx3zH.js → prism-light-DBxbqjnM.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -233,6 +233,60 @@ function cleanRoots(roots) {
|
|
|
233
233
|
function envRecordToPairs(env2) {
|
|
234
234
|
return env2 ? Object.entries(env2).map(([key, value]) => ({ key, value })) : [];
|
|
235
235
|
}
|
|
236
|
+
function envPairsToRecord(pairs) {
|
|
237
|
+
const out = {};
|
|
238
|
+
for (const { key, value } of pairs) {
|
|
239
|
+
if (key.trim() === "") continue;
|
|
240
|
+
out[key] = value;
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
function cleanAuthorizationParams(params) {
|
|
245
|
+
if (params === void 0) return void 0;
|
|
246
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
247
|
+
console.warn(
|
|
248
|
+
"Ignoring `oauth.authorizationParams`: expected an object of string values, got",
|
|
249
|
+
Array.isArray(params) ? "array" : typeof params
|
|
250
|
+
);
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
const out = {};
|
|
254
|
+
for (const [key, value] of Object.entries(params)) {
|
|
255
|
+
if (typeof value !== "string") {
|
|
256
|
+
console.warn(
|
|
257
|
+
`Dropping \`oauth.authorizationParams.${key}\`: expected a string value, got ${typeof value}.`
|
|
258
|
+
);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (key.trim() === "") continue;
|
|
262
|
+
out[key] = value;
|
|
263
|
+
}
|
|
264
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
265
|
+
}
|
|
266
|
+
function oauthAuthorizationParamsFromSettings(settings) {
|
|
267
|
+
if (!settings.oauthAuthorizationParams) return void 0;
|
|
268
|
+
const record = envPairsToRecord(settings.oauthAuthorizationParams);
|
|
269
|
+
return Object.keys(record).length > 0 ? record : void 0;
|
|
270
|
+
}
|
|
271
|
+
function cleanEndpointOverride(value, field) {
|
|
272
|
+
if (value === void 0) return void 0;
|
|
273
|
+
if (typeof value !== "string") {
|
|
274
|
+
console.warn(
|
|
275
|
+
`Ignoring \`oauth.${field}\`: expected a string, got ${typeof value}.`
|
|
276
|
+
);
|
|
277
|
+
return void 0;
|
|
278
|
+
}
|
|
279
|
+
return value.trim() || void 0;
|
|
280
|
+
}
|
|
281
|
+
function oauthEndpointOverridesFromSettings(settings) {
|
|
282
|
+
const authorizationUrl = settings.oauthAuthorizationUrl?.trim();
|
|
283
|
+
const tokenUrl = settings.oauthTokenUrl?.trim();
|
|
284
|
+
if (!authorizationUrl && !tokenUrl) return void 0;
|
|
285
|
+
return {
|
|
286
|
+
...authorizationUrl && { authorizationUrl },
|
|
287
|
+
...tokenUrl && { tokenUrl }
|
|
288
|
+
};
|
|
289
|
+
}
|
|
236
290
|
function storedFieldsToInspectorSettings(stored) {
|
|
237
291
|
const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
|
|
238
292
|
if (!hasAny) return void 0;
|
|
@@ -270,6 +324,19 @@ function storedFieldsToInspectorSettings(stored) {
|
|
|
270
324
|
if (stored.oauth?.clientSecret)
|
|
271
325
|
settings.oauthClientSecret = stored.oauth.clientSecret;
|
|
272
326
|
if (stored.oauth?.scopes) settings.oauthScopes = stored.oauth.scopes;
|
|
327
|
+
const authorizationParams = cleanAuthorizationParams(
|
|
328
|
+
stored.oauth?.authorizationParams
|
|
329
|
+
);
|
|
330
|
+
if (authorizationParams) {
|
|
331
|
+
settings.oauthAuthorizationParams = envRecordToPairs(authorizationParams);
|
|
332
|
+
}
|
|
333
|
+
const authorizationUrl = cleanEndpointOverride(
|
|
334
|
+
stored.oauth?.authorizationUrl,
|
|
335
|
+
"authorizationUrl"
|
|
336
|
+
);
|
|
337
|
+
if (authorizationUrl) settings.oauthAuthorizationUrl = authorizationUrl;
|
|
338
|
+
const tokenUrl = cleanEndpointOverride(stored.oauth?.tokenUrl, "tokenUrl");
|
|
339
|
+
if (tokenUrl) settings.oauthTokenUrl = tokenUrl;
|
|
273
340
|
if (stored.oauth?.onInsufficientScope) {
|
|
274
341
|
settings.oauthOnInsufficientScope = stored.oauth.onInsufficientScope;
|
|
275
342
|
}
|
|
@@ -1369,7 +1436,7 @@ function withProxyDispatcher(baseFetch) {
|
|
|
1369
1436
|
if (readProxyEnv() === void 0) return baseFetch;
|
|
1370
1437
|
let dispatcherPromise;
|
|
1371
1438
|
const getDispatcher = () => {
|
|
1372
|
-
dispatcherPromise ??= import("./undici-
|
|
1439
|
+
dispatcherPromise ??= import("./undici-KNXAVD2F.js").then(
|
|
1373
1440
|
({ EnvHttpProxyAgent }) => new EnvHttpProxyAgent(),
|
|
1374
1441
|
(cause) => {
|
|
1375
1442
|
throw new Error(
|
|
@@ -1713,7 +1780,9 @@ function isOAuthCapableServerConfig(config) {
|
|
|
1713
1780
|
function buildRunnerClientAuthOptions(clientConfig, savedSettings, cliOverrides) {
|
|
1714
1781
|
const activeIdp = getActiveEnterpriseManagedAuthIdp(clientConfig);
|
|
1715
1782
|
const activeCimdUrl = getActiveCimdClientMetadataUrl(clientConfig);
|
|
1716
|
-
const
|
|
1783
|
+
const serverAuthorizationParams = savedSettings ? oauthAuthorizationParamsFromSettings(savedSettings) : void 0;
|
|
1784
|
+
const serverEndpointOverrides = savedSettings ? oauthEndpointOverridesFromSettings(savedSettings) : void 0;
|
|
1785
|
+
const oauthFromServer = savedSettings && (savedSettings.oauthClientId || savedSettings.oauthClientSecret || savedSettings.oauthScopes || serverAuthorizationParams || serverEndpointOverrides || savedSettings.enterpriseManaged) ? {
|
|
1717
1786
|
...savedSettings.oauthClientId && {
|
|
1718
1787
|
clientId: savedSettings.oauthClientId
|
|
1719
1788
|
},
|
|
@@ -1723,6 +1792,10 @@ function buildRunnerClientAuthOptions(clientConfig, savedSettings, cliOverrides)
|
|
|
1723
1792
|
...savedSettings.oauthScopes && {
|
|
1724
1793
|
scope: savedSettings.oauthScopes
|
|
1725
1794
|
},
|
|
1795
|
+
...serverAuthorizationParams && {
|
|
1796
|
+
authorizationParams: serverAuthorizationParams
|
|
1797
|
+
},
|
|
1798
|
+
...serverEndpointOverrides,
|
|
1726
1799
|
...savedSettings.enterpriseManaged && {
|
|
1727
1800
|
enterpriseManaged: true
|
|
1728
1801
|
}
|
|
@@ -2220,7 +2293,10 @@ import {
|
|
|
2220
2293
|
ListToolsResultSchema,
|
|
2221
2294
|
ListResourcesResultSchema,
|
|
2222
2295
|
ListResourceTemplatesResultSchema,
|
|
2223
|
-
ListPromptsResultSchema
|
|
2296
|
+
ListPromptsResultSchema,
|
|
2297
|
+
ResourceSchema,
|
|
2298
|
+
ResourceTemplateSchema,
|
|
2299
|
+
PromptSchema
|
|
2224
2300
|
} from "@modelcontextprotocol/core";
|
|
2225
2301
|
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv";
|
|
2226
2302
|
|
|
@@ -2240,16 +2316,367 @@ function validateToolOutput(provider, tool, result) {
|
|
|
2240
2316
|
}
|
|
2241
2317
|
}
|
|
2242
2318
|
|
|
2319
|
+
// ../../core/mcp/listSalvage.ts
|
|
2320
|
+
import { z as z3 } from "zod/v4";
|
|
2321
|
+
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
2322
|
+
import { ToolSchema } from "@modelcontextprotocol/core";
|
|
2323
|
+
var LIST_MAX_PAGES = 64;
|
|
2324
|
+
function listPaginationExceeded(method) {
|
|
2325
|
+
return new Error(
|
|
2326
|
+
`Salvage walk for ${method} exceeded ${LIST_MAX_PAGES} pages without the server's pagination converging`
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
function isClientDecodeRejection(err) {
|
|
2330
|
+
return SdkError.isInstance(err) && (err.code === SdkErrorCode.InvalidResult || err.code === SdkErrorCode.UnsupportedResultType);
|
|
2331
|
+
}
|
|
2332
|
+
function isSalvageableRejection(err) {
|
|
2333
|
+
return SdkError.isInstance(err) && err.code === SdkErrorCode.InvalidResult;
|
|
2334
|
+
}
|
|
2335
|
+
function lenientListPageSchema(resultSchema, itemsKey) {
|
|
2336
|
+
return resultSchema.extend({ [itemsKey]: z3.array(z3.unknown()) });
|
|
2337
|
+
}
|
|
2338
|
+
function nextCursorOf(page) {
|
|
2339
|
+
if (typeof page !== "object" || page === null) return void 0;
|
|
2340
|
+
const value = page.nextCursor;
|
|
2341
|
+
return typeof value === "string" ? value : void 0;
|
|
2342
|
+
}
|
|
2343
|
+
var ModernResultEnvelopeSchema = z3.looseObject({
|
|
2344
|
+
resultType: z3.literal("complete"),
|
|
2345
|
+
// A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
|
|
2346
|
+
// envelope violation the strict path rejects, and accepting it here on the
|
|
2347
|
+
// strength of an unrelated bad entry would hide it.
|
|
2348
|
+
ttlMs: z3.int().min(0),
|
|
2349
|
+
cacheScope: z3.enum(["public", "private"])
|
|
2350
|
+
});
|
|
2351
|
+
function rawItemsOf(page, itemsKey) {
|
|
2352
|
+
if (typeof page !== "object" || page === null) return void 0;
|
|
2353
|
+
const value = page[itemsKey];
|
|
2354
|
+
return Array.isArray(value) ? value : void 0;
|
|
2355
|
+
}
|
|
2356
|
+
function labelForRawItem(value) {
|
|
2357
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2358
|
+
const record = value;
|
|
2359
|
+
for (const key of ["name", "uriTemplate", "uri", "title"]) {
|
|
2360
|
+
const candidate = record[key];
|
|
2361
|
+
if (typeof candidate === "string" && candidate.length > 0) return candidate;
|
|
2362
|
+
}
|
|
2363
|
+
return void 0;
|
|
2364
|
+
}
|
|
2365
|
+
function describeIssues(error) {
|
|
2366
|
+
const [issue] = error.issues;
|
|
2367
|
+
if (!issue) return "did not match the expected shape";
|
|
2368
|
+
const path3 = issue.path.join(".");
|
|
2369
|
+
const detail = expectedDetail(issue);
|
|
2370
|
+
const message = detail ? `${issue.message} (${detail})` : issue.message;
|
|
2371
|
+
return path3.length > 0 ? `${path3}: ${message}` : message;
|
|
2372
|
+
}
|
|
2373
|
+
function expectedDetail(issue) {
|
|
2374
|
+
if (!("expected" in issue) || typeof issue.expected !== "string") {
|
|
2375
|
+
return void 0;
|
|
2376
|
+
}
|
|
2377
|
+
const detail = `expected ${issue.expected}`;
|
|
2378
|
+
return issue.message.includes(detail) ? void 0 : detail;
|
|
2379
|
+
}
|
|
2380
|
+
function salvageListItems({
|
|
2381
|
+
method,
|
|
2382
|
+
items,
|
|
2383
|
+
schema,
|
|
2384
|
+
startIndex = 0
|
|
2385
|
+
}) {
|
|
2386
|
+
const valid = [];
|
|
2387
|
+
const malformed = [];
|
|
2388
|
+
items.forEach((item, offset) => {
|
|
2389
|
+
const parsed = schema.safeParse(item);
|
|
2390
|
+
if (parsed.success) {
|
|
2391
|
+
valid.push(parsed.data);
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
const label = labelForRawItem(item);
|
|
2395
|
+
malformed.push({
|
|
2396
|
+
method,
|
|
2397
|
+
index: startIndex + offset,
|
|
2398
|
+
...label !== void 0 && { label },
|
|
2399
|
+
reason: describeIssues(parsed.error)
|
|
2400
|
+
});
|
|
2401
|
+
});
|
|
2402
|
+
return { valid, malformed };
|
|
2403
|
+
}
|
|
2404
|
+
function summarizeMalformed(malformed) {
|
|
2405
|
+
const [first] = malformed;
|
|
2406
|
+
if (!first) return "";
|
|
2407
|
+
const where = first.label ?? `index ${first.index}`;
|
|
2408
|
+
const rest = malformed.length > 1 ? ` (+${malformed.length - 1} more)` : "";
|
|
2409
|
+
return `Dropped ${malformed.length} malformed ${malformed.length === 1 ? "entry" : "entries"} \u2014 ${where}: ${first.reason}${rest}`;
|
|
2410
|
+
}
|
|
2411
|
+
function toolItemSchemaForEra(isModern) {
|
|
2412
|
+
return isModern ? eraToolItemSchema(ModernToolWireSchema, stripExecution) : eraToolItemSchema(LegacyToolWireSchema, (tool) => tool);
|
|
2413
|
+
}
|
|
2414
|
+
var LegacyToolWireSchema = ToolSchema.extend({
|
|
2415
|
+
outputSchema: ToolSchema.shape.inputSchema.optional()
|
|
2416
|
+
});
|
|
2417
|
+
var ModernToolWireSchema = ToolSchema.extend({
|
|
2418
|
+
inputSchema: z3.looseObject({
|
|
2419
|
+
type: z3.literal("object"),
|
|
2420
|
+
$schema: z3.string().optional()
|
|
2421
|
+
})
|
|
2422
|
+
});
|
|
2423
|
+
function stripExecution(tool) {
|
|
2424
|
+
if (tool.execution === void 0) return tool;
|
|
2425
|
+
const { execution: _execution, ...rest } = tool;
|
|
2426
|
+
return rest;
|
|
2427
|
+
}
|
|
2428
|
+
function eraToolItemSchema(wire, normalize) {
|
|
2429
|
+
return wire.transform((parsed) => normalize(parsed));
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2243
2432
|
// ../../core/mcp/taskNotificationSchemas.ts
|
|
2244
2433
|
import { NotificationSchema } from "@modelcontextprotocol/core";
|
|
2245
|
-
import { z as
|
|
2434
|
+
import { z as z4 } from "zod/v4";
|
|
2246
2435
|
var TasksListChangedNotificationSchema = NotificationSchema.extend({
|
|
2247
|
-
method:
|
|
2248
|
-
params:
|
|
2436
|
+
method: z4.literal("notifications/tasks/list_changed"),
|
|
2437
|
+
params: z4.record(z4.string(), z4.unknown()).optional()
|
|
2249
2438
|
});
|
|
2250
2439
|
|
|
2251
|
-
// ../../core/mcp/
|
|
2440
|
+
// ../../core/mcp/uriTemplate.ts
|
|
2252
2441
|
import { UriTemplate } from "@modelcontextprotocol/client";
|
|
2442
|
+
var OPERATORS = ["+", "#", ".", "/", ";", "?", "&"];
|
|
2443
|
+
var OMITTABLE_OPERATORS = /* @__PURE__ */ new Set(["#", ".", "/", ";", "?", "&"]);
|
|
2444
|
+
var NAMED_OPERATORS = /* @__PURE__ */ new Set([";", "?", "&"]);
|
|
2445
|
+
var VARCHAR = String.raw`(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})`;
|
|
2446
|
+
var TOLERATED_VARCHAR = String.raw`[-~]`;
|
|
2447
|
+
function varnameGrammar(varchar) {
|
|
2448
|
+
return `${varchar}(?:\\.?${varchar})*`;
|
|
2449
|
+
}
|
|
2450
|
+
var MODIFIER = String.raw`(?:\*|:([1-9][0-9]{0,3}))?`;
|
|
2451
|
+
var CONFORMING_VARSPEC = new RegExp(
|
|
2452
|
+
`^(${varnameGrammar(VARCHAR)})${MODIFIER}$`
|
|
2453
|
+
);
|
|
2454
|
+
var TOLERATED_VARSPEC = new RegExp(
|
|
2455
|
+
`^(${varnameGrammar(`(?:${VARCHAR}|${TOLERATED_VARCHAR})`)})${MODIFIER}$`
|
|
2456
|
+
);
|
|
2457
|
+
function parseVarSpec(raw) {
|
|
2458
|
+
const conforming = CONFORMING_VARSPEC.exec(raw);
|
|
2459
|
+
const match = conforming ?? TOLERATED_VARSPEC.exec(raw);
|
|
2460
|
+
if (match === null) return "invalid";
|
|
2461
|
+
const [, name, maxLength] = match;
|
|
2462
|
+
return {
|
|
2463
|
+
name,
|
|
2464
|
+
...maxLength === void 0 ? {} : { maxLength: +maxLength },
|
|
2465
|
+
...conforming === null ? { conforming: false } : {}
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
function parseUriTemplate(uriTemplate) {
|
|
2469
|
+
const parts = [];
|
|
2470
|
+
let literal = "";
|
|
2471
|
+
let i = 0;
|
|
2472
|
+
while (i < uriTemplate.length) {
|
|
2473
|
+
if (uriTemplate[i] !== "{") {
|
|
2474
|
+
literal += uriTemplate[i];
|
|
2475
|
+
i += 1;
|
|
2476
|
+
continue;
|
|
2477
|
+
}
|
|
2478
|
+
const end = uriTemplate.indexOf("}", i);
|
|
2479
|
+
if (end === -1) {
|
|
2480
|
+
literal += uriTemplate.slice(i);
|
|
2481
|
+
break;
|
|
2482
|
+
}
|
|
2483
|
+
if (literal) {
|
|
2484
|
+
parts.push({ kind: "literal", text: literal });
|
|
2485
|
+
literal = "";
|
|
2486
|
+
}
|
|
2487
|
+
const body = uriTemplate.slice(i + 1, end);
|
|
2488
|
+
const operator = OPERATORS.find((op) => body.startsWith(op)) ?? "";
|
|
2489
|
+
const parsed = body.slice(operator.length).split(",").map(parseVarSpec);
|
|
2490
|
+
const varspecs = parsed.filter(
|
|
2491
|
+
(spec) => spec !== "invalid"
|
|
2492
|
+
);
|
|
2493
|
+
parts.push({
|
|
2494
|
+
kind: "expression",
|
|
2495
|
+
source: uriTemplate.slice(i, end + 1),
|
|
2496
|
+
operator,
|
|
2497
|
+
varspecs,
|
|
2498
|
+
names: varspecs.map((spec) => spec.name),
|
|
2499
|
+
invalid: parsed.includes("invalid")
|
|
2500
|
+
});
|
|
2501
|
+
i = end + 1;
|
|
2502
|
+
}
|
|
2503
|
+
if (literal) parts.push({ kind: "literal", text: literal });
|
|
2504
|
+
return parts;
|
|
2505
|
+
}
|
|
2506
|
+
function templateVariables(uriTemplate) {
|
|
2507
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2508
|
+
for (const part of parseUriTemplate(uriTemplate)) {
|
|
2509
|
+
if (part.kind !== "expression") continue;
|
|
2510
|
+
const required = !OMITTABLE_OPERATORS.has(part.operator);
|
|
2511
|
+
for (const spec of part.varspecs) {
|
|
2512
|
+
const { name } = spec;
|
|
2513
|
+
const conforming = spec.conforming !== false;
|
|
2514
|
+
const existing = byName.get(name);
|
|
2515
|
+
if (existing) {
|
|
2516
|
+
existing.required = existing.required || required;
|
|
2517
|
+
existing.conforming = existing.conforming && conforming;
|
|
2518
|
+
} else {
|
|
2519
|
+
byName.set(name, {
|
|
2520
|
+
name,
|
|
2521
|
+
operator: part.operator,
|
|
2522
|
+
required,
|
|
2523
|
+
conforming
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
return [...byName.values()];
|
|
2529
|
+
}
|
|
2530
|
+
function requiredGroups(uriTemplate) {
|
|
2531
|
+
const groups = [];
|
|
2532
|
+
for (const part of parseUriTemplate(uriTemplate)) {
|
|
2533
|
+
if (part.kind !== "expression") continue;
|
|
2534
|
+
if (OMITTABLE_OPERATORS.has(part.operator)) continue;
|
|
2535
|
+
if (part.names.length === 0) continue;
|
|
2536
|
+
groups.push([...new Set(part.names)]);
|
|
2537
|
+
}
|
|
2538
|
+
return groups;
|
|
2539
|
+
}
|
|
2540
|
+
function unmetRequiredGroups(groups, values) {
|
|
2541
|
+
return groups.filter(
|
|
2542
|
+
(names) => !names.some((name) => (readValue(values, name) ?? "").length > 0)
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
function readValue(values, name) {
|
|
2546
|
+
return Object.hasOwn(values, name) ? values[name] : void 0;
|
|
2547
|
+
}
|
|
2548
|
+
function definedValues(values) {
|
|
2549
|
+
return Object.fromEntries(
|
|
2550
|
+
Object.entries(values).filter(([, value]) => value.length > 0)
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
var ALLOW_RESERVED = /[^A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/gu;
|
|
2554
|
+
function encodeLiteral(text) {
|
|
2555
|
+
return text.split(/([{}])/g).map(
|
|
2556
|
+
(chunk, index) => index % 2 === 1 ? chunk : encodeAllowReserved(chunk)
|
|
2557
|
+
).join("");
|
|
2558
|
+
}
|
|
2559
|
+
function encodeAllowReserved(value) {
|
|
2560
|
+
return value.split(/(%[0-9A-Fa-f]{2})/g).map(
|
|
2561
|
+
(chunk, index) => index % 2 === 1 ? chunk : chunk.replace(ALLOW_RESERVED, (char) => encodeURIComponent(char))
|
|
2562
|
+
).join("");
|
|
2563
|
+
}
|
|
2564
|
+
var FORCE_ENCODED = {
|
|
2565
|
+
"!": "%21",
|
|
2566
|
+
"'": "%27",
|
|
2567
|
+
"(": "%28",
|
|
2568
|
+
")": "%29",
|
|
2569
|
+
"*": "%2A"
|
|
2570
|
+
};
|
|
2571
|
+
function encodeUnreserved(value) {
|
|
2572
|
+
return encodeURIComponent(value).replace(
|
|
2573
|
+
/[!'()*]/g,
|
|
2574
|
+
(char) => FORCE_ENCODED[char]
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
function allowsReserved(operator) {
|
|
2578
|
+
return operator === "+" || operator === "#";
|
|
2579
|
+
}
|
|
2580
|
+
function encodeValue(value, operator) {
|
|
2581
|
+
return allowsReserved(operator) ? encodeAllowReserved(value) : encodeUnreserved(value);
|
|
2582
|
+
}
|
|
2583
|
+
var CONTINUATION = String.raw`%[89AB][0-9A-F]`;
|
|
2584
|
+
var UTF8_SEQUENCE = [
|
|
2585
|
+
String.raw`(?:%F0%(?:9[0-9A-F]|[AB][0-9A-F])|%F[1-3]${CONTINUATION}|%F4%8[0-9A-F])${CONTINUATION}${CONTINUATION}`,
|
|
2586
|
+
String.raw`(?:%E0%[AB][0-9A-F]|%E[1-9ABCEF]${CONTINUATION}|%ED%[89][0-9A-F])${CONTINUATION}`,
|
|
2587
|
+
String.raw`%(?:C[2-9A-F]|D[0-9A-F])${CONTINUATION}`
|
|
2588
|
+
].join("|");
|
|
2589
|
+
var PREFIX_UNIT = new RegExp(`${UTF8_SEQUENCE}|%[0-9A-F]{2}|[\\s\\S]`, "giu");
|
|
2590
|
+
function prefixUnits(value) {
|
|
2591
|
+
return value.match(PREFIX_UNIT) ?? [];
|
|
2592
|
+
}
|
|
2593
|
+
function renderValue(value, spec, operator) {
|
|
2594
|
+
const truncated = spec.maxLength === void 0 ? value : prefixUnits(value).slice(0, spec.maxLength).join("");
|
|
2595
|
+
return encodeValue(truncated, operator);
|
|
2596
|
+
}
|
|
2597
|
+
function expandTemplateExpression(part, values) {
|
|
2598
|
+
const present = part.varspecs.flatMap((spec) => {
|
|
2599
|
+
const value = readValue(values, spec.name);
|
|
2600
|
+
return value === void 0 ? [] : [{ spec, value }];
|
|
2601
|
+
});
|
|
2602
|
+
if (present.length === 0) return "";
|
|
2603
|
+
const { operator } = part;
|
|
2604
|
+
if (NAMED_OPERATORS.has(operator)) {
|
|
2605
|
+
const pairs = present.map(({ spec, value }) => {
|
|
2606
|
+
const rendered2 = renderValue(value, spec, operator);
|
|
2607
|
+
return operator === ";" && rendered2.length === 0 ? spec.name : `${spec.name}=${rendered2}`;
|
|
2608
|
+
});
|
|
2609
|
+
return operator === ";" ? `;${pairs.join(";")}` : `${operator}${pairs.join("&")}`;
|
|
2610
|
+
}
|
|
2611
|
+
const rendered = present.map(
|
|
2612
|
+
({ spec, value }) => renderValue(value, spec, operator)
|
|
2613
|
+
);
|
|
2614
|
+
switch (operator) {
|
|
2615
|
+
case "#":
|
|
2616
|
+
return `#${rendered.join(",")}`;
|
|
2617
|
+
case ".":
|
|
2618
|
+
return `.${rendered.join(".")}`;
|
|
2619
|
+
case "/":
|
|
2620
|
+
return `/${rendered.join("/")}`;
|
|
2621
|
+
// "" and "+" — a bare comma-joined list, no prefix.
|
|
2622
|
+
default:
|
|
2623
|
+
return rendered.join(",");
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
function expandParts(parts, values) {
|
|
2627
|
+
return parts.map(
|
|
2628
|
+
(part) => part.kind === "literal" ? encodeLiteral(part.text) : expandTemplateExpression(part, values)
|
|
2629
|
+
).join("");
|
|
2630
|
+
}
|
|
2631
|
+
function expandUriTemplateStrict(uriTemplate, values) {
|
|
2632
|
+
const sdk = sdkTemplateError(uriTemplate);
|
|
2633
|
+
if (sdk) throw new Error(sdk);
|
|
2634
|
+
const parts = parseUriTemplate(uriTemplate);
|
|
2635
|
+
const invalid = partsError(uriTemplate, parts);
|
|
2636
|
+
if (invalid) throw new Error(invalid);
|
|
2637
|
+
const tooLong = valueLengthError(values, declaredNames(parts));
|
|
2638
|
+
if (tooLong) throw new Error(tooLong);
|
|
2639
|
+
return expandParts(parts, values);
|
|
2640
|
+
}
|
|
2641
|
+
function sdkTemplateError(uriTemplate) {
|
|
2642
|
+
try {
|
|
2643
|
+
new UriTemplate(uriTemplate);
|
|
2644
|
+
return null;
|
|
2645
|
+
} catch (error) {
|
|
2646
|
+
return error instanceof Error ? error.message : String(error);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
function partsError(uriTemplate, parts) {
|
|
2650
|
+
const stray = parts.find(
|
|
2651
|
+
(part) => part.kind === "literal" && /[{}]/.test(part.text)
|
|
2652
|
+
);
|
|
2653
|
+
if (stray) {
|
|
2654
|
+
return `Unmatched brace in "${uriTemplate}" \u2014 an expression must be a complete {\u2026}.`;
|
|
2655
|
+
}
|
|
2656
|
+
const bad = parts.find((part) => part.kind === "expression" && part.invalid);
|
|
2657
|
+
if (bad) {
|
|
2658
|
+
return `Invalid RFC 6570 varspec in "${uriTemplate}": ${bad.kind === "expression" ? bad.source : ""} \u2014 each varspec must name a variable, optionally with an explode (*) or a 1-9999 prefix modifier.`;
|
|
2659
|
+
}
|
|
2660
|
+
return null;
|
|
2661
|
+
}
|
|
2662
|
+
var MAX_VALUE_LENGTH = 1e6;
|
|
2663
|
+
function valueLengthError(values, names) {
|
|
2664
|
+
const considered = names === void 0 ? Object.keys(values) : [...new Set(names)];
|
|
2665
|
+
for (const name of considered) {
|
|
2666
|
+
const value = readValue(values, name);
|
|
2667
|
+
if (value !== void 0 && value.length > MAX_VALUE_LENGTH) {
|
|
2668
|
+
return `The value for "${name}" exceeds the ${MAX_VALUE_LENGTH}-character limit.`;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
return null;
|
|
2672
|
+
}
|
|
2673
|
+
function declaredNames(parts) {
|
|
2674
|
+
return [
|
|
2675
|
+
...new Set(
|
|
2676
|
+
parts.flatMap((part) => part.kind === "expression" ? part.names : [])
|
|
2677
|
+
)
|
|
2678
|
+
];
|
|
2679
|
+
}
|
|
2253
2680
|
|
|
2254
2681
|
// ../../core/mcp/typedEventTarget.ts
|
|
2255
2682
|
var TypedEventGeneric = class extends CustomEvent {
|
|
@@ -2516,6 +2943,136 @@ var ToolCallCancelledError = class extends Error {
|
|
|
2516
2943
|
}
|
|
2517
2944
|
};
|
|
2518
2945
|
|
|
2946
|
+
// ../../core/auth/endpointOverrides.ts
|
|
2947
|
+
var OVERRIDE_FIELDS = {
|
|
2948
|
+
authorizationUrl: "authorization_endpoint",
|
|
2949
|
+
tokenUrl: "token_endpoint"
|
|
2950
|
+
};
|
|
2951
|
+
function oauthEndpointUrlError(value) {
|
|
2952
|
+
const trimmed = value.trim();
|
|
2953
|
+
if (trimmed === "") return void 0;
|
|
2954
|
+
let parsed;
|
|
2955
|
+
try {
|
|
2956
|
+
parsed = new URL(trimmed);
|
|
2957
|
+
} catch {
|
|
2958
|
+
return `"${trimmed}" is not an absolute URL.`;
|
|
2959
|
+
}
|
|
2960
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2961
|
+
return `"${trimmed}" is not an http(s) URL.`;
|
|
2962
|
+
}
|
|
2963
|
+
if (parsed.username || parsed.password) {
|
|
2964
|
+
return "URL must not contain a username or password.";
|
|
2965
|
+
}
|
|
2966
|
+
return void 0;
|
|
2967
|
+
}
|
|
2968
|
+
function normalizeEndpointUrl(value, field) {
|
|
2969
|
+
if (value === void 0) return void 0;
|
|
2970
|
+
const trimmed = value.trim();
|
|
2971
|
+
if (trimmed === "") return void 0;
|
|
2972
|
+
const error = oauthEndpointUrlError(trimmed);
|
|
2973
|
+
if (error) {
|
|
2974
|
+
console.warn(`[oauth] Ignoring \`${field}\`: ${error}`);
|
|
2975
|
+
return void 0;
|
|
2976
|
+
}
|
|
2977
|
+
return trimmed;
|
|
2978
|
+
}
|
|
2979
|
+
function normalizeOAuthEndpointOverrides(overrides) {
|
|
2980
|
+
if (!overrides) return void 0;
|
|
2981
|
+
const authorizationUrl = normalizeEndpointUrl(
|
|
2982
|
+
overrides.authorizationUrl,
|
|
2983
|
+
"authorizationUrl"
|
|
2984
|
+
);
|
|
2985
|
+
const tokenUrl = normalizeEndpointUrl(overrides.tokenUrl, "tokenUrl");
|
|
2986
|
+
if (!authorizationUrl && !tokenUrl) return void 0;
|
|
2987
|
+
return {
|
|
2988
|
+
...authorizationUrl && { authorizationUrl },
|
|
2989
|
+
...tokenUrl && { tokenUrl }
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
function isAuthorizationServerMetadata(value) {
|
|
2993
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2994
|
+
return false;
|
|
2995
|
+
}
|
|
2996
|
+
const doc = value;
|
|
2997
|
+
if (typeof doc.issuer !== "string") return false;
|
|
2998
|
+
return typeof doc.authorization_endpoint === "string" || typeof doc.token_endpoint === "string";
|
|
2999
|
+
}
|
|
3000
|
+
function applyOAuthEndpointOverrides(metadata, overrides) {
|
|
3001
|
+
const patched = { ...metadata };
|
|
3002
|
+
for (const [key, field] of Object.entries(OVERRIDE_FIELDS)) {
|
|
3003
|
+
const override = overrides[key];
|
|
3004
|
+
if (override) patched[field] = override;
|
|
3005
|
+
}
|
|
3006
|
+
return patched;
|
|
3007
|
+
}
|
|
3008
|
+
var AS_METADATA_WELL_KNOWN_PATHS = [
|
|
3009
|
+
"/.well-known/oauth-authorization-server",
|
|
3010
|
+
"/.well-known/openid-configuration"
|
|
3011
|
+
];
|
|
3012
|
+
function requestUrlOf(input) {
|
|
3013
|
+
if (typeof input === "string") return input;
|
|
3014
|
+
if (input instanceof URL) return input.href;
|
|
3015
|
+
return input.url;
|
|
3016
|
+
}
|
|
3017
|
+
function isAuthorizationServerMetadataRequest(input) {
|
|
3018
|
+
const raw = requestUrlOf(input);
|
|
3019
|
+
let pathname;
|
|
3020
|
+
try {
|
|
3021
|
+
pathname = new URL(raw).pathname;
|
|
3022
|
+
} catch {
|
|
3023
|
+
pathname = raw;
|
|
3024
|
+
}
|
|
3025
|
+
return AS_METADATA_WELL_KNOWN_PATHS.some((path3) => pathname.includes(path3));
|
|
3026
|
+
}
|
|
3027
|
+
function isJsonDocumentResponse(contentType) {
|
|
3028
|
+
if (!contentType) return false;
|
|
3029
|
+
const mediaType = contentType.split(";")[0].trim().toLowerCase();
|
|
3030
|
+
return mediaType === "application/json" || mediaType.endsWith("+json");
|
|
3031
|
+
}
|
|
3032
|
+
function responseWithBody(response, body) {
|
|
3033
|
+
const headers = new Headers(response.headers);
|
|
3034
|
+
headers.delete("content-length");
|
|
3035
|
+
headers.delete("content-encoding");
|
|
3036
|
+
return new Response(body, {
|
|
3037
|
+
status: response.status,
|
|
3038
|
+
statusText: response.statusText,
|
|
3039
|
+
headers
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
function withOAuthEndpointOverrides(fetchFn, getOverrides) {
|
|
3043
|
+
let lastKey;
|
|
3044
|
+
let lastNormalized;
|
|
3045
|
+
const resolveOverrides = () => {
|
|
3046
|
+
const raw = getOverrides();
|
|
3047
|
+
const key = JSON.stringify([raw?.authorizationUrl, raw?.tokenUrl]);
|
|
3048
|
+
if (key !== lastKey) {
|
|
3049
|
+
lastKey = key;
|
|
3050
|
+
lastNormalized = normalizeOAuthEndpointOverrides(raw);
|
|
3051
|
+
}
|
|
3052
|
+
return lastNormalized;
|
|
3053
|
+
};
|
|
3054
|
+
return async (input, init) => {
|
|
3055
|
+
const response = await fetchFn(input, init);
|
|
3056
|
+
const overrides = resolveOverrides();
|
|
3057
|
+
if (!overrides) return response;
|
|
3058
|
+
if (!isAuthorizationServerMetadataRequest(input)) return response;
|
|
3059
|
+
if (!response.ok) return response;
|
|
3060
|
+
if (!isJsonDocumentResponse(response.headers.get("content-type")))
|
|
3061
|
+
return response;
|
|
3062
|
+
let parsed;
|
|
3063
|
+
try {
|
|
3064
|
+
parsed = await response.clone().json();
|
|
3065
|
+
} catch {
|
|
3066
|
+
return response;
|
|
3067
|
+
}
|
|
3068
|
+
if (!isAuthorizationServerMetadata(parsed)) return response;
|
|
3069
|
+
return responseWithBody(
|
|
3070
|
+
response,
|
|
3071
|
+
JSON.stringify(applyOAuthEndpointOverrides(parsed, overrides))
|
|
3072
|
+
);
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
|
|
2519
3076
|
// ../../core/logging/logger.ts
|
|
2520
3077
|
var noop = () => {
|
|
2521
3078
|
};
|
|
@@ -2535,6 +3092,41 @@ function createSilentLogger() {
|
|
|
2535
3092
|
}
|
|
2536
3093
|
var silentLogger = createSilentLogger();
|
|
2537
3094
|
|
|
3095
|
+
// ../../core/auth/authorizationParams.ts
|
|
3096
|
+
var RESERVED_AUTHORIZATION_PARAMS = [
|
|
3097
|
+
"client_id",
|
|
3098
|
+
"code_challenge",
|
|
3099
|
+
"code_challenge_method",
|
|
3100
|
+
"redirect_uri",
|
|
3101
|
+
"resource",
|
|
3102
|
+
"response_type",
|
|
3103
|
+
"scope",
|
|
3104
|
+
"state"
|
|
3105
|
+
];
|
|
3106
|
+
var RESERVED = new Set(RESERVED_AUTHORIZATION_PARAMS);
|
|
3107
|
+
function isReservedAuthorizationParam(key) {
|
|
3108
|
+
return RESERVED.has(key.trim().toLowerCase());
|
|
3109
|
+
}
|
|
3110
|
+
function applyAuthorizationParams(authorizationUrl, params) {
|
|
3111
|
+
if (!params) return authorizationUrl;
|
|
3112
|
+
const applicable = Object.entries(params).filter(([key]) => {
|
|
3113
|
+
if (key.trim() === "") return false;
|
|
3114
|
+
if (isReservedAuthorizationParam(key)) {
|
|
3115
|
+
console.warn(
|
|
3116
|
+
`[oauth] Ignoring reserved authorization parameter "${key.trim()}" \u2014 it is set by the authorization flow.`
|
|
3117
|
+
);
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
return true;
|
|
3121
|
+
});
|
|
3122
|
+
if (applicable.length === 0) return authorizationUrl;
|
|
3123
|
+
const merged = new URL(authorizationUrl.href);
|
|
3124
|
+
for (const [key, value] of applicable) {
|
|
3125
|
+
merged.searchParams.set(key.trim(), value);
|
|
3126
|
+
}
|
|
3127
|
+
return merged;
|
|
3128
|
+
}
|
|
3129
|
+
|
|
2538
3130
|
// ../../core/auth/providers.ts
|
|
2539
3131
|
var MutableRedirectUrlProvider = class {
|
|
2540
3132
|
redirectUrl = "";
|
|
@@ -2570,12 +3162,15 @@ var BaseOAuthClientProvider = class {
|
|
|
2570
3162
|
redirectUrlProvider;
|
|
2571
3163
|
navigation;
|
|
2572
3164
|
clientMetadataUrl;
|
|
3165
|
+
/** Custom authorization-request parameters (#2018). Authorize URL only. */
|
|
3166
|
+
authorizationParams;
|
|
2573
3167
|
constructor(serverUrl, oauthConfig) {
|
|
2574
3168
|
this.serverUrl = serverUrl;
|
|
2575
3169
|
this.storage = oauthConfig.storage;
|
|
2576
3170
|
this.redirectUrlProvider = oauthConfig.redirectUrlProvider;
|
|
2577
3171
|
this.navigation = oauthConfig.navigation;
|
|
2578
3172
|
this.clientMetadataUrl = oauthConfig.clientMetadataUrl;
|
|
3173
|
+
this.authorizationParams = oauthConfig.authorizationParams;
|
|
2579
3174
|
}
|
|
2580
3175
|
/**
|
|
2581
3176
|
* Load persisted scope into {@link cachedScope} before SDK `auth()` (which
|
|
@@ -2682,6 +3277,27 @@ var BaseOAuthClientProvider = class {
|
|
|
2682
3277
|
});
|
|
2683
3278
|
}
|
|
2684
3279
|
redirectToAuthorization(authorizationUrl) {
|
|
3280
|
+
this.performAuthorizationRedirect(
|
|
3281
|
+
applyAuthorizationParams(authorizationUrl, this.authorizationParams)
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3284
|
+
/**
|
|
3285
|
+
* Redirect to an authorize URL belonging to a **different** authorization
|
|
3286
|
+
* server than the one this provider was configured for, so the per-server
|
|
3287
|
+
* custom parameters (#2018) MUST NOT be applied.
|
|
3288
|
+
*
|
|
3289
|
+
* The only caller is the enterprise-managed (EMA) transport provider, which
|
|
3290
|
+
* discards the resource AS URL and sends the user to the enterprise IdP
|
|
3291
|
+
* instead. Parameters a user configured for their MCP server's authorization
|
|
3292
|
+
* server — a `kc_idp_hint`, an `audience` — are meaningless and potentially
|
|
3293
|
+
* flow-breaking on the IdP's, and appending them there would leak per-server
|
|
3294
|
+
* config across an authorization-server boundary.
|
|
3295
|
+
*/
|
|
3296
|
+
redirectToExternalAuthorization(authorizationUrl) {
|
|
3297
|
+
this.performAuthorizationRedirect(authorizationUrl);
|
|
3298
|
+
}
|
|
3299
|
+
/** Capture, announce, and navigate to a finished authorize URL. */
|
|
3300
|
+
performAuthorizationRedirect(authorizationUrl) {
|
|
2685
3301
|
this.capturedAuthUrl = authorizationUrl;
|
|
2686
3302
|
if (!this.suppressAuthorizationNavigation) {
|
|
2687
3303
|
if (this.eventTarget) {
|
|
@@ -3549,7 +4165,7 @@ var EmaTransportOAuthProvider = class {
|
|
|
3549
4165
|
async redirectToAuthorization(_authorizationUrl) {
|
|
3550
4166
|
const idpAuthorizationUrl = await startEmaIdpAuthorization(this.emaConfig);
|
|
3551
4167
|
this.inner.clearCapturedAuthUrl();
|
|
3552
|
-
this.inner.
|
|
4168
|
+
this.inner.redirectToExternalAuthorization(idpAuthorizationUrl);
|
|
3553
4169
|
}
|
|
3554
4170
|
saveCodeVerifier(codeVerifier) {
|
|
3555
4171
|
return this.inner.saveCodeVerifier(codeVerifier);
|
|
@@ -3684,6 +4300,30 @@ var OAuthManager = class {
|
|
|
3684
4300
|
getServerUrl() {
|
|
3685
4301
|
return this.params.getServerUrl();
|
|
3686
4302
|
}
|
|
4303
|
+
/**
|
|
4304
|
+
* The per-server authorization/token endpoint overrides, read live so a
|
|
4305
|
+
* `setOAuthConfig` between requests takes effect without rebuilding the
|
|
4306
|
+
* client's fetch. `InspectorClient` reads this from inside its fetch wrapper.
|
|
4307
|
+
* (#1906)
|
|
4308
|
+
*
|
|
4309
|
+
* Returns nothing under enterprise-managed authorization, for the same reason
|
|
4310
|
+
* `redirectToExternalAuthorization` skips the custom authorization parameters
|
|
4311
|
+
* (#2018): the EMA leg authorizes against the enterprise IdP, a *different*
|
|
4312
|
+
* authorization server, and its OIDC discovery runs through this same fetch
|
|
4313
|
+
* (`emaFlow` → `idpOidc`). Rewriting that document would point the IdP login —
|
|
4314
|
+
* or the IdP code/refresh-token exchange — at the resource server's
|
|
4315
|
+
* authorization server, which is both wrong and a way to leak an IdP
|
|
4316
|
+
* credential across an authorization-server boundary.
|
|
4317
|
+
*/
|
|
4318
|
+
getEndpointOverrides() {
|
|
4319
|
+
if (this.isEnterpriseManaged()) return void 0;
|
|
4320
|
+
const { authorizationUrl, tokenUrl } = this.oauthConfig;
|
|
4321
|
+
if (!authorizationUrl && !tokenUrl) return void 0;
|
|
4322
|
+
return {
|
|
4323
|
+
...authorizationUrl && { authorizationUrl },
|
|
4324
|
+
...tokenUrl && { tokenUrl }
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
3687
4327
|
/**
|
|
3688
4328
|
* Return the configured OAuth storage, throwing a clear error if it is
|
|
3689
4329
|
* absent. Used on the EMA re-mint/persist paths, which are only reached for
|
|
@@ -3707,7 +4347,12 @@ var OAuthManager = class {
|
|
|
3707
4347
|
storage: this.oauthConfig.storage,
|
|
3708
4348
|
redirectUrlProvider: this.oauthConfig.redirectUrlProvider,
|
|
3709
4349
|
navigation: this.oauthConfig.navigation,
|
|
3710
|
-
clientMetadataUrl: this.oauthConfig.clientMetadataUrl
|
|
4350
|
+
clientMetadataUrl: this.oauthConfig.clientMetadataUrl,
|
|
4351
|
+
// #2018: per-server custom authorization-request parameters. Carried on
|
|
4352
|
+
// the provider (not in OAuth storage like `scope`) — they are pure config
|
|
4353
|
+
// read from mcp.json, and nothing in the flow needs to persist or union
|
|
4354
|
+
// them.
|
|
4355
|
+
authorizationParams: this.oauthConfig.authorizationParams
|
|
3711
4356
|
});
|
|
3712
4357
|
provider.setEventTarget(this.params.getEventTarget());
|
|
3713
4358
|
await provider.prepareForAuth();
|
|
@@ -4933,6 +5578,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
4933
5578
|
// annotations (SEP-2243), recomputed on every aggregate tools refresh and
|
|
4934
5579
|
// surfaced so the Tools tab can show why a tool vanished (#1632).
|
|
4935
5580
|
excludedTools = [];
|
|
5581
|
+
// Entries the Inspector dropped from a list result because they failed the
|
|
5582
|
+
// spec schema for that primitive, keyed by list method. Populated only by the
|
|
5583
|
+
// salvage fallback in `listAllSalvaging` — empty against a conforming server
|
|
5584
|
+
// (#1909).
|
|
5585
|
+
malformedListItems = /* @__PURE__ */ new Map();
|
|
4936
5586
|
// The capabilities this Inspector client advertises to the server during the
|
|
4937
5587
|
// initialize handshake. Built once in setupClient() and snapshotted here so
|
|
4938
5588
|
// UI surfaces (Server Info modal) can display them without poking at the
|
|
@@ -5111,6 +5761,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
5111
5761
|
resources: options.listChangedNotifications?.resources ?? true,
|
|
5112
5762
|
prompts: options.listChangedNotifications?.prompts ?? true
|
|
5113
5763
|
};
|
|
5764
|
+
this.fetchFn = withOAuthEndpointOverrides(
|
|
5765
|
+
this.fetchFn ?? fetch,
|
|
5766
|
+
() => this.oauthManager?.getEndpointOverrides()
|
|
5767
|
+
);
|
|
5114
5768
|
this.effectiveAuthFetch = this.buildEffectiveAuthFetch();
|
|
5115
5769
|
this.sessionId = options.sessionId;
|
|
5116
5770
|
if (options.oauth || options.environment.oauth) {
|
|
@@ -5138,6 +5792,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
5138
5792
|
});
|
|
5139
5793
|
}
|
|
5140
5794
|
const clientOptions = {
|
|
5795
|
+
// Bound the SDK's auto-aggregate with the SAME constant the #1909 salvage
|
|
5796
|
+
// re-walks use, so the strict path and its fallback cannot drift apart.
|
|
5797
|
+
// This is the SDK's own default today; setting it explicitly is what makes
|
|
5798
|
+
// the shared value a fact rather than a copied guess.
|
|
5799
|
+
listMaxPages: LIST_MAX_PAGES,
|
|
5141
5800
|
// Per-server protocol era (SEP §7.8), threaded from config via
|
|
5142
5801
|
// `eraToVersionNegotiation` and defaulted to `{ mode: "legacy" }` in the
|
|
5143
5802
|
// constructor. "legacy" keeps the wire byte-identical to a 2025 client;
|
|
@@ -5807,6 +6466,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
5807
6466
|
this.cancelledTaskIds.clear();
|
|
5808
6467
|
this.outboundRequestMethods.clear();
|
|
5809
6468
|
this.lastAnsweredRequestByMethod.clear();
|
|
6469
|
+
if (this.malformedListItems.size > 0) {
|
|
6470
|
+
this.malformedListItems.clear();
|
|
6471
|
+
this.dispatchTypedEvent("malformedListItemsChange", []);
|
|
6472
|
+
}
|
|
6473
|
+
if (this.excludedTools.length > 0) {
|
|
6474
|
+
this.excludedTools = [];
|
|
6475
|
+
this.dispatchTypedEvent("excludedToolsChange", []);
|
|
6476
|
+
}
|
|
5810
6477
|
for (const [, controller] of this.taskInputAbortControllers) {
|
|
5811
6478
|
controller.abort(new Error("Connection ended"));
|
|
5812
6479
|
}
|
|
@@ -6030,7 +6697,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6030
6697
|
this.status = "connected";
|
|
6031
6698
|
this.dispatchTypedEvent("statusChange", this.status);
|
|
6032
6699
|
await this.fetchServerInfo();
|
|
6033
|
-
if (this.initialLoggingLevel && this.capabilities?.logging) {
|
|
6700
|
+
if (this.initialLoggingLevel && this.capabilities?.logging && this.protocolEra !== "modern") {
|
|
6034
6701
|
await this.client.setLoggingLevel(
|
|
6035
6702
|
this.initialLoggingLevel,
|
|
6036
6703
|
this.getRequestOptions()
|
|
@@ -6175,6 +6842,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6175
6842
|
this.protocolEra = void 0;
|
|
6176
6843
|
this.discoverResult = void 0;
|
|
6177
6844
|
this.excludedTools = [];
|
|
6845
|
+
this.malformedListItems.clear();
|
|
6178
6846
|
this.modernLogLevel = void 0;
|
|
6179
6847
|
this.dispatchTypedEvent("pendingSamplesChange", this.pendingSamples);
|
|
6180
6848
|
this.dispatchTypedEvent(
|
|
@@ -6188,6 +6856,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6188
6856
|
this.dispatchTypedEvent("protocolEraChange", this.protocolEra);
|
|
6189
6857
|
this.dispatchTypedEvent("discoverResultChange", this.discoverResult);
|
|
6190
6858
|
this.dispatchTypedEvent("excludedToolsChange", this.excludedTools);
|
|
6859
|
+
this.dispatchTypedEvent("malformedListItemsChange", []);
|
|
6191
6860
|
}
|
|
6192
6861
|
/**
|
|
6193
6862
|
* Returns a client proxy for use by AppRenderer / @mcp-ui. Delegates to the
|
|
@@ -6281,7 +6950,6 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6281
6950
|
* `clientCapabilities` carrying the tasks extension). We reproduce it here.
|
|
6282
6951
|
*/
|
|
6283
6952
|
withModernTaskEnvelope(params) {
|
|
6284
|
-
const existingMeta = params._meta ?? {};
|
|
6285
6953
|
const clientCapabilities = {
|
|
6286
6954
|
...this.clientCapabilities,
|
|
6287
6955
|
// Force-stamp the tasks extension regardless of what the client
|
|
@@ -6293,6 +6961,26 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6293
6961
|
[TASKS_EXTENSION_KEY]: {}
|
|
6294
6962
|
}
|
|
6295
6963
|
};
|
|
6964
|
+
return this.withModernEnvelope(params, clientCapabilities);
|
|
6965
|
+
}
|
|
6966
|
+
/**
|
|
6967
|
+
* Stamp the `_meta` envelope every raw-wire request needs on the modern leg:
|
|
6968
|
+
* the negotiated protocol version, the client identity, and the client
|
|
6969
|
+
* capabilities.
|
|
6970
|
+
*
|
|
6971
|
+
* Split out of {@link withModernTaskEnvelope} so the list-salvage re-walk
|
|
6972
|
+
* (#1909) can reuse it WITHOUT that method's force-stamped tasks extension —
|
|
6973
|
+
* a `tools/list` has no business claiming an extension the user may have
|
|
6974
|
+
* deliberately turned off (#1738).
|
|
6975
|
+
*
|
|
6976
|
+
* The NEGOTIATED protocol version is used so the envelope agrees with the
|
|
6977
|
+
* `MCP-Protocol-Version` header the transport stamps from the same source — a
|
|
6978
|
+
* future modern-family revision would negotiate a different string, and the
|
|
6979
|
+
* two must not disagree. The raw channel only runs on a connected modern
|
|
6980
|
+
* session, so this is always set; the constant is a defensive fallback.
|
|
6981
|
+
*/
|
|
6982
|
+
withModernEnvelope(params, clientCapabilities = this.clientCapabilities) {
|
|
6983
|
+
const existingMeta = params._meta ?? {};
|
|
6296
6984
|
const protocolVersion = this.getProtocolVersion() ?? MODERN_PROTOCOL_VERSION2;
|
|
6297
6985
|
return {
|
|
6298
6986
|
...params,
|
|
@@ -6889,7 +7577,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6889
7577
|
const effectiveMeta = this.mergeMeta(metadata);
|
|
6890
7578
|
const params = {
|
|
6891
7579
|
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
6892
|
-
|
|
7580
|
+
// `!== undefined`, not truthiness: "" is a valid cursor. Dropping it asks
|
|
7581
|
+
// for page one again — and `listToolsForScan` pairs this call with a
|
|
7582
|
+
// fallback that already preserves it, so the two legs of one scan page
|
|
7583
|
+
// would otherwise disagree about which page they fetched.
|
|
7584
|
+
...cursor !== void 0 && { cursor }
|
|
6893
7585
|
};
|
|
6894
7586
|
const response = await this.invokeMcpClient(
|
|
6895
7587
|
() => this.client.request(
|
|
@@ -6917,19 +7609,29 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6917
7609
|
if (!this.client) {
|
|
6918
7610
|
throw new Error("Client is not connected");
|
|
6919
7611
|
}
|
|
6920
|
-
const
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
7612
|
+
const tools = await this.listAllSalvaging({
|
|
7613
|
+
method: "tools/list",
|
|
7614
|
+
itemsKey: "tools",
|
|
7615
|
+
resultSchema: ListToolsResultSchema,
|
|
7616
|
+
// Era-aware: the neutral `ToolSchema` is more permissive than the
|
|
7617
|
+
// negotiated era's wire schema — see `toolItemSchemaForEra`.
|
|
7618
|
+
itemSchema: toolItemSchemaForEra(this.isModernEra()),
|
|
7619
|
+
metadata: options?.metadata,
|
|
7620
|
+
finalize: (salvaged) => this.excludeInvalidXMcpHeaderTools(salvaged),
|
|
7621
|
+
aggregate: async () => [
|
|
7622
|
+
...(await this.client.listTools(
|
|
7623
|
+
this.aggregateListParams(options?.metadata),
|
|
7624
|
+
this.getCacheableRequestOptions(options?.cacheMode)
|
|
7625
|
+
)).tools
|
|
7626
|
+
]
|
|
7627
|
+
});
|
|
6926
7628
|
await this.refreshExcludedTools(options?.metadata).catch((err) => {
|
|
6927
7629
|
this.logger.warn(
|
|
6928
7630
|
{ err },
|
|
6929
7631
|
"Excluded-tools walk failed; the SEP-2243 excluded list may be incomplete"
|
|
6930
7632
|
);
|
|
6931
7633
|
});
|
|
6932
|
-
return { tools
|
|
7634
|
+
return { tools };
|
|
6933
7635
|
}
|
|
6934
7636
|
/**
|
|
6935
7637
|
* Whether this connection excludes tools with invalid `x-mcp-header`
|
|
@@ -6960,6 +7662,202 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6960
7662
|
if (id === void 0) return;
|
|
6961
7663
|
this.dispatchTypedEvent("responseRejected", { id, reason });
|
|
6962
7664
|
}
|
|
7665
|
+
/**
|
|
7666
|
+
* Put the method's response correlation back where it was before a salvage
|
|
7667
|
+
* re-walk, so a caller that goes on to mark the failure still marks the
|
|
7668
|
+
* ORIGINAL rejected response.
|
|
7669
|
+
*
|
|
7670
|
+
* The re-walk answers the same method, so on any rethrow path the map points
|
|
7671
|
+
* at a lenient page that succeeded. `ManagedListState.refresh()` catches the
|
|
7672
|
+
* rethrown error and calls `markResponseRejected(method, ...)`, which would
|
|
7673
|
+
* then stamp "Rejected by the Inspector" onto that page and leave the real
|
|
7674
|
+
* one rendering clean — the same misattribution the capture inside
|
|
7675
|
+
* `salvageList` exists to prevent, just one frame further out. Deleting when
|
|
7676
|
+
* nothing had answered before makes the outer mark a no-op rather than a lie.
|
|
7677
|
+
*/
|
|
7678
|
+
restoreResponseCorrelation(method, id) {
|
|
7679
|
+
if (id === void 0) this.lastAnsweredRequestByMethod.delete(method);
|
|
7680
|
+
else this.lastAnsweredRequestByMethod.set(method, id);
|
|
7681
|
+
}
|
|
7682
|
+
/** Every entry dropped from a list result as malformed, across methods (#1909). */
|
|
7683
|
+
getMalformedListItems() {
|
|
7684
|
+
return [...this.malformedListItems.values()].flat();
|
|
7685
|
+
}
|
|
7686
|
+
/**
|
|
7687
|
+
* Record (or clear) the malformed entries for one list method and notify.
|
|
7688
|
+
*
|
|
7689
|
+
* A no-op when the method had none and still has none, so the common case —
|
|
7690
|
+
* every conforming refresh, of which there are many — costs no event and no
|
|
7691
|
+
* re-render.
|
|
7692
|
+
*/
|
|
7693
|
+
setMalformedListItems(method, malformed) {
|
|
7694
|
+
const previous = this.malformedListItems.get(method) ?? [];
|
|
7695
|
+
if (previous.length === 0 && malformed.length === 0) return;
|
|
7696
|
+
if (malformed.length === 0) this.malformedListItems.delete(method);
|
|
7697
|
+
else this.malformedListItems.set(method, malformed);
|
|
7698
|
+
this.dispatchTypedEvent(
|
|
7699
|
+
"malformedListItemsChange",
|
|
7700
|
+
this.getMalformedListItems()
|
|
7701
|
+
);
|
|
7702
|
+
}
|
|
7703
|
+
/**
|
|
7704
|
+
* Run a list aggregate, falling back to a per-item salvage when the SDK
|
|
7705
|
+
* rejects the result it received (#1909).
|
|
7706
|
+
*
|
|
7707
|
+
* The strict aggregate runs first and unchanged, so a conforming server pays
|
|
7708
|
+
* nothing: no extra request, no extra parse, and the SDK's response cache
|
|
7709
|
+
* still serves it. Only an `InvalidResult` rejection — a response in hand
|
|
7710
|
+
* that failed validation — reaches {@link salvageList}; every other failure
|
|
7711
|
+
* (transport drop, timeout, server-sent error) rethrows untouched, because
|
|
7712
|
+
* re-listing would not produce anything to salvage.
|
|
7713
|
+
*/
|
|
7714
|
+
async listAllSalvaging({
|
|
7715
|
+
method,
|
|
7716
|
+
itemsKey,
|
|
7717
|
+
itemSchema,
|
|
7718
|
+
resultSchema,
|
|
7719
|
+
aggregate,
|
|
7720
|
+
metadata,
|
|
7721
|
+
finalize
|
|
7722
|
+
}) {
|
|
7723
|
+
try {
|
|
7724
|
+
const items = await this.invokeMcpClient(aggregate, { method });
|
|
7725
|
+
this.setMalformedListItems(method, []);
|
|
7726
|
+
return items;
|
|
7727
|
+
} catch (err) {
|
|
7728
|
+
if (!isSalvageableRejection(err)) throw err;
|
|
7729
|
+
const salvaged = await this.salvageList({
|
|
7730
|
+
method,
|
|
7731
|
+
itemsKey,
|
|
7732
|
+
itemSchema,
|
|
7733
|
+
resultSchema,
|
|
7734
|
+
metadata,
|
|
7735
|
+
err
|
|
7736
|
+
});
|
|
7737
|
+
return finalize ? finalize(salvaged) : salvaged;
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
/**
|
|
7741
|
+
* Drop the tools the SDK would have excluded from `tools/list` for invalid
|
|
7742
|
+
* `x-mcp-header` annotations (SEP-2243).
|
|
7743
|
+
*
|
|
7744
|
+
* The strict aggregate is filtered by the SDK before we see it, so the
|
|
7745
|
+
* salvage path has to reapply the same rule itself: otherwise one ordinary
|
|
7746
|
+
* malformed tool anywhere in the list would trip the fallback and quietly
|
|
7747
|
+
* readmit every invalid-header tool alongside it — turning a rendering fix
|
|
7748
|
+
* into a spec violation. The dropped tools are still reported through
|
|
7749
|
+
* `refreshExcludedTools`, which lists them with their reason.
|
|
7750
|
+
*/
|
|
7751
|
+
excludeInvalidXMcpHeaderTools(tools) {
|
|
7752
|
+
if (!this.excludesInvalidXMcpHeaderTools()) return tools;
|
|
7753
|
+
return tools.filter(
|
|
7754
|
+
(tool) => scanXMcpHeaderDeclarations(tool.inputSchema).valid
|
|
7755
|
+
);
|
|
7756
|
+
}
|
|
7757
|
+
/**
|
|
7758
|
+
* Re-walk a list method with pagination validated but entries left raw, then
|
|
7759
|
+
* validate each entry on its own — keeping the good ones and reporting the
|
|
7760
|
+
* rest (#1909).
|
|
7761
|
+
*
|
|
7762
|
+
* Two deliberate properties:
|
|
7763
|
+
*
|
|
7764
|
+
* - **The original error wins when nothing is salvageable.** If every entry
|
|
7765
|
+
* validates here, the strict rejection was about something else (a
|
|
7766
|
+
* top-level field, an era codec decision), so there is no per-item story to
|
|
7767
|
+
* tell and swallowing it would hide a real failure. Rethrow the original.
|
|
7768
|
+
* - **A salvaged response is still a rejected response.** The Protocol entry
|
|
7769
|
+
* is marked (#1953) with what was dropped, so the wire truth survives even
|
|
7770
|
+
* though the list now renders.
|
|
7771
|
+
*
|
|
7772
|
+
* This walk is deliberately un-cached (it uses the raw per-page path), which
|
|
7773
|
+
* is fine: it only runs against a server that just returned a non-conforming
|
|
7774
|
+
* result.
|
|
7775
|
+
*/
|
|
7776
|
+
async salvageList({
|
|
7777
|
+
method,
|
|
7778
|
+
itemsKey,
|
|
7779
|
+
itemSchema,
|
|
7780
|
+
resultSchema,
|
|
7781
|
+
metadata,
|
|
7782
|
+
err
|
|
7783
|
+
}) {
|
|
7784
|
+
const pageSchema = lenientListPageSchema(resultSchema, itemsKey);
|
|
7785
|
+
const rejectedResponseId = this.lastAnsweredRequestByMethod.get(method);
|
|
7786
|
+
const valid = [];
|
|
7787
|
+
const malformed = [];
|
|
7788
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
7789
|
+
let cursor;
|
|
7790
|
+
let pages = 0;
|
|
7791
|
+
try {
|
|
7792
|
+
do {
|
|
7793
|
+
if (pages >= LIST_MAX_PAGES) throw listPaginationExceeded(method);
|
|
7794
|
+
pages += 1;
|
|
7795
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
7796
|
+
const params = {
|
|
7797
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
7798
|
+
// `!== undefined`, not truthiness: "" is a valid cursor, and dropping it
|
|
7799
|
+
// would re-request page one and duplicate its entries until the
|
|
7800
|
+
// repeated-cursor guard below stopped the walk.
|
|
7801
|
+
...cursor !== void 0 && { cursor }
|
|
7802
|
+
};
|
|
7803
|
+
const page = await this.invokeMcpClient(
|
|
7804
|
+
() => this.isModernEra() ? this.rawWireRequest(
|
|
7805
|
+
method,
|
|
7806
|
+
this.withModernEnvelope(params),
|
|
7807
|
+
pageSchema
|
|
7808
|
+
) : this.client.request(
|
|
7809
|
+
{ method, params },
|
|
7810
|
+
pageSchema,
|
|
7811
|
+
this.getRequestOptions(metadata?.progressToken)
|
|
7812
|
+
),
|
|
7813
|
+
{ method }
|
|
7814
|
+
);
|
|
7815
|
+
if (this.isModernEra() && !ModernResultEnvelopeSchema.safeParse(page).success) {
|
|
7816
|
+
throw err;
|
|
7817
|
+
}
|
|
7818
|
+
const items = rawItemsOf(page, itemsKey);
|
|
7819
|
+
if (items === void 0) throw err;
|
|
7820
|
+
const salvaged = salvageListItems({
|
|
7821
|
+
method,
|
|
7822
|
+
items,
|
|
7823
|
+
schema: itemSchema,
|
|
7824
|
+
startIndex: valid.length + malformed.length
|
|
7825
|
+
});
|
|
7826
|
+
valid.push(...salvaged.valid);
|
|
7827
|
+
malformed.push(...salvaged.malformed);
|
|
7828
|
+
cursor = nextCursorOf(page);
|
|
7829
|
+
if (cursor !== void 0) {
|
|
7830
|
+
if (seenCursors.has(cursor)) break;
|
|
7831
|
+
seenCursors.add(cursor);
|
|
7832
|
+
}
|
|
7833
|
+
} while (cursor !== void 0);
|
|
7834
|
+
} catch (walkErr) {
|
|
7835
|
+
if (walkErr !== err) {
|
|
7836
|
+
this.logger.warn(
|
|
7837
|
+
{ err: walkErr, method },
|
|
7838
|
+
"Salvage re-walk failed; surfacing the original list error"
|
|
7839
|
+
);
|
|
7840
|
+
}
|
|
7841
|
+
this.restoreResponseCorrelation(method, rejectedResponseId);
|
|
7842
|
+
throw err;
|
|
7843
|
+
}
|
|
7844
|
+
if (malformed.length === 0) {
|
|
7845
|
+
this.restoreResponseCorrelation(method, rejectedResponseId);
|
|
7846
|
+
throw err;
|
|
7847
|
+
}
|
|
7848
|
+
if (rejectedResponseId !== void 0) {
|
|
7849
|
+
this.dispatchTypedEvent("responseRejected", {
|
|
7850
|
+
id: rejectedResponseId,
|
|
7851
|
+
reason: summarizeMalformed(malformed)
|
|
7852
|
+
});
|
|
7853
|
+
}
|
|
7854
|
+
this.setMalformedListItems(method, malformed);
|
|
7855
|
+
this.logger.warn(
|
|
7856
|
+
{ method, malformed },
|
|
7857
|
+
"Dropped malformed entries from a list result; the rest of the list was kept"
|
|
7858
|
+
);
|
|
7859
|
+
return valid;
|
|
7860
|
+
}
|
|
6963
7861
|
/** The current SEP-2243 excluded-tools set (empty on legacy/stdio). */
|
|
6964
7862
|
getExcludedTools() {
|
|
6965
7863
|
return this.excludedTools;
|
|
@@ -6978,8 +7876,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6978
7876
|
if (this.excludesInvalidXMcpHeaderTools()) {
|
|
6979
7877
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
6980
7878
|
let cursor;
|
|
7879
|
+
let pages = 0;
|
|
6981
7880
|
do {
|
|
6982
|
-
|
|
7881
|
+
if (pages >= LIST_MAX_PAGES) throw listPaginationExceeded("tools/list");
|
|
7882
|
+
pages += 1;
|
|
7883
|
+
const page = await this.listToolsForScan(cursor, metadata);
|
|
6983
7884
|
for (const tool of page.tools) {
|
|
6984
7885
|
const scan = scanXMcpHeaderDeclarations(tool.inputSchema);
|
|
6985
7886
|
if (!scan.valid) excluded.push({ tool, reason: scan.reason });
|
|
@@ -6995,6 +7896,90 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
6995
7896
|
this.dispatchTypedEvent("excludedToolsChange", excluded);
|
|
6996
7897
|
return excluded;
|
|
6997
7898
|
}
|
|
7899
|
+
/**
|
|
7900
|
+
* One page of the RAW `tools/list` for the SEP-2243 scan, tolerating a
|
|
7901
|
+
* malformed sibling entry (#1632 + #1909).
|
|
7902
|
+
*
|
|
7903
|
+
* The scan needs the unfiltered list, so it uses the strict single-page call
|
|
7904
|
+
* — but that call rejects the whole page when any entry is non-conforming,
|
|
7905
|
+
* which would blank the excluded set and leave a tool that vanished for an
|
|
7906
|
+
* invalid header with no explanation at all. That is the same failure #1909
|
|
7907
|
+
* is about, one level down. So on a decode rejection the page is re-fetched
|
|
7908
|
+
* leniently and scanned entry by entry.
|
|
7909
|
+
*
|
|
7910
|
+
* The malformed ones are not "excluded tools" and are not returned: they
|
|
7911
|
+
* describe THIS exchange, not the aggregate the Tools panel renders, so they
|
|
7912
|
+
* surface as the rejection reason on this response's own Protocol entry and
|
|
7913
|
+
* go no further (see `refreshExcludedTools`).
|
|
7914
|
+
*/
|
|
7915
|
+
async listToolsForScan(cursor, metadata) {
|
|
7916
|
+
try {
|
|
7917
|
+
return await this.listTools(cursor, metadata);
|
|
7918
|
+
} catch (err) {
|
|
7919
|
+
if (!isSalvageableRejection(err)) throw err;
|
|
7920
|
+
const rejectedResponseId = this.lastAnsweredRequestByMethod.get("tools/list");
|
|
7921
|
+
try {
|
|
7922
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
7923
|
+
const params = {
|
|
7924
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
7925
|
+
// See `salvageList`: "" is a valid cursor.
|
|
7926
|
+
...cursor !== void 0 && { cursor }
|
|
7927
|
+
};
|
|
7928
|
+
const pageSchema = lenientListPageSchema(
|
|
7929
|
+
ListToolsResultSchema,
|
|
7930
|
+
"tools"
|
|
7931
|
+
);
|
|
7932
|
+
const page = await this.invokeMcpClient(
|
|
7933
|
+
() => this.isModernEra() ? this.rawWireRequest(
|
|
7934
|
+
"tools/list",
|
|
7935
|
+
this.withModernEnvelope(params),
|
|
7936
|
+
pageSchema
|
|
7937
|
+
) : this.client.request(
|
|
7938
|
+
{ method: "tools/list", params },
|
|
7939
|
+
pageSchema,
|
|
7940
|
+
this.getRequestOptions(metadata?.progressToken)
|
|
7941
|
+
),
|
|
7942
|
+
{ method: "tools/list" }
|
|
7943
|
+
);
|
|
7944
|
+
if (this.isModernEra() && !ModernResultEnvelopeSchema.safeParse(page).success) {
|
|
7945
|
+
throw err;
|
|
7946
|
+
}
|
|
7947
|
+
const items = rawItemsOf(page, "tools");
|
|
7948
|
+
if (items === void 0) throw err;
|
|
7949
|
+
const { valid, malformed } = salvageListItems({
|
|
7950
|
+
method: "tools/list",
|
|
7951
|
+
items,
|
|
7952
|
+
schema: toolItemSchemaForEra(this.isModernEra())
|
|
7953
|
+
});
|
|
7954
|
+
if (rejectedResponseId !== void 0) {
|
|
7955
|
+
this.dispatchTypedEvent("responseRejected", {
|
|
7956
|
+
id: rejectedResponseId,
|
|
7957
|
+
reason: malformed.length > 0 ? summarizeMalformed(malformed) : err instanceof Error ? err.message : String(err)
|
|
7958
|
+
});
|
|
7959
|
+
}
|
|
7960
|
+
const nextCursor = nextCursorOf(page);
|
|
7961
|
+
return {
|
|
7962
|
+
tools: valid,
|
|
7963
|
+
...nextCursor !== void 0 && { nextCursor }
|
|
7964
|
+
};
|
|
7965
|
+
} catch (fallbackErr) {
|
|
7966
|
+
if (rejectedResponseId !== void 0) {
|
|
7967
|
+
this.dispatchTypedEvent("responseRejected", {
|
|
7968
|
+
id: rejectedResponseId,
|
|
7969
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
7970
|
+
});
|
|
7971
|
+
}
|
|
7972
|
+
if (fallbackErr !== err) {
|
|
7973
|
+
this.logger.warn(
|
|
7974
|
+
{ err: fallbackErr },
|
|
7975
|
+
"Excluded-tools salvage re-fetch failed; surfacing the original list error"
|
|
7976
|
+
);
|
|
7977
|
+
}
|
|
7978
|
+
this.restoreResponseCorrelation("tools/list", rejectedResponseId);
|
|
7979
|
+
throw err;
|
|
7980
|
+
}
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
6998
7983
|
/**
|
|
6999
7984
|
* Call a tool. Caller must provide the Tool (e.g. from a state manager).
|
|
7000
7985
|
* @param tool The tool to call (use tool.name for the request)
|
|
@@ -7781,13 +8766,20 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7781
8766
|
if (!this.client) {
|
|
7782
8767
|
throw new Error("Client is not connected");
|
|
7783
8768
|
}
|
|
7784
|
-
const
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
8769
|
+
const resources = await this.listAllSalvaging({
|
|
8770
|
+
method: "resources/list",
|
|
8771
|
+
itemsKey: "resources",
|
|
8772
|
+
resultSchema: ListResourcesResultSchema,
|
|
8773
|
+
itemSchema: ResourceSchema,
|
|
8774
|
+
metadata: options?.metadata,
|
|
8775
|
+
aggregate: async () => [
|
|
8776
|
+
...(await this.client.listResources(
|
|
8777
|
+
this.aggregateListParams(options?.metadata),
|
|
8778
|
+
this.getCacheableRequestOptions(options?.cacheMode)
|
|
8779
|
+
)).resources
|
|
8780
|
+
]
|
|
8781
|
+
});
|
|
8782
|
+
return { resources };
|
|
7791
8783
|
}
|
|
7792
8784
|
/**
|
|
7793
8785
|
* Read a resource by URI
|
|
@@ -7843,8 +8835,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7843
8835
|
const uriTemplateString = uriTemplate;
|
|
7844
8836
|
let expandedUri;
|
|
7845
8837
|
try {
|
|
7846
|
-
|
|
7847
|
-
expandedUri = uriTemplate2.expand(params);
|
|
8838
|
+
expandedUri = expandUriTemplateStrict(uriTemplateString, params);
|
|
7848
8839
|
} catch (error) {
|
|
7849
8840
|
throw new Error(
|
|
7850
8841
|
`Failed to expand URI template "${uriTemplate}": ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -7906,14 +8897,20 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7906
8897
|
if (!this.client) {
|
|
7907
8898
|
throw new Error("Client is not connected");
|
|
7908
8899
|
}
|
|
7909
|
-
const
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
8900
|
+
const resourceTemplates = await this.listAllSalvaging({
|
|
8901
|
+
method: "resources/templates/list",
|
|
8902
|
+
itemsKey: "resourceTemplates",
|
|
8903
|
+
resultSchema: ListResourceTemplatesResultSchema,
|
|
8904
|
+
itemSchema: ResourceTemplateSchema,
|
|
8905
|
+
metadata: options?.metadata,
|
|
8906
|
+
aggregate: async () => [
|
|
8907
|
+
...(await this.client.listResourceTemplates(
|
|
8908
|
+
this.aggregateListParams(options?.metadata),
|
|
8909
|
+
this.getCacheableRequestOptions(options?.cacheMode)
|
|
8910
|
+
)).resourceTemplates
|
|
8911
|
+
]
|
|
8912
|
+
});
|
|
8913
|
+
return { resourceTemplates };
|
|
7917
8914
|
}
|
|
7918
8915
|
/**
|
|
7919
8916
|
* List available prompts with pagination support
|
|
@@ -7952,13 +8949,20 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7952
8949
|
if (!this.client) {
|
|
7953
8950
|
throw new Error("Client is not connected");
|
|
7954
8951
|
}
|
|
7955
|
-
const
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
8952
|
+
const prompts = await this.listAllSalvaging({
|
|
8953
|
+
method: "prompts/list",
|
|
8954
|
+
itemsKey: "prompts",
|
|
8955
|
+
resultSchema: ListPromptsResultSchema,
|
|
8956
|
+
itemSchema: PromptSchema,
|
|
8957
|
+
metadata: options?.metadata,
|
|
8958
|
+
aggregate: async () => [
|
|
8959
|
+
...(await this.client.listPrompts(
|
|
8960
|
+
this.aggregateListParams(options?.metadata),
|
|
8961
|
+
this.getCacheableRequestOptions(options?.cacheMode)
|
|
8962
|
+
)).prompts
|
|
8963
|
+
]
|
|
8964
|
+
});
|
|
8965
|
+
return { prompts };
|
|
7962
8966
|
}
|
|
7963
8967
|
/**
|
|
7964
8968
|
* Get a prompt by name
|
|
@@ -8874,11 +9878,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
8874
9878
|
};
|
|
8875
9879
|
|
|
8876
9880
|
// ../../core/mcp/state/managedListState.ts
|
|
8877
|
-
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
8878
9881
|
var DEFAULT_LIST_CHANGED_DEBOUNCE_MS = 250;
|
|
8879
|
-
function isClientDecodeRejection(err) {
|
|
8880
|
-
return SdkError.isInstance(err) && (err.code === SdkErrorCode.InvalidResult || err.code === SdkErrorCode.UnsupportedResultType);
|
|
8881
|
-
}
|
|
8882
9882
|
var ManagedListState = class extends TypedEventTarget {
|
|
8883
9883
|
items = [];
|
|
8884
9884
|
client = null;
|
|
@@ -9509,6 +10509,7 @@ function useInspectorClient(inspectorClient) {
|
|
|
9509
10509
|
const [excludedTools, setExcludedTools] = useState(
|
|
9510
10510
|
inspectorClient?.getExcludedTools() ?? []
|
|
9511
10511
|
);
|
|
10512
|
+
const [malformedListItems, setMalformedListItems] = useState(inspectorClient?.getMalformedListItems() ?? []);
|
|
9512
10513
|
const [lastError, setLastError] = useState(void 0);
|
|
9513
10514
|
useEffect(() => {
|
|
9514
10515
|
if (!inspectorClient) {
|
|
@@ -9520,6 +10521,7 @@ function useInspectorClient(inspectorClient) {
|
|
|
9520
10521
|
setProtocolEra(void 0);
|
|
9521
10522
|
setDiscoverResult(void 0);
|
|
9522
10523
|
setExcludedTools([]);
|
|
10524
|
+
setMalformedListItems([]);
|
|
9523
10525
|
setLastError(void 0);
|
|
9524
10526
|
return;
|
|
9525
10527
|
}
|
|
@@ -9531,6 +10533,7 @@ function useInspectorClient(inspectorClient) {
|
|
|
9531
10533
|
setProtocolEra(inspectorClient.getProtocolEra());
|
|
9532
10534
|
setDiscoverResult(inspectorClient.getDiscoverResult());
|
|
9533
10535
|
setExcludedTools(inspectorClient.getExcludedTools());
|
|
10536
|
+
setMalformedListItems(inspectorClient.getMalformedListItems());
|
|
9534
10537
|
setLastError(void 0);
|
|
9535
10538
|
const onStatusChange = (event) => {
|
|
9536
10539
|
setStatus(event.detail);
|
|
@@ -9559,6 +10562,9 @@ function useInspectorClient(inspectorClient) {
|
|
|
9559
10562
|
const onDiscoverResultChange = (event) => {
|
|
9560
10563
|
setDiscoverResult(event.detail);
|
|
9561
10564
|
};
|
|
10565
|
+
const onMalformedListItemsChange = (event) => {
|
|
10566
|
+
setMalformedListItems(event.detail);
|
|
10567
|
+
};
|
|
9562
10568
|
const onExcludedToolsChange = (event) => {
|
|
9563
10569
|
setExcludedTools(event.detail);
|
|
9564
10570
|
};
|
|
@@ -9586,6 +10592,10 @@ function useInspectorClient(inspectorClient) {
|
|
|
9586
10592
|
"excludedToolsChange",
|
|
9587
10593
|
onExcludedToolsChange
|
|
9588
10594
|
);
|
|
10595
|
+
inspectorClient.addEventListener(
|
|
10596
|
+
"malformedListItemsChange",
|
|
10597
|
+
onMalformedListItemsChange
|
|
10598
|
+
);
|
|
9589
10599
|
return () => {
|
|
9590
10600
|
inspectorClient.removeEventListener("statusChange", onStatusChange);
|
|
9591
10601
|
inspectorClient.removeEventListener("error", onError);
|
|
@@ -9617,6 +10627,10 @@ function useInspectorClient(inspectorClient) {
|
|
|
9617
10627
|
"excludedToolsChange",
|
|
9618
10628
|
onExcludedToolsChange
|
|
9619
10629
|
);
|
|
10630
|
+
inspectorClient.removeEventListener(
|
|
10631
|
+
"malformedListItemsChange",
|
|
10632
|
+
onMalformedListItemsChange
|
|
10633
|
+
);
|
|
9620
10634
|
};
|
|
9621
10635
|
}, [inspectorClient]);
|
|
9622
10636
|
const connect = useCallback(async () => {
|
|
@@ -9642,6 +10656,7 @@ function useInspectorClient(inspectorClient) {
|
|
|
9642
10656
|
protocolEra,
|
|
9643
10657
|
discoverResult,
|
|
9644
10658
|
excludedTools,
|
|
10659
|
+
malformedListItems,
|
|
9645
10660
|
lastError,
|
|
9646
10661
|
appRendererClient: inspectorClient?.getAppRendererClient() ?? null,
|
|
9647
10662
|
connect,
|
|
@@ -9652,9 +10667,9 @@ function useInspectorClient(inspectorClient) {
|
|
|
9652
10667
|
// ../../core/react/useManagedTools.ts
|
|
9653
10668
|
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
9654
10669
|
|
|
9655
|
-
// ../../core/react/
|
|
10670
|
+
// ../../core/react/useListError.ts
|
|
9656
10671
|
import { useCallback as useCallback2, useSyncExternalStore } from "react";
|
|
9657
|
-
function
|
|
10672
|
+
function useListError(state) {
|
|
9658
10673
|
const subscribe = useCallback2(
|
|
9659
10674
|
(onStoreChange) => {
|
|
9660
10675
|
if (!state) return () => {
|
|
@@ -9706,7 +10721,7 @@ function useManagedTools(client, managedToolsState) {
|
|
|
9706
10721
|
);
|
|
9707
10722
|
};
|
|
9708
10723
|
}, [managedToolsState]);
|
|
9709
|
-
const error =
|
|
10724
|
+
const error = useListError(managedToolsState);
|
|
9710
10725
|
const refresh = useCallback3(async () => {
|
|
9711
10726
|
if (!managedToolsState || !client) return [];
|
|
9712
10727
|
managedToolsState.clearListChanged();
|
|
@@ -9762,7 +10777,7 @@ function useManagedResources(client, managedResourcesState) {
|
|
|
9762
10777
|
);
|
|
9763
10778
|
};
|
|
9764
10779
|
}, [managedResourcesState]);
|
|
9765
|
-
const error =
|
|
10780
|
+
const error = useListError(managedResourcesState);
|
|
9766
10781
|
const refresh = useCallback4(async () => {
|
|
9767
10782
|
if (!managedResourcesState || !client) return [];
|
|
9768
10783
|
managedResourcesState.clearListChanged();
|
|
@@ -9800,7 +10815,7 @@ function useManagedResourceTemplates(client, managedResourceTemplatesState) {
|
|
|
9800
10815
|
);
|
|
9801
10816
|
};
|
|
9802
10817
|
}, [managedResourceTemplatesState]);
|
|
9803
|
-
const error =
|
|
10818
|
+
const error = useListError(managedResourceTemplatesState);
|
|
9804
10819
|
const refresh = useCallback5(async () => {
|
|
9805
10820
|
if (!managedResourceTemplatesState || !client) return [];
|
|
9806
10821
|
const next = await managedResourceTemplatesState.refresh(
|
|
@@ -9849,7 +10864,7 @@ function useManagedPrompts(client, managedPromptsState) {
|
|
|
9849
10864
|
);
|
|
9850
10865
|
};
|
|
9851
10866
|
}, [managedPromptsState]);
|
|
9852
|
-
const error =
|
|
10867
|
+
const error = useListError(managedPromptsState);
|
|
9853
10868
|
const refresh = useCallback6(async () => {
|
|
9854
10869
|
if (!managedPromptsState || !client) return [];
|
|
9855
10870
|
managedPromptsState.clearListChanged();
|
|
@@ -11796,7 +12811,7 @@ function ResourcesTab({
|
|
|
11796
12811
|
setShouldFetchResource(null);
|
|
11797
12812
|
}
|
|
11798
12813
|
};
|
|
11799
|
-
fetchContent();
|
|
12814
|
+
void fetchContent();
|
|
11800
12815
|
}, [shouldFetchResource, inspectorClient, onAuthRecoveryRequired]);
|
|
11801
12816
|
const listWidth = Math.floor(width * 0.4);
|
|
11802
12817
|
const detailWidth = width - listWidth;
|
|
@@ -11979,7 +12994,7 @@ function PromptsTab({
|
|
|
11979
12994
|
if (selectedPrompt.arguments && selectedPrompt.arguments.length > 0) {
|
|
11980
12995
|
onFetchPrompt(selectedPrompt);
|
|
11981
12996
|
} else {
|
|
11982
|
-
(async () => {
|
|
12997
|
+
void (async () => {
|
|
11983
12998
|
try {
|
|
11984
12999
|
const invocation = await inspectorClient.getPrompt(
|
|
11985
13000
|
selectedPrompt.name
|
|
@@ -14361,6 +15376,161 @@ var Form = (props) => {
|
|
|
14361
15376
|
import { render } from "ink";
|
|
14362
15377
|
import React24 from "react";
|
|
14363
15378
|
|
|
15379
|
+
// ../../core/json/nullableUnion.ts
|
|
15380
|
+
var RENDERABLE_TYPES = [
|
|
15381
|
+
"string",
|
|
15382
|
+
"number",
|
|
15383
|
+
"integer",
|
|
15384
|
+
"boolean",
|
|
15385
|
+
"array",
|
|
15386
|
+
"object"
|
|
15387
|
+
];
|
|
15388
|
+
function isRenderableType(type) {
|
|
15389
|
+
return RENDERABLE_TYPES.includes(type);
|
|
15390
|
+
}
|
|
15391
|
+
function toBranch(value) {
|
|
15392
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
15393
|
+
return null;
|
|
15394
|
+
}
|
|
15395
|
+
return value;
|
|
15396
|
+
}
|
|
15397
|
+
function isStringEnum(value) {
|
|
15398
|
+
return Array.isArray(value) && value.length > 0 && value.every((member) => typeof member === "string");
|
|
15399
|
+
}
|
|
15400
|
+
function nullExcludedBySiblings(schema) {
|
|
15401
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(null)) {
|
|
15402
|
+
return true;
|
|
15403
|
+
}
|
|
15404
|
+
return schema.const !== void 0 && schema.const !== null;
|
|
15405
|
+
}
|
|
15406
|
+
var ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
|
|
15407
|
+
"title",
|
|
15408
|
+
"description",
|
|
15409
|
+
"default",
|
|
15410
|
+
"enumNames",
|
|
15411
|
+
"examples",
|
|
15412
|
+
"deprecated",
|
|
15413
|
+
"readOnly",
|
|
15414
|
+
"writeOnly",
|
|
15415
|
+
"$comment",
|
|
15416
|
+
"$schema",
|
|
15417
|
+
"$id"
|
|
15418
|
+
]);
|
|
15419
|
+
function wrapperCarriesConstraints(schema) {
|
|
15420
|
+
return Object.keys(schema).some(
|
|
15421
|
+
(key) => key !== "anyOf" && !ANNOTATION_KEYWORDS.has(key)
|
|
15422
|
+
);
|
|
15423
|
+
}
|
|
15424
|
+
function typeNamesNull(type) {
|
|
15425
|
+
return Array.isArray(type) ? type.includes("null") : type === "null";
|
|
15426
|
+
}
|
|
15427
|
+
function hasOpaqueApplicator(schema) {
|
|
15428
|
+
return schema.not !== void 0 || schema.allOf !== void 0 || schema.oneOf !== void 0 || // `$ref` points at a schema this module never resolves, and it applies
|
|
15429
|
+
// *alongside* its siblings — `{ type: "string", $ref: "#/$defs/intOnly" }`
|
|
15430
|
+
// can be unsatisfiable. The wrapper path already declines on it (it is not
|
|
15431
|
+
// an annotation); this is the same rule for every other position.
|
|
15432
|
+
schema.$ref !== void 0;
|
|
15433
|
+
}
|
|
15434
|
+
function hasUnevaluatedComposition(schema) {
|
|
15435
|
+
return schema.anyOf !== void 0 || hasOpaqueApplicator(schema);
|
|
15436
|
+
}
|
|
15437
|
+
function stripNullEnumMembers(members, names) {
|
|
15438
|
+
if (!Array.isArray(members)) {
|
|
15439
|
+
return { kind: "unchanged" };
|
|
15440
|
+
}
|
|
15441
|
+
const kept = members.map((member, index) => ({ member, index })).filter((entry) => entry.member !== null);
|
|
15442
|
+
if (kept.length === 0) {
|
|
15443
|
+
return { kind: "unselectable" };
|
|
15444
|
+
}
|
|
15445
|
+
if (kept.length === members.length) {
|
|
15446
|
+
return { kind: "unchanged" };
|
|
15447
|
+
}
|
|
15448
|
+
const wasParallel = Array.isArray(names) && names.length === members.length;
|
|
15449
|
+
const aligned = wasParallel ? kept.map((entry) => names[entry.index]) : void 0;
|
|
15450
|
+
return {
|
|
15451
|
+
kind: "filtered",
|
|
15452
|
+
members: kept.map((entry) => entry.member),
|
|
15453
|
+
names: aligned
|
|
15454
|
+
};
|
|
15455
|
+
}
|
|
15456
|
+
function collapsed(schema, branch, type) {
|
|
15457
|
+
const merged = { ...schema, ...branch };
|
|
15458
|
+
const strip = stripNullEnumMembers(
|
|
15459
|
+
merged.enum,
|
|
15460
|
+
merged.enumNames
|
|
15461
|
+
);
|
|
15462
|
+
if (strip.kind === "unselectable") {
|
|
15463
|
+
return null;
|
|
15464
|
+
}
|
|
15465
|
+
return {
|
|
15466
|
+
...merged,
|
|
15467
|
+
type,
|
|
15468
|
+
anyOf: void 0,
|
|
15469
|
+
// Read off the ORIGINAL schema, not the merge: a hoisted branch `enum` is
|
|
15470
|
+
// not a sibling constraint, and treating it as one would mark the very
|
|
15471
|
+
// shape #1928 is about as non-nullable. See `nullExcludedBySiblings`.
|
|
15472
|
+
nullable: admitsNull(schema),
|
|
15473
|
+
...strip.kind === "filtered" ? { enum: strip.members, enumNames: strip.names } : {}
|
|
15474
|
+
};
|
|
15475
|
+
}
|
|
15476
|
+
function admitsNull(schema) {
|
|
15477
|
+
if (nullExcludedBySiblings(schema)) {
|
|
15478
|
+
return false;
|
|
15479
|
+
}
|
|
15480
|
+
if (hasOpaqueApplicator(schema)) {
|
|
15481
|
+
return false;
|
|
15482
|
+
}
|
|
15483
|
+
if (schema.nullable === true) {
|
|
15484
|
+
return true;
|
|
15485
|
+
}
|
|
15486
|
+
if (schema.type !== void 0) {
|
|
15487
|
+
if (schema.anyOf !== void 0) {
|
|
15488
|
+
return false;
|
|
15489
|
+
}
|
|
15490
|
+
return typeNamesNull(schema.type);
|
|
15491
|
+
}
|
|
15492
|
+
return schema.anyOf?.some((entry) => {
|
|
15493
|
+
const branch = toBranch(entry);
|
|
15494
|
+
if (branch === null || !typeNamesNull(branch.type)) {
|
|
15495
|
+
return false;
|
|
15496
|
+
}
|
|
15497
|
+
const branchSchema = branch;
|
|
15498
|
+
return !nullExcludedBySiblings(branchSchema) && !hasUnevaluatedComposition(branchSchema);
|
|
15499
|
+
}) ?? false;
|
|
15500
|
+
}
|
|
15501
|
+
function normalizeNullableUnion(schema) {
|
|
15502
|
+
if (schema.anyOf?.length === 2) {
|
|
15503
|
+
const branches = schema.anyOf.map(toBranch);
|
|
15504
|
+
const nullBranch = branches.find((entry) => entry?.type === "null");
|
|
15505
|
+
const branch = branches.find(
|
|
15506
|
+
(entry) => entry !== null && entry.type !== "null"
|
|
15507
|
+
);
|
|
15508
|
+
if (nullBranch && branch && !wrapperCarriesConstraints(schema) && !hasUnevaluatedComposition(branch)) {
|
|
15509
|
+
const type = branch.type ?? (isStringEnum(branch.enum) ? "string" : void 0);
|
|
15510
|
+
if (isRenderableType(type)) {
|
|
15511
|
+
const result = collapsed(schema, branch, type);
|
|
15512
|
+
if (result !== null) {
|
|
15513
|
+
return result;
|
|
15514
|
+
}
|
|
15515
|
+
}
|
|
15516
|
+
}
|
|
15517
|
+
}
|
|
15518
|
+
if (Array.isArray(schema.type) && schema.type.length === 2 && schema.type.includes("null") && // This path keeps the schema's own keywords, but `collapsed` clears `anyOf`
|
|
15519
|
+
// and the renderers ignore the other applicators — so a compound schema
|
|
15520
|
+
// like `{ type: ["string", "null"], anyOf: [{ const: "a" }] }` would be
|
|
15521
|
+
// widened into an unconstrained string field. Leave it for the JSON editor.
|
|
15522
|
+
!hasUnevaluatedComposition(schema)) {
|
|
15523
|
+
const type = schema.type.find((member) => member !== "null");
|
|
15524
|
+
if (isRenderableType(type)) {
|
|
15525
|
+
const result = collapsed(schema, void 0, type);
|
|
15526
|
+
if (result !== null) {
|
|
15527
|
+
return result;
|
|
15528
|
+
}
|
|
15529
|
+
}
|
|
15530
|
+
}
|
|
15531
|
+
return schema;
|
|
15532
|
+
}
|
|
15533
|
+
|
|
14364
15534
|
// src/utils/schemaToForm.ts
|
|
14365
15535
|
function toSelectOptions(values, names) {
|
|
14366
15536
|
const useNames = names !== void 0 && names.length === values.length;
|
|
@@ -14380,7 +15550,9 @@ function schemaToForm(schema, toolName) {
|
|
|
14380
15550
|
const properties = schema.properties || {};
|
|
14381
15551
|
const required = schema.required || [];
|
|
14382
15552
|
for (const [key, prop] of Object.entries(properties)) {
|
|
14383
|
-
const property =
|
|
15553
|
+
const property = normalizeNullableUnion(
|
|
15554
|
+
typeof prop === "object" && prop !== null ? prop : {}
|
|
15555
|
+
);
|
|
14384
15556
|
const baseField = {
|
|
14385
15557
|
name: key,
|
|
14386
15558
|
label: property.title || key,
|
|
@@ -14393,11 +15565,11 @@ function schemaToForm(schema, toolName) {
|
|
|
14393
15565
|
...baseField,
|
|
14394
15566
|
options: toSelectOptions(property.items.enum, property.items.enumNames)
|
|
14395
15567
|
};
|
|
14396
|
-
} else if (property.enum) {
|
|
15568
|
+
} else if (isStringEnum(property.enum)) {
|
|
14397
15569
|
field = {
|
|
14398
15570
|
type: "select",
|
|
14399
15571
|
...baseField,
|
|
14400
|
-
options: toSelectOptions(property.enum, property.enumNames)
|
|
15572
|
+
options: toSelectOptions(property.enum ?? [], property.enumNames)
|
|
14401
15573
|
};
|
|
14402
15574
|
} else {
|
|
14403
15575
|
switch (property.type) {
|
|
@@ -14639,20 +15811,18 @@ import { Box as Box24, Text as Text21, useInput as useInput18 } from "ink";
|
|
|
14639
15811
|
// src/utils/uriTemplateToForm.ts
|
|
14640
15812
|
import { UriTemplate as UriTemplate2 } from "@modelcontextprotocol/client";
|
|
14641
15813
|
function uriTemplateToForm(uriTemplate, templateName) {
|
|
14642
|
-
|
|
15814
|
+
let fields = [];
|
|
14643
15815
|
try {
|
|
14644
|
-
|
|
14645
|
-
const
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
fields.push(field);
|
|
14655
|
-
}
|
|
15816
|
+
new UriTemplate2(uriTemplate);
|
|
15817
|
+
const mandatory = new Set(
|
|
15818
|
+
requiredGroups(uriTemplate).filter((names) => names.length === 1).map(([name]) => name)
|
|
15819
|
+
);
|
|
15820
|
+
fields = templateVariables(uriTemplate).map(({ name }) => ({
|
|
15821
|
+
name,
|
|
15822
|
+
label: name,
|
|
15823
|
+
type: "string",
|
|
15824
|
+
required: mandatory.has(name)
|
|
15825
|
+
}));
|
|
14656
15826
|
} catch (error) {
|
|
14657
15827
|
console.error("Failed to parse URI template:", error);
|
|
14658
15828
|
}
|
|
@@ -14749,12 +15919,28 @@ function ResourceTestModal({
|
|
|
14749
15919
|
);
|
|
14750
15920
|
const handleFormSubmit = async (values) => {
|
|
14751
15921
|
if (!inspectorClient || !template) return;
|
|
15922
|
+
const unmetGroups = unmetRequiredGroups(
|
|
15923
|
+
requiredGroups(template.uriTemplate),
|
|
15924
|
+
values
|
|
15925
|
+
);
|
|
15926
|
+
if (unmetGroups.length > 0) {
|
|
15927
|
+
const unmet = unmetGroups.map((names) => names.join(" or "));
|
|
15928
|
+
setResult({
|
|
15929
|
+
input: values,
|
|
15930
|
+
output: null,
|
|
15931
|
+
error: `Missing required template variable(s): ${unmet.join(", ")}`,
|
|
15932
|
+
duration: 0,
|
|
15933
|
+
uri: template.uriTemplate
|
|
15934
|
+
});
|
|
15935
|
+
setState("results");
|
|
15936
|
+
return;
|
|
15937
|
+
}
|
|
14752
15938
|
setState("loading");
|
|
14753
15939
|
const startTime = Date.now();
|
|
14754
15940
|
try {
|
|
14755
15941
|
const invocation = await inspectorClient.readResourceFromTemplate(
|
|
14756
15942
|
template.uriTemplate,
|
|
14757
|
-
values
|
|
15943
|
+
definedValues(values)
|
|
14758
15944
|
);
|
|
14759
15945
|
const duration = Date.now() - startTime;
|
|
14760
15946
|
setResult({
|
|
@@ -15222,6 +16408,8 @@ function App({
|
|
|
15222
16408
|
pendingStepUpRef.current = pendingStepUp;
|
|
15223
16409
|
}, [pendingStepUp]);
|
|
15224
16410
|
const [connectError, setConnectError] = useState23(null);
|
|
16411
|
+
const disconnectAttemptRef = useRef13(0);
|
|
16412
|
+
const [disconnectError, setDisconnectError] = useState23(null);
|
|
15225
16413
|
const oauthInProgressRef = useRef13(false);
|
|
15226
16414
|
const callbackServerRef = useRef13(null);
|
|
15227
16415
|
const selectedServerRef = useRef13(null);
|
|
@@ -15409,6 +16597,8 @@ function App({
|
|
|
15409
16597
|
useEffect20(() => {
|
|
15410
16598
|
setOauthStatus("idle");
|
|
15411
16599
|
setOauthMessage(null);
|
|
16600
|
+
disconnectAttemptRef.current++;
|
|
16601
|
+
setDisconnectError(null);
|
|
15412
16602
|
const stepUp = pendingStepUpRef.current;
|
|
15413
16603
|
if (stepUp && selectedServer && stepUp.serverName !== selectedServer) {
|
|
15414
16604
|
setPendingStepUp(null);
|
|
@@ -15653,6 +16843,8 @@ function App({
|
|
|
15653
16843
|
if (!selectedServer || !selectedInspectorClient || !selectedServerConfig) {
|
|
15654
16844
|
return;
|
|
15655
16845
|
}
|
|
16846
|
+
disconnectAttemptRef.current++;
|
|
16847
|
+
setDisconnectError(null);
|
|
15656
16848
|
const finishConnect = async () => {
|
|
15657
16849
|
await connectInspector();
|
|
15658
16850
|
setConnectError(null);
|
|
@@ -15760,7 +16952,17 @@ function App({
|
|
|
15760
16952
|
}, [inspectorClients, handleAuthRecoveryRequired]);
|
|
15761
16953
|
const handleDisconnect = useCallback11(async () => {
|
|
15762
16954
|
if (!selectedServer) return;
|
|
15763
|
-
|
|
16955
|
+
const attempt = ++disconnectAttemptRef.current;
|
|
16956
|
+
const attemptServer = selectedServer;
|
|
16957
|
+
setDisconnectError(null);
|
|
16958
|
+
try {
|
|
16959
|
+
await disconnectInspector();
|
|
16960
|
+
} catch (err) {
|
|
16961
|
+
if (disconnectAttemptRef.current !== attempt || selectedServerRef.current !== attemptServer) {
|
|
16962
|
+
return;
|
|
16963
|
+
}
|
|
16964
|
+
setDisconnectError(err instanceof Error ? err.message : String(err));
|
|
16965
|
+
}
|
|
15764
16966
|
}, [selectedServer, disconnectInspector]);
|
|
15765
16967
|
const handleClearOAuth = useCallback11(async () => {
|
|
15766
16968
|
if (!selectedInspectorClient) return;
|
|
@@ -16121,9 +17323,9 @@ function App({
|
|
|
16121
17323
|
}
|
|
16122
17324
|
if (selectedServer) {
|
|
16123
17325
|
if (input.toLowerCase() === "c" && (inspectorStatus === "disconnected" || inspectorStatus === "error")) {
|
|
16124
|
-
handleConnect();
|
|
17326
|
+
void handleConnect();
|
|
16125
17327
|
} else if (input.toLowerCase() === "d" && (inspectorStatus === "connected" || inspectorStatus === "connecting")) {
|
|
16126
|
-
handleDisconnect();
|
|
17328
|
+
void handleDisconnect();
|
|
16127
17329
|
}
|
|
16128
17330
|
}
|
|
16129
17331
|
});
|
|
@@ -16294,6 +17496,10 @@ function App({
|
|
|
16294
17496
|
oauthStatus === "error" && oauthMessage && /* @__PURE__ */ jsx16(Box27, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text24, { color: "red", children: [
|
|
16295
17497
|
"OAuth: ",
|
|
16296
17498
|
oauthMessage
|
|
17499
|
+
] }) }),
|
|
17500
|
+
disconnectError && /* @__PURE__ */ jsx16(Box27, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text24, { color: "red", children: [
|
|
17501
|
+
"Disconnect failed: ",
|
|
17502
|
+
disconnectError
|
|
16297
17503
|
] }) })
|
|
16298
17504
|
] })
|
|
16299
17505
|
}
|