@modelcontextprotocol/inspector 2.6.0 → 2.7.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.
@@ -91,6 +91,7 @@ async function deleteStoreFile(filePath) {
91
91
 
92
92
  // ../../core/mcp/types.ts
93
93
  var DEFAULT_TASK_TTL_MS = 6e4;
94
+ var DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
94
95
  var DEFAULT_MAX_FETCH_REQUESTS = 1e3;
95
96
  var DEFAULT_PROTOCOL_ERA = "legacy";
96
97
  var DEFAULT_MODERN_LOG_LEVEL = "debug";
@@ -109,6 +110,139 @@ function isModernLogLevel(value) {
109
110
  return typeof value === "string" && MODERN_LOG_LEVELS.includes(value);
110
111
  }
111
112
 
113
+ // ../../core/mcp/skillsSchemas.ts
114
+ import { z } from "zod/v4";
115
+ import { ResourceSchema } from "@modelcontextprotocol/core";
116
+ var DYNAMIC_RESOURCES = "dynamic";
117
+ var SkillFrontmatterSchema = z.looseObject({
118
+ name: z.string().optional(),
119
+ description: z.string().optional()
120
+ });
121
+ var SkillResourceSchema = z.looseObject({
122
+ uri: z.string(),
123
+ digest: z.string().optional(),
124
+ size: z.number().optional()
125
+ });
126
+ var SkillEntrySchema = z.looseObject({
127
+ uri: z.string(),
128
+ frontmatter: SkillFrontmatterSchema,
129
+ resources: z.union([
130
+ z.literal(DYNAMIC_RESOURCES),
131
+ z.array(SkillResourceSchema)
132
+ ])
133
+ });
134
+ var ListSkillsResultSchema = z.looseObject({
135
+ skills: z.array(SkillEntrySchema),
136
+ nextCursor: z.string().optional()
137
+ });
138
+ var ModernListSkillsResultSchema = ListSkillsResultSchema.extend({
139
+ // A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
140
+ // envelope violation, and accepting it here would hide exactly the kind of
141
+ // defect this schema exists to surface.
142
+ ttlMs: z.int().min(0),
143
+ cacheScope: z.enum(["public", "private"])
144
+ });
145
+ var GetSkillEnvelopeSchema = z.looseObject({
146
+ skill: SkillEntrySchema
147
+ });
148
+ var GetSkillResultSchema = GetSkillEnvelopeSchema.transform(
149
+ (result) => result.skill
150
+ );
151
+ var DirectoryChildSchema = ResourceSchema;
152
+ var DirectoryReadResultSchema = z.looseObject({
153
+ resources: z.array(DirectoryChildSchema),
154
+ nextCursor: z.string().optional()
155
+ });
156
+
157
+ // ../../core/mcp/sha256.ts
158
+ var K = new Uint32Array([
159
+ 1116352408,
160
+ 1899447441,
161
+ 3049323471,
162
+ 3921009573,
163
+ 961987163,
164
+ 1508970993,
165
+ 2453635748,
166
+ 2870763221,
167
+ 3624381080,
168
+ 310598401,
169
+ 607225278,
170
+ 1426881987,
171
+ 1925078388,
172
+ 2162078206,
173
+ 2614888103,
174
+ 3248222580,
175
+ 3835390401,
176
+ 4022224774,
177
+ 264347078,
178
+ 604807628,
179
+ 770255983,
180
+ 1249150122,
181
+ 1555081692,
182
+ 1996064986,
183
+ 2554220882,
184
+ 2821834349,
185
+ 2952996808,
186
+ 3210313671,
187
+ 3336571891,
188
+ 3584528711,
189
+ 113926993,
190
+ 338241895,
191
+ 666307205,
192
+ 773529912,
193
+ 1294757372,
194
+ 1396182291,
195
+ 1695183700,
196
+ 1986661051,
197
+ 2177026350,
198
+ 2456956037,
199
+ 2730485921,
200
+ 2820302411,
201
+ 3259730800,
202
+ 3345764771,
203
+ 3516065817,
204
+ 3600352804,
205
+ 4094571909,
206
+ 275423344,
207
+ 430227734,
208
+ 506948616,
209
+ 659060556,
210
+ 883997877,
211
+ 958139571,
212
+ 1322822218,
213
+ 1537002063,
214
+ 1747873779,
215
+ 1955562222,
216
+ 2024104815,
217
+ 2227730452,
218
+ 2361852424,
219
+ 2428436474,
220
+ 2756734187,
221
+ 3204031479,
222
+ 3329325298
223
+ ]);
224
+ var H0 = new Uint32Array([
225
+ 1779033703,
226
+ 3144134277,
227
+ 1013904242,
228
+ 2773480762,
229
+ 1359893119,
230
+ 2600822924,
231
+ 528734635,
232
+ 1541459225
233
+ ]);
234
+
235
+ // ../../core/mcp/skillFile.ts
236
+ import { parse as parseYaml } from "yaml";
237
+
238
+ // ../../core/mcp/skills.ts
239
+ var SKILL_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
240
+ var SKILL_MAX_CATALOG_SKILLS = 256;
241
+ var SKILL_MAX_CATALOG_BYTES = 64 * 1024 * 1024;
242
+ function isSkillCatalogLimit(value) {
243
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
244
+ }
245
+
112
246
  // ../../core/auth/secret-fields.ts
113
247
  var SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
114
248
  var SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
@@ -290,16 +424,19 @@ function cleanEndpointOverride(value, field) {
290
424
  return value.trim() || void 0;
291
425
  }
292
426
  function storedFieldsToInspectorSettings(stored) {
293
- 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;
427
+ 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.skillCatalogMaxSkills !== void 0 || stored.skillCatalogMaxBytes !== 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;
294
428
  if (!hasAny) return void 0;
295
429
  const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
296
430
  const settings = {
297
431
  headers: headersPairs,
298
432
  env: envRecordToPairs(stored.env),
299
433
  metadata: normalizeStoredMetadata(stored.metadata),
300
- connectionTimeout: stored.connectionTimeout ?? 0,
434
+ // Concrete product default, like taskTtl below: an absent field reads back
435
+ // as 30 s so the form shows the bound that will actually apply. An explicit
436
+ // 0 is preserved as-is — it is the user's opt-out, not a sentinel (#2320).
437
+ connectionTimeout: stored.connectionTimeout ?? DEFAULT_CONNECTION_TIMEOUT_MS,
301
438
  requestTimeout: stored.requestTimeout ?? 0,
302
- // Unlike the timeouts (0 = "SDK default"), task TTL has a concrete product
439
+ // Unlike requestTimeout (0 = "SDK default"), task TTL has a concrete product
303
440
  // default so the form shows it and "Run as task" has a value to send.
304
441
  taskTtl: stored.taskTtl ?? DEFAULT_TASK_TTL_MS,
305
442
  autoRefreshOnListChanged: stored.autoRefreshOnListChanged ?? false,
@@ -313,6 +450,12 @@ function storedFieldsToInspectorSettings(stored) {
313
450
  // `[]`, which `inspectorSettingsToStoredFields` then omits on write.
314
451
  roots: stored.roots ?? []
315
452
  };
453
+ if (isSkillCatalogLimit(stored.skillCatalogMaxSkills)) {
454
+ settings.skillCatalogMaxSkills = stored.skillCatalogMaxSkills;
455
+ }
456
+ if (isSkillCatalogLimit(stored.skillCatalogMaxBytes)) {
457
+ settings.skillCatalogMaxBytes = stored.skillCatalogMaxBytes;
458
+ }
316
459
  if (isProtocolEra(stored.protocolEra)) {
317
460
  settings.protocolEra = stored.protocolEra;
318
461
  }
@@ -367,7 +510,7 @@ function inspectorSettingsToStoredFields(settings) {
367
510
  if (Object.keys(settings.metadata).length > 0) {
368
511
  out.metadata = settings.metadata;
369
512
  }
370
- if (settings.connectionTimeout > 0) {
513
+ if (settings.connectionTimeout !== DEFAULT_CONNECTION_TIMEOUT_MS) {
371
514
  out.connectionTimeout = settings.connectionTimeout;
372
515
  }
373
516
  if (settings.requestTimeout > 0) {
@@ -394,6 +537,12 @@ function inspectorSettingsToStoredFields(settings) {
394
537
  if (settings.maxFetchRequests !== DEFAULT_MAX_FETCH_REQUESTS) {
395
538
  out.maxFetchRequests = settings.maxFetchRequests;
396
539
  }
540
+ if (isSkillCatalogLimit(settings.skillCatalogMaxSkills) && settings.skillCatalogMaxSkills !== SKILL_MAX_CATALOG_SKILLS) {
541
+ out.skillCatalogMaxSkills = settings.skillCatalogMaxSkills;
542
+ }
543
+ if (isSkillCatalogLimit(settings.skillCatalogMaxBytes) && settings.skillCatalogMaxBytes !== SKILL_MAX_CATALOG_BYTES) {
544
+ out.skillCatalogMaxBytes = settings.skillCatalogMaxBytes;
545
+ }
397
546
  const oauthFields = {};
398
547
  if (settings.oauthClientId) oauthFields.clientId = settings.oauthClientId;
399
548
  if (settings.oauthClientSecret)
@@ -444,6 +593,8 @@ var INSPECTOR_FIELD_KEY_MAP = {
444
593
  paginatedLists: true,
445
594
  advertisedExtensions: true,
446
595
  maxFetchRequests: true,
596
+ skillCatalogMaxSkills: true,
597
+ skillCatalogMaxBytes: true,
447
598
  oauth: true,
448
599
  roots: true
449
600
  };
@@ -565,6 +716,14 @@ function parseHeaderPair(value, previous = {}) {
565
716
  }
566
717
  return { ...previous, [key]: val };
567
718
  }
719
+ function parseProtocolEra(value) {
720
+ if (!isProtocolEra(value)) {
721
+ throw new Error(
722
+ `Invalid protocol era: ${value}. Valid eras are: legacy, auto, modern`
723
+ );
724
+ }
725
+ return value;
726
+ }
568
727
  var EMPTY_CATALOG_CONTENT = `${JSON.stringify({ mcpServers: {} }, null, 2)}
569
728
  `;
570
729
  function seedEmptyCatalog(resolvedPath) {
@@ -924,10 +1083,25 @@ function sandboxFrameAncestors(allowedOrigins) {
924
1083
  return frameAncestorsDirective(allowedOrigins);
925
1084
  }
926
1085
  function frameAncestorsDirective(origins) {
927
- const valid = (origins ?? []).filter((o) => CSP_HOST_SOURCE.test(o));
1086
+ const valid = validHostSources(origins);
928
1087
  const sources = valid.length > 0 ? valid : LOOPBACK_FRAME_ANCESTORS;
929
1088
  return `frame-ancestors ${sources.join(" ")}`;
930
1089
  }
1090
+ function validHostSources(origins) {
1091
+ return (origins ?? []).filter((o) => CSP_HOST_SOURCE.test(o));
1092
+ }
1093
+ var EMBEDDER_ORIGINS_PLACEHOLDER = "/*INSPECTOR_EMBEDDER_ORIGINS*/ null";
1094
+ function embedderOriginsLiteral(origins) {
1095
+ const valid = validHostSources(origins);
1096
+ if (valid.length === 0) return "null";
1097
+ return JSON.stringify(valid).replace(/</g, "\\u003c");
1098
+ }
1099
+ function renderSandboxProxyHtml(html, origins) {
1100
+ return html.replace(
1101
+ EMBEDDER_ORIGINS_PLACEHOLDER,
1102
+ () => embedderOriginsLiteral(origins)
1103
+ );
1104
+ }
931
1105
  function parseListenPort(raw) {
932
1106
  const v = raw?.trim();
933
1107
  if (!v || !/^\d+$/.test(v)) return void 0;
@@ -945,22 +1119,25 @@ function resolveSandboxPort() {
945
1119
  return fromSandbox ?? parseListenPort(process.env.SERVER_PORT) ?? DEFAULT_SANDBOX_PORT;
946
1120
  }
947
1121
  function createSandboxController(options) {
948
- const { port, host = DEFAULT_BIND_HOST, allowedOrigins } = options;
1122
+ const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl } = options;
949
1123
  let server = null;
950
1124
  let sandboxUrl = null;
1125
+ let boundPort = 0;
951
1126
  const SANDBOX_PROXY_CSP = sandboxFrameAncestors(allowedOrigins);
952
1127
  let sandboxHtml;
953
1128
  try {
954
1129
  const sandboxHtmlPath = join3(__dirname, "../static/sandbox_proxy.html");
955
- sandboxHtml = readFileSync3(sandboxHtmlPath, "utf-8");
1130
+ sandboxHtml = renderSandboxProxyHtml(
1131
+ readFileSync3(sandboxHtmlPath, "utf-8"),
1132
+ allowedOrigins
1133
+ );
956
1134
  } catch (e) {
957
1135
  sandboxHtml = "<!DOCTYPE html><html><body>Sandbox not loaded: " + String(e.message) + "</body></html>";
958
1136
  }
959
1137
  return {
960
1138
  async start() {
961
1139
  if (server && sandboxUrl) {
962
- const p = parseInt(new URL(sandboxUrl).port, 10);
963
- return { port: p, url: sandboxUrl };
1140
+ return { port: boundPort, url: sandboxUrl };
964
1141
  }
965
1142
  return new Promise((resolve9) => {
966
1143
  let settled = false;
@@ -1014,7 +1191,15 @@ function createSandboxController(options) {
1014
1191
  );
1015
1192
  const canonicalHost = canonicalUrlHost(host);
1016
1193
  const urlHost = isAllInterfacesHost(canonicalHost) ? "localhost" : canonicalHost;
1017
- sandboxUrl = `http://${urlHost}:${actualPort}/sandbox`;
1194
+ const derivedUrl = `http://${urlHost}:${actualPort}/sandbox`;
1195
+ const onFixedPort = port !== 0 && !retriedDynamic;
1196
+ if (publicUrl && !onFixedPort) {
1197
+ console.warn(
1198
+ `Sandbox: not advertising MCP_SANDBOX_FULL_ADDRESS, since the sandbox is not on a fixed port for it to route to; advertising ${derivedUrl} instead.`
1199
+ );
1200
+ }
1201
+ sandboxUrl = publicUrl && onFixedPort ? publicUrl : derivedUrl;
1202
+ boundPort = actualPort;
1018
1203
  settle({ port: actualPort, url: sandboxUrl });
1019
1204
  });
1020
1205
  });
@@ -1075,9 +1260,15 @@ function parseDocumentId(url) {
1075
1260
  return /^[0-9a-f]{32}$/.test(id ?? "") ? id ?? null : null;
1076
1261
  }
1077
1262
  function createAppOriginController(options) {
1078
- const { port, host = DEFAULT_BIND_HOST, embedderOrigins } = options;
1263
+ const {
1264
+ port,
1265
+ host = DEFAULT_BIND_HOST,
1266
+ embedderOrigins,
1267
+ publicOrigin
1268
+ } = options;
1079
1269
  let server = null;
1080
1270
  let origin = null;
1271
+ let boundPort = 0;
1081
1272
  const documents = /* @__PURE__ */ new Map();
1082
1273
  let retainedChars = 0;
1083
1274
  function drop(id) {
@@ -1095,7 +1286,7 @@ function createAppOriginController(options) {
1095
1286
  return {
1096
1287
  async start() {
1097
1288
  if (server && origin) {
1098
- return { port: parseInt(new URL(origin).port, 10), url: origin };
1289
+ return { port: boundPort, url: origin };
1099
1290
  }
1100
1291
  return new Promise((resolve9) => {
1101
1292
  let settled = false;
@@ -1129,6 +1320,22 @@ function createAppOriginController(options) {
1129
1320
  res.end(doc.html);
1130
1321
  });
1131
1322
  let retriedDynamic = false;
1323
+ const adoptedOrigin = (derived) => {
1324
+ if (!publicOrigin) return derived;
1325
+ if (port === 0 || retriedDynamic) {
1326
+ console.warn(
1327
+ `App origin: not using MCP_APP_ORIGIN_FULL_ADDRESS, since the listener is not on a fixed port for it to route to; serving app documents from ${derived} instead.`
1328
+ );
1329
+ return derived;
1330
+ }
1331
+ if (embedderOrigins?.includes(publicOrigin)) {
1332
+ console.warn(
1333
+ `App origin: not using MCP_APP_ORIGIN_FULL_ADDRESS=${publicOrigin}: it is the sandbox's or the Inspector UI's origin, and a dedicated app origin must differ from both; serving app documents from ${derived} instead.`
1334
+ );
1335
+ return derived;
1336
+ }
1337
+ return publicOrigin;
1338
+ };
1132
1339
  server.on("error", (err) => {
1133
1340
  if (err.code === "EADDRINUSE") {
1134
1341
  if (!retriedDynamic && port !== 0) {
@@ -1160,7 +1367,8 @@ function createAppOriginController(options) {
1160
1367
  const actualPort = addr.port;
1161
1368
  const canonicalHost = canonicalUrlHost(host);
1162
1369
  const urlHost = isAllInterfacesHost(canonicalHost) ? "localhost" : canonicalHost;
1163
- origin = `http://${urlHost}:${actualPort}`;
1370
+ origin = adoptedOrigin(`http://${urlHost}:${actualPort}`);
1371
+ boundPort = actualPort;
1164
1372
  settle({ port: actualPort, url: origin });
1165
1373
  });
1166
1374
  });
@@ -1198,6 +1406,107 @@ function createAppOriginController(options) {
1198
1406
  };
1199
1407
  }
1200
1408
 
1409
+ // server/public-address.ts
1410
+ var SANDBOX_FULL_ADDRESS_ENV = "MCP_SANDBOX_FULL_ADDRESS";
1411
+ var APP_ORIGIN_FULL_ADDRESS_ENV = "MCP_APP_ORIGIN_FULL_ADDRESS";
1412
+ var SANDBOX_PATH = "/sandbox";
1413
+ function parsePublicUrl(raw) {
1414
+ let url;
1415
+ try {
1416
+ url = new URL(raw);
1417
+ } catch {
1418
+ return { reason: "it is not an absolute URL" };
1419
+ }
1420
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1421
+ return { reason: "it needs an http:// or https:// scheme" };
1422
+ }
1423
+ if (url.username || url.password) {
1424
+ return { reason: "it must not carry credentials" };
1425
+ }
1426
+ if (url.hostname.includes("*")) {
1427
+ return { reason: "it must not contain a wildcard" };
1428
+ }
1429
+ if (url.hostname.startsWith("[")) {
1430
+ return {
1431
+ reason: "a bracketed IPv6 literal is not a valid CSP host-source \u2014 use a hostname or an IPv4 address"
1432
+ };
1433
+ }
1434
+ if (url.search || url.hash) {
1435
+ return { reason: "it must not carry a query string or fragment" };
1436
+ }
1437
+ return { url };
1438
+ }
1439
+ function describeForLog(raw) {
1440
+ try {
1441
+ const u = new URL(raw);
1442
+ return `"${u.protocol}${u.host ? `//${u.host}` : ""}"`;
1443
+ } catch {
1444
+ return "(unparseable value, not echoed)";
1445
+ }
1446
+ }
1447
+ function warnIgnored(env, raw, reason) {
1448
+ console.warn(
1449
+ `Ignoring ${env}=${describeForLog(raw)}: ${reason}. Advertising the bind-derived address instead.`
1450
+ );
1451
+ }
1452
+ function warnMixedContent(env, url, allowedOrigins) {
1453
+ if (url.protocol === "http:" && allowedOrigins.some((o) => o.startsWith("https:"))) {
1454
+ console.warn(
1455
+ `${env} is plain http (${url.origin}) but ALLOWED_ORIGINS includes an https origin: a browser on the https UI blocks it as mixed content and MCP Apps will not render there.`
1456
+ );
1457
+ }
1458
+ }
1459
+ function resolveSandboxPublicUrl(raw, allowedOrigins) {
1460
+ const value = raw?.trim();
1461
+ if (!value) return void 0;
1462
+ const parsed = parsePublicUrl(value);
1463
+ if ("reason" in parsed) {
1464
+ warnIgnored(SANDBOX_FULL_ADDRESS_ENV, value, parsed.reason);
1465
+ return void 0;
1466
+ }
1467
+ const { url } = parsed;
1468
+ if (url.pathname === "/") url.pathname = SANDBOX_PATH;
1469
+ if (allowedOrigins.includes(url.origin)) {
1470
+ warnIgnored(
1471
+ SANDBOX_FULL_ADDRESS_ENV,
1472
+ value,
1473
+ `its origin ${url.origin} is also an Inspector UI origin (ALLOWED_ORIGINS), and the MCP Apps sandbox must be on a different origin from its host \u2014 route it to its own hostname or port`
1474
+ );
1475
+ return void 0;
1476
+ }
1477
+ warnMixedContent(SANDBOX_FULL_ADDRESS_ENV, url, allowedOrigins);
1478
+ return url.href;
1479
+ }
1480
+ function resolveAppOriginPublicOrigin(raw, allowedOrigins, sandboxUrl) {
1481
+ const value = raw?.trim();
1482
+ if (!value) return void 0;
1483
+ const parsed = parsePublicUrl(value);
1484
+ if ("reason" in parsed) {
1485
+ warnIgnored(APP_ORIGIN_FULL_ADDRESS_ENV, value, parsed.reason);
1486
+ return void 0;
1487
+ }
1488
+ const { url } = parsed;
1489
+ if (url.pathname !== "/") {
1490
+ warnIgnored(
1491
+ APP_ORIGIN_FULL_ADDRESS_ENV,
1492
+ value,
1493
+ "it must be an origin only, with no path \u2014 app documents are served at <origin>/app-document/<id>"
1494
+ );
1495
+ return void 0;
1496
+ }
1497
+ const trusted = sandboxUrl ? [...allowedOrigins, new URL(sandboxUrl).origin] : allowedOrigins;
1498
+ if (trusted.includes(url.origin)) {
1499
+ warnIgnored(
1500
+ APP_ORIGIN_FULL_ADDRESS_ENV,
1501
+ value,
1502
+ `its origin ${url.origin} is also the Inspector UI's or the sandbox's, and a dedicated app origin must differ from both \u2014 route it to its own hostname or port`
1503
+ );
1504
+ return void 0;
1505
+ }
1506
+ warnMixedContent(APP_ORIGIN_FULL_ADDRESS_ENV, url, allowedOrigins);
1507
+ return url.origin;
1508
+ }
1509
+
1201
1510
  // server/web-server-config.ts
1202
1511
  var inspectorVersion = readInspectorVersionSafe(import.meta.url);
1203
1512
  function defaultEnvironmentFromProcess(extra) {
@@ -1382,6 +1691,16 @@ function buildWebServerConfig(options = {}) {
1382
1691
  return null;
1383
1692
  }
1384
1693
  }).filter((o) => o !== null);
1694
+ const allowedOrigins = configuredOrigins?.length ? configuredOrigins : defaultAllowedOrigins(hostname, port);
1695
+ const sandboxPublicUrl = resolveSandboxPublicUrl(
1696
+ process.env[SANDBOX_FULL_ADDRESS_ENV],
1697
+ allowedOrigins
1698
+ );
1699
+ const appOriginPublicOrigin = resolveAppOriginPublicOrigin(
1700
+ process.env[APP_ORIGIN_FULL_ADDRESS_ENV],
1701
+ allowedOrigins,
1702
+ sandboxPublicUrl
1703
+ );
1385
1704
  return {
1386
1705
  port,
1387
1706
  hostname,
@@ -1392,10 +1711,12 @@ function buildWebServerConfig(options = {}) {
1392
1711
  writable,
1393
1712
  initialServers,
1394
1713
  storageDir: process.env.MCP_STORAGE_DIR,
1395
- allowedOrigins: configuredOrigins?.length ? configuredOrigins : defaultAllowedOrigins(hostname, port),
1714
+ allowedOrigins,
1396
1715
  sandboxPort,
1397
1716
  sandboxHost: hostname,
1398
1717
  appOriginPort,
1718
+ sandboxPublicUrl,
1719
+ appOriginPublicOrigin,
1399
1720
  logger,
1400
1721
  autoOpen: resolveAutoOpen()
1401
1722
  };
@@ -1547,6 +1868,7 @@ function redactBody(body, contentType) {
1547
1868
  return body;
1548
1869
  }
1549
1870
  function findHeader(headers, name) {
1871
+ if (!headers) return void 0;
1550
1872
  const target = name.toLowerCase();
1551
1873
  for (const [key, value] of Object.entries(headers)) {
1552
1874
  if (key.toLowerCase() === target) return value;
@@ -1554,9 +1876,146 @@ function findHeader(headers, name) {
1554
1876
  return void 0;
1555
1877
  }
1556
1878
  function isLongLivedStreamResponse(method, contentType) {
1557
- if (method !== "GET") return false;
1879
+ if (method.toUpperCase() !== "GET") return false;
1558
1880
  if (!contentType) return false;
1559
- return contentType.includes("text/event-stream") || contentType.includes("application/x-ndjson");
1881
+ const type = contentType.toLowerCase();
1882
+ return type.includes("text/event-stream") || type.includes("application/x-ndjson");
1883
+ }
1884
+ var CHALLENGE_BODY_MAX_BYTES = 64 * 1024;
1885
+ var CHALLENGE_BODY_READ_MS = 2e3;
1886
+ var TRUNCATED_BODY_SUFFIX = "\n[truncated]";
1887
+ var WITHHELD_TRUNCATED_JSON_BODY = "[truncated JSON body withheld: an incomplete body cannot be redacted]";
1888
+ function recordableChallengeBody(read, contentType) {
1889
+ if (!read.truncated) return redactBody(read.text, contentType);
1890
+ const looksLikeJson = (contentType ?? "").toLowerCase().includes("json") || /^\s*[{[]/.test(read.text);
1891
+ if (looksLikeJson) return WITHHELD_TRUNCATED_JSON_BODY;
1892
+ return redactBody(read.text, contentType) + TRUNCATED_BODY_SUFFIX;
1893
+ }
1894
+ async function readChallengeBody(response) {
1895
+ let reader;
1896
+ try {
1897
+ const body = response.clone().body;
1898
+ if (!body) return void 0;
1899
+ reader = body.getReader();
1900
+ } catch {
1901
+ return void 0;
1902
+ }
1903
+ const decoder = new TextDecoder();
1904
+ let text = "";
1905
+ let bytes = 0;
1906
+ let timer;
1907
+ const deadline = new Promise((resolve9) => {
1908
+ timer = setTimeout(() => resolve9("timeout"), CHALLENGE_BODY_READ_MS);
1909
+ });
1910
+ try {
1911
+ while (bytes < CHALLENGE_BODY_MAX_BYTES) {
1912
+ const next = await Promise.race([reader.read(), deadline]);
1913
+ if (next === "timeout") break;
1914
+ if (next.done) return { text: text + decoder.decode(), truncated: false };
1915
+ const chunk = next.value.subarray(0, CHALLENGE_BODY_MAX_BYTES - bytes);
1916
+ bytes += chunk.byteLength;
1917
+ text += decoder.decode(chunk, { stream: true });
1918
+ }
1919
+ reader.cancel().catch(() => {
1920
+ });
1921
+ return { text: text + decoder.decode(), truncated: true };
1922
+ } catch {
1923
+ return void 0;
1924
+ } finally {
1925
+ clearTimeout(timer);
1926
+ }
1927
+ }
1928
+ var DEFAULT_STREAM_UPDATE_INTERVAL_MS = 250;
1929
+ function longLivedStreamFraming(contentType) {
1930
+ return contentType?.toLowerCase().includes("application/x-ndjson") ? "ndjson" : "sse";
1931
+ }
1932
+ function watchLongLivedStream(response, id, framing, callbacks) {
1933
+ const listener = callbacks.updateStream;
1934
+ if (!listener) return;
1935
+ const report = (state) => {
1936
+ try {
1937
+ listener(id, state);
1938
+ } catch {
1939
+ }
1940
+ };
1941
+ const intervalMs = callbacks.streamUpdateIntervalMs ?? DEFAULT_STREAM_UPDATE_INTERVAL_MS;
1942
+ let body;
1943
+ try {
1944
+ body = response.clone().body;
1945
+ } catch {
1946
+ return;
1947
+ }
1948
+ if (!body) return;
1949
+ const reader = body.getReader();
1950
+ const decoder = new TextDecoder();
1951
+ let eventCount = 0;
1952
+ let pending = "";
1953
+ let blockHasData = false;
1954
+ let reportTimer = null;
1955
+ const reportCount = () => {
1956
+ if (intervalMs <= 0) {
1957
+ report({ eventCount });
1958
+ return;
1959
+ }
1960
+ if (reportTimer !== null) return;
1961
+ reportTimer = setTimeout(() => {
1962
+ reportTimer = null;
1963
+ report({ eventCount });
1964
+ }, intervalMs);
1965
+ };
1966
+ report({ eventCount });
1967
+ const consumeLine = (line) => {
1968
+ if (framing === "ndjson") {
1969
+ if (line.trim() === "") return;
1970
+ eventCount += 1;
1971
+ reportCount();
1972
+ return;
1973
+ }
1974
+ if (line === "") {
1975
+ if (blockHasData) {
1976
+ eventCount += 1;
1977
+ blockHasData = false;
1978
+ reportCount();
1979
+ }
1980
+ return;
1981
+ }
1982
+ if (line === "data" || line.startsWith("data:")) blockHasData = true;
1983
+ };
1984
+ const consumeCompleteLines = () => {
1985
+ for (; ; ) {
1986
+ const cr = pending.indexOf("\r");
1987
+ const lf = pending.indexOf("\n");
1988
+ const end = cr === -1 ? lf : lf === -1 ? cr : Math.min(cr, lf);
1989
+ if (end === -1) return;
1990
+ if (pending[end] === "\r") {
1991
+ if (end === pending.length - 1) return;
1992
+ const width = pending[end + 1] === "\n" ? 2 : 1;
1993
+ consumeLine(pending.slice(0, end));
1994
+ pending = pending.slice(end + width);
1995
+ } else {
1996
+ consumeLine(pending.slice(0, end));
1997
+ pending = pending.slice(end + 1);
1998
+ }
1999
+ }
2000
+ };
2001
+ const pump = async () => {
2002
+ try {
2003
+ for (; ; ) {
2004
+ const { value, done } = await reader.read();
2005
+ if (done) break;
2006
+ pending += decoder.decode(value, { stream: true });
2007
+ consumeCompleteLines();
2008
+ }
2009
+ if (pending.endsWith("\r")) consumeLine(pending.slice(0, -1));
2010
+ } catch {
2011
+ }
2012
+ if (reportTimer !== null) {
2013
+ clearTimeout(reportTimer);
2014
+ reportTimer = null;
2015
+ }
2016
+ report({ eventCount, closedAt: /* @__PURE__ */ new Date() });
2017
+ };
2018
+ void pump();
1560
2019
  }
1561
2020
  function createFetchTracker(baseFetch, callbacks) {
1562
2021
  return async (input, init) => {
@@ -1564,7 +2023,7 @@ function createFetchTracker(baseFetch, callbacks) {
1564
2023
  const timestamp = /* @__PURE__ */ new Date();
1565
2024
  const id = `${timestamp.getTime()}-${Math.random().toString(36).slice(2, 11)}`;
1566
2025
  const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1567
- const method = init?.method || "GET";
2026
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
1568
2027
  const rawRequestHeaders = {};
1569
2028
  if (input instanceof Request) {
1570
2029
  input.headers.forEach((value, key) => {
@@ -1644,7 +2103,21 @@ function createFetchTracker(baseFetch, callbacks) {
1644
2103
  responseBody: void 0,
1645
2104
  duration
1646
2105
  };
2106
+ if ((responseStatus === 401 || responseStatus === 403) && response.body && !response.bodyUsed) {
2107
+ const read = await readChallengeBody(response);
2108
+ entry.responseBody = read === void 0 ? void 0 : recordableChallengeBody(read, response.headers.get("content-type"));
2109
+ callbacks.trackRequest?.(entry);
2110
+ return response;
2111
+ }
1647
2112
  callbacks.trackRequest?.(entry);
2113
+ if (isLongLivedStream && response.body && !response.bodyUsed) {
2114
+ watchLongLivedStream(
2115
+ response,
2116
+ id,
2117
+ longLivedStreamFraming(response.headers.get("content-type")),
2118
+ callbacks
2119
+ );
2120
+ }
1648
2121
  if (!isLongLivedStream && response.body && !response.bodyUsed) {
1649
2122
  const responseContentType = response.headers.get("content-type");
1650
2123
  try {
@@ -1756,7 +2229,7 @@ function createAuthChallengeInterceptFetch(baseFetch) {
1756
2229
  if (!challenge) {
1757
2230
  return response;
1758
2231
  }
1759
- await response.body?.cancel().catch(() => {
2232
+ void response.body?.cancel().catch(() => {
1760
2233
  });
1761
2234
  throw new AuthChallengeError(
1762
2235
  challenge,
@@ -1904,14 +2377,24 @@ function createTransportNode(config, options = {}) {
1904
2377
  pipeStderr = false,
1905
2378
  onFetchRequest,
1906
2379
  onFetchResponseBody,
2380
+ onFetchStreamUpdate,
1907
2381
  authProvider,
1908
2382
  settings,
1909
2383
  interceptAuthChallenges = false,
1910
2384
  onAuthChallengeObserved
1911
2385
  } = options;
2386
+ const wantsFetchTracking = Boolean(
2387
+ onFetchRequest || onFetchResponseBody || onFetchStreamUpdate
2388
+ );
1912
2389
  const baseFetch = optionsFetchFn ?? createProxyFetch() ?? globalThis.fetch;
1913
2390
  const withChallengeObserver = (inner) => onAuthChallengeObserved ? createAuthChallengeObserverFetch(inner, onAuthChallengeObserved) : inner;
1914
- const fetchWithOptionalAuthIntercept = interceptAuthChallenges ? createAuthChallengeInterceptFetch(withChallengeObserver(baseFetch)) : withChallengeObserver(baseFetch);
2391
+ const withTracking = (inner) => wantsFetchTracking ? createFetchTracker(inner, {
2392
+ trackRequest: onFetchRequest,
2393
+ updateResponseBody: onFetchResponseBody,
2394
+ updateStream: onFetchStreamUpdate
2395
+ }) : inner;
2396
+ const trackedBase = withTracking(withChallengeObserver(baseFetch));
2397
+ const fetchWithOptionalAuthIntercept = interceptAuthChallenges ? createAuthChallengeInterceptFetch(trackedBase) : trackedBase;
1915
2398
  if (serverType === "stdio") {
1916
2399
  const stdioConfig = config;
1917
2400
  const transport = new StdioClientTransport({
@@ -1937,11 +2420,7 @@ function createTransportNode(config, options = {}) {
1937
2420
  const sseConfig = config;
1938
2421
  const url = new URL(sseConfig.url);
1939
2422
  const configuredSseFetch = sseConfig.eventSourceInit?.fetch;
1940
- const sseFetch = configuredSseFetch ? withChallengeObserver(configuredSseFetch) : fetchWithOptionalAuthIntercept;
1941
- const trackedFetch = onFetchRequest ? createFetchTracker(sseFetch, {
1942
- trackRequest: onFetchRequest,
1943
- updateResponseBody: onFetchResponseBody
1944
- }) : sseFetch;
2423
+ const trackedFetch = configuredSseFetch ? withTracking(withChallengeObserver(configuredSseFetch)) : fetchWithOptionalAuthIntercept;
1945
2424
  const headers = headersFromSettings(settings);
1946
2425
  const eventSourceInit = {
1947
2426
  ...sseConfig.eventSourceInit,
@@ -1952,15 +2431,11 @@ function createTransportNode(config, options = {}) {
1952
2431
  ...sseConfig.requestInit,
1953
2432
  ...headers && { headers }
1954
2433
  };
1955
- const postFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
1956
- trackRequest: onFetchRequest,
1957
- updateResponseBody: onFetchResponseBody
1958
- }) : fetchWithOptionalAuthIntercept;
1959
2434
  const transport = new SSEClientTransport(url, {
1960
2435
  authProvider,
1961
2436
  eventSourceInit,
1962
2437
  requestInit,
1963
- fetch: postFetch
2438
+ fetch: fetchWithOptionalAuthIntercept
1964
2439
  });
1965
2440
  return { transport };
1966
2441
  } else {
@@ -1971,14 +2446,10 @@ function createTransportNode(config, options = {}) {
1971
2446
  ...httpConfig.requestInit,
1972
2447
  ...headers && { headers }
1973
2448
  };
1974
- const transportFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
1975
- trackRequest: onFetchRequest,
1976
- updateResponseBody: onFetchResponseBody
1977
- }) : fetchWithOptionalAuthIntercept;
1978
2449
  const transport = new StreamableHTTPClientTransport(url, {
1979
2450
  authProvider,
1980
2451
  requestInit,
1981
- fetch: transportFetch,
2452
+ fetch: fetchWithOptionalAuthIntercept,
1982
2453
  // SEP-2350: how the transport reacts to a `403 insufficient_scope`
1983
2454
  // challenge. Defaults to the SDK's `reauthorize` when unset.
1984
2455
  ...settings?.oauthOnInsufficientScope && {
@@ -1989,6 +2460,9 @@ function createTransportNode(config, options = {}) {
1989
2460
  }
1990
2461
  }
1991
2462
 
2463
+ // ../../core/auth/requestTimeout.ts
2464
+ var OAUTH_TIMEOUT_WIRE_CODE = "oauth_request_timeout";
2465
+
1992
2466
  // ../../core/json/xMcpHeader.ts
1993
2467
  var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
1994
2468
 
@@ -2141,6 +2615,20 @@ function resolveImportSource(type, platform, homeDir, readFile2) {
2141
2615
  return { type, found: false, searched };
2142
2616
  }
2143
2617
 
2618
+ // ../../core/mcp/remote/progressToken.ts
2619
+ import { ProgressTokenSchema } from "@modelcontextprotocol/core";
2620
+ function progressTokenOf(message) {
2621
+ if ("method" in message && message.method === "notifications/progress" && "params" in message && typeof message.params === "object" && message.params !== null) {
2622
+ const parsed = ProgressTokenSchema.safeParse(
2623
+ message.params.progressToken
2624
+ );
2625
+ if (parsed.success) {
2626
+ return parsed.data;
2627
+ }
2628
+ }
2629
+ return void 0;
2630
+ }
2631
+
2144
2632
  // ../../core/mcp/remote/node/remote-session.ts
2145
2633
  var RemoteSession = class _RemoteSession {
2146
2634
  sessionId;
@@ -2260,30 +2748,54 @@ var RemoteSession = class _RemoteSession {
2260
2748
  */
2261
2749
  waitForRequestResponse(requestId, timeoutMs = 6e4) {
2262
2750
  return new Promise((resolve9, reject) => {
2263
- const timer = timeoutMs > 0 ? setTimeout(() => {
2264
- this.requestWaits.delete(requestId);
2265
- reject(
2266
- new Error(
2267
- `MCP request ${String(requestId)} timed out after ${timeoutMs}ms`
2268
- )
2269
- );
2270
- }, timeoutMs) : void 0;
2751
+ let timer;
2752
+ const arm = () => {
2753
+ if (timeoutMs <= 0) return;
2754
+ timer = setTimeout(() => {
2755
+ this.requestWaits.delete(requestId);
2756
+ reject(
2757
+ new Error(
2758
+ `MCP request ${String(requestId)} timed out after ${timeoutMs}ms`
2759
+ )
2760
+ );
2761
+ }, timeoutMs);
2762
+ };
2763
+ const clear = () => {
2764
+ if (timer !== void 0) {
2765
+ clearTimeout(timer);
2766
+ timer = void 0;
2767
+ }
2768
+ };
2769
+ arm();
2271
2770
  this.requestWaits.set(requestId, {
2272
2771
  resolve: () => {
2273
- if (timer !== void 0) {
2274
- clearTimeout(timer);
2275
- }
2772
+ clear();
2276
2773
  resolve9();
2277
2774
  },
2278
2775
  reject: (error) => {
2279
- if (timer !== void 0) {
2280
- clearTimeout(timer);
2281
- }
2776
+ clear();
2282
2777
  reject(error);
2778
+ },
2779
+ // A progress notification for this request re-arms the full window, so
2780
+ // a tool that keeps reporting progress never trips the relay's own
2781
+ // deadline — the same guarantee the SDK client's `resetTimeoutOnProgress`
2782
+ // gives on a direct transport (#2028).
2783
+ resetTimeout: () => {
2784
+ if (timer === void 0) return;
2785
+ clear();
2786
+ arm();
2283
2787
  }
2284
2788
  });
2285
2789
  });
2286
2790
  }
2791
+ /**
2792
+ * Re-arm a pending request's wait timeout. Invoked from {@link onMessage} when
2793
+ * a `notifications/progress` carrying this request's `progressToken` arrives.
2794
+ * A no-op when no wait is pending for the id.
2795
+ */
2796
+ noteRequestProgress(requestId) {
2797
+ this.requestWaits.get(requestId)?.resetTimeout();
2798
+ }
2287
2799
  cancelRequestWait(requestId) {
2288
2800
  const wait = this.requestWaits.get(requestId);
2289
2801
  if (!wait) {
@@ -2338,6 +2850,10 @@ var RemoteSession = class _RemoteSession {
2338
2850
  if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) {
2339
2851
  this.settleRequestWait(message.id);
2340
2852
  }
2853
+ const progressToken = progressTokenOf(message);
2854
+ if (progressToken !== void 0) {
2855
+ this.noteRequestProgress(progressToken);
2856
+ }
2341
2857
  this.pushEvent({ type: "message", data: message });
2342
2858
  }
2343
2859
  onFetchRequest(entry) {
@@ -2355,6 +2871,16 @@ var RemoteSession = class _RemoteSession {
2355
2871
  data: { id, responseBody }
2356
2872
  });
2357
2873
  }
2874
+ onFetchStreamUpdate(id, stream) {
2875
+ this.pushEvent({
2876
+ type: "fetch_stream_update",
2877
+ data: {
2878
+ id,
2879
+ eventCount: stream.eventCount,
2880
+ ...stream.closedAt && { closedAt: stream.closedAt.toISOString() }
2881
+ }
2882
+ });
2883
+ }
2358
2884
  onStderr(entry) {
2359
2885
  this.pushEvent({
2360
2886
  type: "stdio_log",
@@ -3821,7 +4347,7 @@ function defaultSecretStore() {
3821
4347
  }
3822
4348
 
3823
4349
  // ../../core/client/config-parse.ts
3824
- import { z } from "zod";
4350
+ import { z as z2 } from "zod";
3825
4351
  function isAbsoluteHttpUrl(value) {
3826
4352
  let url;
3827
4353
  try {
@@ -3838,17 +4364,21 @@ function isRealHost(hostname) {
3838
4364
  const labels = hostname.split(".");
3839
4365
  return labels.length >= 2 && labels.every((label) => label !== "");
3840
4366
  }
3841
- var HttpUrlStringSchema = z.string().min(1).superRefine((val, ctx) => {
4367
+ var HttpUrlStringSchema = z2.string().min(1).superRefine((val, ctx) => {
3842
4368
  const trimmed = val.trim();
3843
4369
  if (!isAbsoluteHttpUrl(trimmed)) {
3844
4370
  ctx.addIssue({
3845
- code: z.ZodIssueCode.custom,
4371
+ code: z2.ZodIssueCode.custom,
3846
4372
  message: `Invalid URL: "${trimmed}" \u2014 must be an http(s) URL (e.g. https://idp.example.com)`
3847
4373
  });
3848
4374
  }
3849
4375
  });
3850
4376
  var CIMD_METADATA_URL_INVALID_ERROR = "Must be a valid URL, like https://example.com/oauth/client.json";
3851
- var CIMD_METADATA_URL_HTTPS_ERROR = "CIMD client metadata URL must use HTTPS";
4377
+ var CIMD_METADATA_URL_HTTPS_ERROR = "CIMD client metadata URL must use HTTPS, except on localhost, 127.0.0.1 or [::1]";
4378
+ var CIMD_HTTP_EXEMPT_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
4379
+ function isCimdHttpExemptHost(hostname) {
4380
+ return CIMD_HTTP_EXEMPT_HOSTS.has(hostname);
4381
+ }
3852
4382
  var CIMD_METADATA_URL_PATH_ERROR = "Must include a path (not the site root), like https://example.com/oauth/client.json";
3853
4383
  function getCimdClientMetadataUrlError(value) {
3854
4384
  const trimmed = value.trim();
@@ -3858,7 +4388,7 @@ function getCimdClientMetadataUrlError(value) {
3858
4388
  }
3859
4389
  try {
3860
4390
  const url = new URL(trimmed);
3861
- if (url.protocol !== "https:") {
4391
+ if (url.protocol !== "https:" && !isCimdHttpExemptHost(url.hostname)) {
3862
4392
  return CIMD_METADATA_URL_HTTPS_ERROR;
3863
4393
  }
3864
4394
  if (url.pathname === "/" || url.pathname === "") {
@@ -3874,7 +4404,7 @@ function refineCimdMetadataUrl(val, ctx, required) {
3874
4404
  if (trimmed === "") {
3875
4405
  if (required) {
3876
4406
  ctx.addIssue({
3877
- code: z.ZodIssueCode.custom,
4407
+ code: z2.ZodIssueCode.custom,
3878
4408
  message: "CIMD client metadata URL is required when CIMD is enabled"
3879
4409
  });
3880
4410
  }
@@ -3883,25 +4413,25 @@ function refineCimdMetadataUrl(val, ctx, required) {
3883
4413
  const error = getCimdClientMetadataUrlError(trimmed);
3884
4414
  if (error) {
3885
4415
  ctx.addIssue({
3886
- code: z.ZodIssueCode.custom,
4416
+ code: z2.ZodIssueCode.custom,
3887
4417
  message: error
3888
4418
  });
3889
4419
  }
3890
4420
  }
3891
- var CimdConfigSchema = z.object({
3892
- enabled: z.boolean(),
3893
- clientMetadataUrl: z.string()
4421
+ var CimdConfigSchema = z2.object({
4422
+ enabled: z2.boolean(),
4423
+ clientMetadataUrl: z2.string()
3894
4424
  }).superRefine((data, ctx) => {
3895
4425
  refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
3896
4426
  });
3897
- var EnterpriseManagedAuthIdpConfigSchema = z.object({
4427
+ var EnterpriseManagedAuthIdpConfigSchema = z2.object({
3898
4428
  issuer: HttpUrlStringSchema,
3899
- clientId: z.string().min(1),
3900
- clientSecret: z.string().optional()
4429
+ clientId: z2.string().min(1),
4430
+ clientSecret: z2.string().optional()
3901
4431
  });
3902
- var ClientConfigSchema = z.object({
3903
- enterpriseManagedAuth: z.object({
3904
- enabled: z.boolean().optional(),
4432
+ var ClientConfigSchema = z2.object({
4433
+ enterpriseManagedAuth: z2.object({
4434
+ enabled: z2.boolean().optional(),
3905
4435
  idp: EnterpriseManagedAuthIdpConfigSchema
3906
4436
  }).optional(),
3907
4437
  cimd: CimdConfigSchema.optional()
@@ -3910,7 +4440,7 @@ function parseClientConfig(raw) {
3910
4440
  return ClientConfigSchema.parse(raw);
3911
4441
  }
3912
4442
  function formatClientConfigLoadError(error) {
3913
- if (error instanceof z.ZodError) {
4443
+ if (error instanceof z2.ZodError) {
3914
4444
  const issue = error.issues[0];
3915
4445
  if (issue) {
3916
4446
  const field = issue.path.length > 0 ? issue.path.map(String).join(".") : "config";
@@ -4395,6 +4925,7 @@ function createRemoteApp(options) {
4395
4925
  onStderr: (entry) => session.onStderr(entry),
4396
4926
  onFetchRequest: (entry) => session.onFetchRequest(entry),
4397
4927
  onFetchResponseBody: (id, body2) => session.onFetchResponseBody(id, body2),
4928
+ onFetchStreamUpdate: (id, stream) => session.onFetchStreamUpdate(id, stream),
4398
4929
  authProvider: authHandle?.provider,
4399
4930
  settings: body.settings,
4400
4931
  // Always intercept 401/403 on the node MCP transport. Without this, the
@@ -4629,15 +5160,36 @@ function createRemoteApp(options) {
4629
5160
  } catch {
4630
5161
  return c.json({ error: "Invalid JSON body" }, 400);
4631
5162
  }
4632
- const { url, method = "GET", headers = {}, body: reqBody } = body;
5163
+ const {
5164
+ url,
5165
+ method = "GET",
5166
+ headers = {},
5167
+ body: reqBody,
5168
+ timeoutMs: requestedTimeoutMs
5169
+ } = body;
4633
5170
  if (!url) {
4634
5171
  return c.json({ error: "Missing url" }, 400);
4635
5172
  }
5173
+ const controller = new AbortController();
5174
+ let deadlineFired = false;
5175
+ const clientSignal = c.req.raw.signal;
5176
+ const signal = clientSignal ? AbortSignal.any([controller.signal, clientSignal]) : controller.signal;
5177
+ const deadlineMs = typeof requestedTimeoutMs === "number" && Number.isFinite(requestedTimeoutMs) ? Math.min(2147483647, Math.max(0, Math.round(requestedTimeoutMs))) : void 0;
5178
+ const safeUrl = redactUrlQuery(url);
5179
+ const timer = deadlineMs === void 0 ? void 0 : setTimeout(() => {
5180
+ deadlineFired = true;
5181
+ controller.abort(
5182
+ new Error(
5183
+ `proxied request to ${safeUrl} timed out after ${deadlineMs}ms`
5184
+ )
5185
+ );
5186
+ }, deadlineMs);
4636
5187
  try {
4637
5188
  const res = await (createProxyFetch() ?? fetch)(url, {
4638
5189
  method,
4639
5190
  headers: new Headers(headers),
4640
- body: reqBody
5191
+ body: reqBody,
5192
+ signal
4641
5193
  });
4642
5194
  const resHeaders = {};
4643
5195
  res.headers.forEach((v, k) => {
@@ -4648,6 +5200,9 @@ function createRemoteApp(options) {
4648
5200
  let resBody;
4649
5201
  if (!isStream && res.body) {
4650
5202
  resBody = await res.text();
5203
+ } else if (isStream) {
5204
+ void res.body?.cancel().catch(() => {
5205
+ });
4651
5206
  }
4652
5207
  return c.json({
4653
5208
  ok: res.ok,
@@ -4658,7 +5213,20 @@ function createRemoteApp(options) {
4658
5213
  });
4659
5214
  } catch (err) {
4660
5215
  const msg = err instanceof Error ? err.message : String(err);
5216
+ if (deadlineFired) {
5217
+ return c.json(
5218
+ {
5219
+ error: msg,
5220
+ code: OAUTH_TIMEOUT_WIRE_CODE,
5221
+ url: safeUrl,
5222
+ timeoutMs: deadlineMs
5223
+ },
5224
+ 504
5225
+ );
5226
+ }
4661
5227
  return c.json({ error: msg }, 500);
5228
+ } finally {
5229
+ if (timer !== void 0) clearTimeout(timer);
4662
5230
  }
4663
5231
  });
4664
5232
  app.post("/api/log", async (c) => {
@@ -4854,6 +5422,18 @@ function createRemoteApp(options) {
4854
5422
  );
4855
5423
  delete valObj.maxFetchRequests;
4856
5424
  }
5425
+ for (const key of [
5426
+ "skillCatalogMaxSkills",
5427
+ "skillCatalogMaxBytes"
5428
+ ]) {
5429
+ if (key in valObj && !isSkillCatalogLimit(valObj[key])) {
5430
+ logWarn(
5431
+ { route: "/api/servers", id, droppedKey: key },
5432
+ `Dropping malformed \`${key}\` field \u2014 expected a positive integer.`
5433
+ );
5434
+ delete valObj[key];
5435
+ }
5436
+ }
4857
5437
  if ("oauth" in valObj && !isOauthObject(valObj.oauth)) {
4858
5438
  logWarn(
4859
5439
  { route: "/api/servers", id, droppedKey: "oauth" },
@@ -5010,6 +5590,17 @@ function createRemoteApp(options) {
5010
5590
  error: "settings.maxFetchRequests must be a non-negative number"
5011
5591
  };
5012
5592
  }
5593
+ for (const key of [
5594
+ "skillCatalogMaxSkills",
5595
+ "skillCatalogMaxBytes"
5596
+ ]) {
5597
+ if (obj[key] !== void 0 && !isSkillCatalogLimit(obj[key])) {
5598
+ return {
5599
+ ok: false,
5600
+ error: `settings.${key} must be a positive integer`
5601
+ };
5602
+ }
5603
+ }
5013
5604
  for (const optional of [
5014
5605
  "oauthClientId",
5015
5606
  "oauthClientSecret",
@@ -5105,6 +5696,12 @@ function createRemoteApp(options) {
5105
5696
  // writes no spurious `roots` field.
5106
5697
  roots: isRootArray(obj.roots) ? obj.roots : []
5107
5698
  };
5699
+ if (isSkillCatalogLimit(obj.skillCatalogMaxSkills)) {
5700
+ value.skillCatalogMaxSkills = obj.skillCatalogMaxSkills;
5701
+ }
5702
+ if (isSkillCatalogLimit(obj.skillCatalogMaxBytes)) {
5703
+ value.skillCatalogMaxBytes = obj.skillCatalogMaxBytes;
5704
+ }
5108
5705
  if (typeof obj.oauthClientId === "string" && obj.oauthClientId !== "") {
5109
5706
  value.oauthClientId = obj.oauthClientId;
5110
5707
  }
@@ -5667,12 +6264,14 @@ function honoMiddlewarePlugin(config) {
5667
6264
  const sandboxController = createSandboxController({
5668
6265
  port: config.sandboxPort,
5669
6266
  host: config.sandboxHost,
5670
- allowedOrigins: config.allowedOrigins
6267
+ allowedOrigins: config.allowedOrigins,
6268
+ publicUrl: config.sandboxPublicUrl
5671
6269
  });
5672
6270
  await sandboxController.start();
5673
6271
  const appOriginController = createAppOriginController({
5674
6272
  port: config.appOriginPort,
5675
6273
  host: config.sandboxHost,
6274
+ publicOrigin: config.appOriginPublicOrigin,
5676
6275
  embedderOrigins: appDocumentEmbedders(
5677
6276
  sandboxController.getUrl(),
5678
6277
  config.allowedOrigins
@@ -5890,6 +6489,33 @@ function clearViteDepsCache(clientWebRoot) {
5890
6489
  // ../../vitest.shared.mts
5891
6490
  import path5 from "path";
5892
6491
  import { fileURLToPath as fileURLToPath3 } from "url";
6492
+ var NO_RETRY_SETUP = path5.resolve(
6493
+ path5.dirname(fileURLToPath3(import.meta.url)),
6494
+ "vitest.setup.shared.mts"
6495
+ );
6496
+ var TIMEOUTS = Object.freeze({
6497
+ /**
6498
+ * 3x Vitest's default. Covers the measured load; past this a genuinely hung
6499
+ * unit test costs the whole budget to discover. Both projects that had
6500
+ * already answered this question by hand — `clients/cli` and web's
6501
+ * `storybook` (#2292) — independently arrived at 15000, which is why it is
6502
+ * the shared value rather than a new one.
6503
+ */
6504
+ testTimeout: 15e3,
6505
+ /**
6506
+ * Hooks do the setup and teardown a test's own budget never covers —
6507
+ * spawning servers, provisioning temp dirs, unlinking filesystem-backed
6508
+ * storage. They are also where the real-transitions auto-settle in
6509
+ * `clients/web/src/test/renderWithMantine.tsx` awaits, so this is the one
6510
+ * budget in the repo that actually governs a deliberate wait.
6511
+ */
6512
+ hookTimeout: 3e4,
6513
+ teardownTimeout: 3e4
6514
+ });
6515
+ var INTEGRATION_TIMEOUTS = Object.freeze({
6516
+ ...TIMEOUTS,
6517
+ testTimeout: 3e4
6518
+ });
5893
6519
  function vitestSharedPaths(clientDir) {
5894
6520
  const dirname10 = path5.resolve(clientDir);
5895
6521
  const repoRoot = path5.resolve(dirname10, "../..");
@@ -6132,12 +6758,14 @@ async function startHonoServer(config) {
6132
6758
  const sandboxController = createSandboxController({
6133
6759
  port: config.sandboxPort,
6134
6760
  host: config.sandboxHost,
6135
- allowedOrigins: config.allowedOrigins
6761
+ allowedOrigins: config.allowedOrigins,
6762
+ publicUrl: config.sandboxPublicUrl
6136
6763
  });
6137
6764
  await sandboxController.start();
6138
6765
  const appOriginController = createAppOriginController({
6139
6766
  port: config.appOriginPort,
6140
6767
  host: config.sandboxHost,
6768
+ publicOrigin: config.appOriginPublicOrigin,
6141
6769
  embedderOrigins: appDocumentEmbedders(
6142
6770
  sandboxController.getUrl(),
6143
6771
  config.allowedOrigins
@@ -6310,6 +6938,10 @@ async function runWeb(argv) {
6310
6938
  'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
6311
6939
  parseHeaderPair,
6312
6940
  {}
6941
+ ).option(
6942
+ "--protocol-era <era>",
6943
+ "protocol era for the ad-hoc server: legacy, auto, or modern (default legacy)",
6944
+ parseProtocolEra
6313
6945
  ).option("--dev", "run in development mode (Vite)").parse(preArgs);
6314
6946
  const opts = program.opts();
6315
6947
  const args = program.args;
@@ -6319,8 +6951,9 @@ async function runWeb(argv) {
6319
6951
  const hasCatalog = !!catalogPath;
6320
6952
  const hasConfig = !!opts.config;
6321
6953
  const hasHeaders = !!opts.header && Object.keys(opts.header).length > 0;
6954
+ const hasProtocolEra = opts.protocolEra !== void 0;
6322
6955
  const hasAdHocServer = target.length > 0 || !!opts.serverUrl || !!opts.transport && opts.transport !== "stdio";
6323
- const conflict = hasCatalog && hasConfig ? "--catalog and --config are mutually exclusive. --catalog is the writable catalog; --config is a read-only session file." : hasCatalog && hasAdHocServer ? "--catalog cannot be combined with an ad-hoc server URL/command." : hasCatalog && hasHeaders ? "--header cannot be combined with --catalog. Set per-server headers in the catalog file." : hasConfig && hasAdHocServer ? "--config cannot be combined with an ad-hoc server URL/command. --config selects a read-only session file." : hasConfig && hasHeaders ? "--header cannot be combined with --config. Set per-server headers inside the config file instead." : null;
6956
+ const conflict = hasCatalog && hasConfig ? "--catalog and --config are mutually exclusive. --catalog is the writable catalog; --config is a read-only session file." : hasCatalog && hasAdHocServer ? "--catalog cannot be combined with an ad-hoc server URL/command." : hasCatalog && hasHeaders ? "--header cannot be combined with --catalog. Set per-server headers in the catalog file." : hasCatalog && hasProtocolEra ? "--protocol-era cannot be combined with --catalog. Set protocolEra per server in the catalog file." : hasConfig && hasAdHocServer ? "--config cannot be combined with an ad-hoc server URL/command. --config selects a read-only session file." : hasConfig && hasHeaders ? "--header cannot be combined with --config. Set per-server headers inside the config file instead." : hasConfig && hasProtocolEra ? "--protocol-era cannot be combined with --config. Set protocolEra per server inside the config file instead." : null;
6324
6957
  if (conflict) {
6325
6958
  console.error(`Error: ${conflict}`);
6326
6959
  process.exit(1);
@@ -6385,6 +7018,9 @@ async function runWeb(argv) {
6385
7018
  }
6386
7019
  initialMcpConfig = config;
6387
7020
  const entry = hasHeaders && config.type !== "stdio" && config.type !== void 0 ? { ...config, headers: opts.header } : { ...config };
7021
+ if (opts.protocolEra !== void 0) {
7022
+ entry.protocolEra = opts.protocolEra;
7023
+ }
6388
7024
  initialServers = {
6389
7025
  mcpServers: { [deriveSeedServerId(config)]: entry }
6390
7026
  };
@@ -6394,6 +7030,11 @@ async function runWeb(argv) {
6394
7030
  "Error: --header requires an ad-hoc --server-url or command target."
6395
7031
  );
6396
7032
  process.exit(1);
7033
+ } else if (hasProtocolEra) {
7034
+ console.error(
7035
+ "Error: --protocol-era requires an ad-hoc --server-url or command target."
7036
+ );
7037
+ process.exit(1);
6397
7038
  }
6398
7039
  let webConfig;
6399
7040
  try {