@modelcontextprotocol/inspector 2.5.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.
- package/README.md +4 -1
- package/clients/cli/build/index.js +2336 -242
- package/clients/tui/build/index.js +3388 -971
- package/clients/web/build/index.js +725 -73
- package/clients/web/dist/assets/index-DZkZ6KYt.js +1598 -0
- package/clients/web/dist/assets/{prism-light-_bvDBTQE.js → prism-light-CoQfbOXL.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/clients/web/static/sandbox_proxy.html +30 -4
- package/package.json +15 -10
- package/clients/web/dist/assets/index-ZYqA4f7P.js +0 -1486
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
};
|
|
@@ -517,6 +668,7 @@ function expectedSecretFields(stored) {
|
|
|
517
668
|
}
|
|
518
669
|
return fields;
|
|
519
670
|
}
|
|
671
|
+
var EXAMPLE_SERVER_URL = "https://example-server.modelcontextprotocol.io/mcp";
|
|
520
672
|
var DEFAULT_SEED_CONFIG = {
|
|
521
673
|
mcpServers: {
|
|
522
674
|
"filesystem-server-default": {
|
|
@@ -528,6 +680,10 @@ var DEFAULT_SEED_CONFIG = {
|
|
|
528
680
|
type: "stdio",
|
|
529
681
|
command: "npx",
|
|
530
682
|
args: ["-y", "@modelcontextprotocol/server-everything"]
|
|
683
|
+
},
|
|
684
|
+
"example-server-default": {
|
|
685
|
+
type: "streamable-http",
|
|
686
|
+
url: EXAMPLE_SERVER_URL
|
|
531
687
|
}
|
|
532
688
|
}
|
|
533
689
|
};
|
|
@@ -560,6 +716,14 @@ function parseHeaderPair(value, previous = {}) {
|
|
|
560
716
|
}
|
|
561
717
|
return { ...previous, [key]: val };
|
|
562
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
|
+
}
|
|
563
727
|
var EMPTY_CATALOG_CONTENT = `${JSON.stringify({ mcpServers: {} }, null, 2)}
|
|
564
728
|
`;
|
|
565
729
|
function seedEmptyCatalog(resolvedPath) {
|
|
@@ -919,10 +1083,25 @@ function sandboxFrameAncestors(allowedOrigins) {
|
|
|
919
1083
|
return frameAncestorsDirective(allowedOrigins);
|
|
920
1084
|
}
|
|
921
1085
|
function frameAncestorsDirective(origins) {
|
|
922
|
-
const valid = (origins
|
|
1086
|
+
const valid = validHostSources(origins);
|
|
923
1087
|
const sources = valid.length > 0 ? valid : LOOPBACK_FRAME_ANCESTORS;
|
|
924
1088
|
return `frame-ancestors ${sources.join(" ")}`;
|
|
925
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
|
+
}
|
|
926
1105
|
function parseListenPort(raw) {
|
|
927
1106
|
const v = raw?.trim();
|
|
928
1107
|
if (!v || !/^\d+$/.test(v)) return void 0;
|
|
@@ -940,22 +1119,25 @@ function resolveSandboxPort() {
|
|
|
940
1119
|
return fromSandbox ?? parseListenPort(process.env.SERVER_PORT) ?? DEFAULT_SANDBOX_PORT;
|
|
941
1120
|
}
|
|
942
1121
|
function createSandboxController(options) {
|
|
943
|
-
const { port, host = DEFAULT_BIND_HOST, allowedOrigins } = options;
|
|
1122
|
+
const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl } = options;
|
|
944
1123
|
let server = null;
|
|
945
1124
|
let sandboxUrl = null;
|
|
1125
|
+
let boundPort = 0;
|
|
946
1126
|
const SANDBOX_PROXY_CSP = sandboxFrameAncestors(allowedOrigins);
|
|
947
1127
|
let sandboxHtml;
|
|
948
1128
|
try {
|
|
949
1129
|
const sandboxHtmlPath = join3(__dirname, "../static/sandbox_proxy.html");
|
|
950
|
-
sandboxHtml =
|
|
1130
|
+
sandboxHtml = renderSandboxProxyHtml(
|
|
1131
|
+
readFileSync3(sandboxHtmlPath, "utf-8"),
|
|
1132
|
+
allowedOrigins
|
|
1133
|
+
);
|
|
951
1134
|
} catch (e) {
|
|
952
1135
|
sandboxHtml = "<!DOCTYPE html><html><body>Sandbox not loaded: " + String(e.message) + "</body></html>";
|
|
953
1136
|
}
|
|
954
1137
|
return {
|
|
955
1138
|
async start() {
|
|
956
1139
|
if (server && sandboxUrl) {
|
|
957
|
-
|
|
958
|
-
return { port: p, url: sandboxUrl };
|
|
1140
|
+
return { port: boundPort, url: sandboxUrl };
|
|
959
1141
|
}
|
|
960
1142
|
return new Promise((resolve9) => {
|
|
961
1143
|
let settled = false;
|
|
@@ -1009,7 +1191,15 @@ function createSandboxController(options) {
|
|
|
1009
1191
|
);
|
|
1010
1192
|
const canonicalHost = canonicalUrlHost(host);
|
|
1011
1193
|
const urlHost = isAllInterfacesHost(canonicalHost) ? "localhost" : canonicalHost;
|
|
1012
|
-
|
|
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;
|
|
1013
1203
|
settle({ port: actualPort, url: sandboxUrl });
|
|
1014
1204
|
});
|
|
1015
1205
|
});
|
|
@@ -1070,9 +1260,15 @@ function parseDocumentId(url) {
|
|
|
1070
1260
|
return /^[0-9a-f]{32}$/.test(id ?? "") ? id ?? null : null;
|
|
1071
1261
|
}
|
|
1072
1262
|
function createAppOriginController(options) {
|
|
1073
|
-
const {
|
|
1263
|
+
const {
|
|
1264
|
+
port,
|
|
1265
|
+
host = DEFAULT_BIND_HOST,
|
|
1266
|
+
embedderOrigins,
|
|
1267
|
+
publicOrigin
|
|
1268
|
+
} = options;
|
|
1074
1269
|
let server = null;
|
|
1075
1270
|
let origin = null;
|
|
1271
|
+
let boundPort = 0;
|
|
1076
1272
|
const documents = /* @__PURE__ */ new Map();
|
|
1077
1273
|
let retainedChars = 0;
|
|
1078
1274
|
function drop(id) {
|
|
@@ -1090,7 +1286,7 @@ function createAppOriginController(options) {
|
|
|
1090
1286
|
return {
|
|
1091
1287
|
async start() {
|
|
1092
1288
|
if (server && origin) {
|
|
1093
|
-
return { port:
|
|
1289
|
+
return { port: boundPort, url: origin };
|
|
1094
1290
|
}
|
|
1095
1291
|
return new Promise((resolve9) => {
|
|
1096
1292
|
let settled = false;
|
|
@@ -1124,6 +1320,22 @@ function createAppOriginController(options) {
|
|
|
1124
1320
|
res.end(doc.html);
|
|
1125
1321
|
});
|
|
1126
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
|
+
};
|
|
1127
1339
|
server.on("error", (err) => {
|
|
1128
1340
|
if (err.code === "EADDRINUSE") {
|
|
1129
1341
|
if (!retriedDynamic && port !== 0) {
|
|
@@ -1155,7 +1367,8 @@ function createAppOriginController(options) {
|
|
|
1155
1367
|
const actualPort = addr.port;
|
|
1156
1368
|
const canonicalHost = canonicalUrlHost(host);
|
|
1157
1369
|
const urlHost = isAllInterfacesHost(canonicalHost) ? "localhost" : canonicalHost;
|
|
1158
|
-
origin = `http://${urlHost}:${actualPort}
|
|
1370
|
+
origin = adoptedOrigin(`http://${urlHost}:${actualPort}`);
|
|
1371
|
+
boundPort = actualPort;
|
|
1159
1372
|
settle({ port: actualPort, url: origin });
|
|
1160
1373
|
});
|
|
1161
1374
|
});
|
|
@@ -1193,6 +1406,107 @@ function createAppOriginController(options) {
|
|
|
1193
1406
|
};
|
|
1194
1407
|
}
|
|
1195
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
|
+
|
|
1196
1510
|
// server/web-server-config.ts
|
|
1197
1511
|
var inspectorVersion = readInspectorVersionSafe(import.meta.url);
|
|
1198
1512
|
function defaultEnvironmentFromProcess(extra) {
|
|
@@ -1377,6 +1691,16 @@ function buildWebServerConfig(options = {}) {
|
|
|
1377
1691
|
return null;
|
|
1378
1692
|
}
|
|
1379
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
|
+
);
|
|
1380
1704
|
return {
|
|
1381
1705
|
port,
|
|
1382
1706
|
hostname,
|
|
@@ -1387,10 +1711,12 @@ function buildWebServerConfig(options = {}) {
|
|
|
1387
1711
|
writable,
|
|
1388
1712
|
initialServers,
|
|
1389
1713
|
storageDir: process.env.MCP_STORAGE_DIR,
|
|
1390
|
-
allowedOrigins
|
|
1714
|
+
allowedOrigins,
|
|
1391
1715
|
sandboxPort,
|
|
1392
1716
|
sandboxHost: hostname,
|
|
1393
1717
|
appOriginPort,
|
|
1718
|
+
sandboxPublicUrl,
|
|
1719
|
+
appOriginPublicOrigin,
|
|
1394
1720
|
logger,
|
|
1395
1721
|
autoOpen: resolveAutoOpen()
|
|
1396
1722
|
};
|
|
@@ -1542,6 +1868,7 @@ function redactBody(body, contentType) {
|
|
|
1542
1868
|
return body;
|
|
1543
1869
|
}
|
|
1544
1870
|
function findHeader(headers, name) {
|
|
1871
|
+
if (!headers) return void 0;
|
|
1545
1872
|
const target = name.toLowerCase();
|
|
1546
1873
|
for (const [key, value] of Object.entries(headers)) {
|
|
1547
1874
|
if (key.toLowerCase() === target) return value;
|
|
@@ -1549,9 +1876,146 @@ function findHeader(headers, name) {
|
|
|
1549
1876
|
return void 0;
|
|
1550
1877
|
}
|
|
1551
1878
|
function isLongLivedStreamResponse(method, contentType) {
|
|
1552
|
-
if (method !== "GET") return false;
|
|
1879
|
+
if (method.toUpperCase() !== "GET") return false;
|
|
1553
1880
|
if (!contentType) return false;
|
|
1554
|
-
|
|
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();
|
|
1555
2019
|
}
|
|
1556
2020
|
function createFetchTracker(baseFetch, callbacks) {
|
|
1557
2021
|
return async (input, init) => {
|
|
@@ -1559,7 +2023,7 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
1559
2023
|
const timestamp = /* @__PURE__ */ new Date();
|
|
1560
2024
|
const id = `${timestamp.getTime()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1561
2025
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
1562
|
-
const method = init?.method
|
|
2026
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
1563
2027
|
const rawRequestHeaders = {};
|
|
1564
2028
|
if (input instanceof Request) {
|
|
1565
2029
|
input.headers.forEach((value, key) => {
|
|
@@ -1639,7 +2103,21 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
1639
2103
|
responseBody: void 0,
|
|
1640
2104
|
duration
|
|
1641
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
|
+
}
|
|
1642
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
|
+
}
|
|
1643
2121
|
if (!isLongLivedStream && response.body && !response.bodyUsed) {
|
|
1644
2122
|
const responseContentType = response.headers.get("content-type");
|
|
1645
2123
|
try {
|
|
@@ -1751,7 +2229,7 @@ function createAuthChallengeInterceptFetch(baseFetch) {
|
|
|
1751
2229
|
if (!challenge) {
|
|
1752
2230
|
return response;
|
|
1753
2231
|
}
|
|
1754
|
-
|
|
2232
|
+
void response.body?.cancel().catch(() => {
|
|
1755
2233
|
});
|
|
1756
2234
|
throw new AuthChallengeError(
|
|
1757
2235
|
challenge,
|
|
@@ -1899,14 +2377,24 @@ function createTransportNode(config, options = {}) {
|
|
|
1899
2377
|
pipeStderr = false,
|
|
1900
2378
|
onFetchRequest,
|
|
1901
2379
|
onFetchResponseBody,
|
|
2380
|
+
onFetchStreamUpdate,
|
|
1902
2381
|
authProvider,
|
|
1903
2382
|
settings,
|
|
1904
2383
|
interceptAuthChallenges = false,
|
|
1905
2384
|
onAuthChallengeObserved
|
|
1906
2385
|
} = options;
|
|
2386
|
+
const wantsFetchTracking = Boolean(
|
|
2387
|
+
onFetchRequest || onFetchResponseBody || onFetchStreamUpdate
|
|
2388
|
+
);
|
|
1907
2389
|
const baseFetch = optionsFetchFn ?? createProxyFetch() ?? globalThis.fetch;
|
|
1908
2390
|
const withChallengeObserver = (inner) => onAuthChallengeObserved ? createAuthChallengeObserverFetch(inner, onAuthChallengeObserved) : inner;
|
|
1909
|
-
const
|
|
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;
|
|
1910
2398
|
if (serverType === "stdio") {
|
|
1911
2399
|
const stdioConfig = config;
|
|
1912
2400
|
const transport = new StdioClientTransport({
|
|
@@ -1932,11 +2420,7 @@ function createTransportNode(config, options = {}) {
|
|
|
1932
2420
|
const sseConfig = config;
|
|
1933
2421
|
const url = new URL(sseConfig.url);
|
|
1934
2422
|
const configuredSseFetch = sseConfig.eventSourceInit?.fetch;
|
|
1935
|
-
const
|
|
1936
|
-
const trackedFetch = onFetchRequest ? createFetchTracker(sseFetch, {
|
|
1937
|
-
trackRequest: onFetchRequest,
|
|
1938
|
-
updateResponseBody: onFetchResponseBody
|
|
1939
|
-
}) : sseFetch;
|
|
2423
|
+
const trackedFetch = configuredSseFetch ? withTracking(withChallengeObserver(configuredSseFetch)) : fetchWithOptionalAuthIntercept;
|
|
1940
2424
|
const headers = headersFromSettings(settings);
|
|
1941
2425
|
const eventSourceInit = {
|
|
1942
2426
|
...sseConfig.eventSourceInit,
|
|
@@ -1947,15 +2431,11 @@ function createTransportNode(config, options = {}) {
|
|
|
1947
2431
|
...sseConfig.requestInit,
|
|
1948
2432
|
...headers && { headers }
|
|
1949
2433
|
};
|
|
1950
|
-
const postFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
|
|
1951
|
-
trackRequest: onFetchRequest,
|
|
1952
|
-
updateResponseBody: onFetchResponseBody
|
|
1953
|
-
}) : fetchWithOptionalAuthIntercept;
|
|
1954
2434
|
const transport = new SSEClientTransport(url, {
|
|
1955
2435
|
authProvider,
|
|
1956
2436
|
eventSourceInit,
|
|
1957
2437
|
requestInit,
|
|
1958
|
-
fetch:
|
|
2438
|
+
fetch: fetchWithOptionalAuthIntercept
|
|
1959
2439
|
});
|
|
1960
2440
|
return { transport };
|
|
1961
2441
|
} else {
|
|
@@ -1966,14 +2446,10 @@ function createTransportNode(config, options = {}) {
|
|
|
1966
2446
|
...httpConfig.requestInit,
|
|
1967
2447
|
...headers && { headers }
|
|
1968
2448
|
};
|
|
1969
|
-
const transportFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
|
|
1970
|
-
trackRequest: onFetchRequest,
|
|
1971
|
-
updateResponseBody: onFetchResponseBody
|
|
1972
|
-
}) : fetchWithOptionalAuthIntercept;
|
|
1973
2449
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
1974
2450
|
authProvider,
|
|
1975
2451
|
requestInit,
|
|
1976
|
-
fetch:
|
|
2452
|
+
fetch: fetchWithOptionalAuthIntercept,
|
|
1977
2453
|
// SEP-2350: how the transport reacts to a `403 insufficient_scope`
|
|
1978
2454
|
// challenge. Defaults to the SDK's `reauthorize` when unset.
|
|
1979
2455
|
...settings?.oauthOnInsufficientScope && {
|
|
@@ -1984,6 +2460,9 @@ function createTransportNode(config, options = {}) {
|
|
|
1984
2460
|
}
|
|
1985
2461
|
}
|
|
1986
2462
|
|
|
2463
|
+
// ../../core/auth/requestTimeout.ts
|
|
2464
|
+
var OAUTH_TIMEOUT_WIRE_CODE = "oauth_request_timeout";
|
|
2465
|
+
|
|
1987
2466
|
// ../../core/json/xMcpHeader.ts
|
|
1988
2467
|
var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
|
|
1989
2468
|
|
|
@@ -2136,6 +2615,20 @@ function resolveImportSource(type, platform, homeDir, readFile2) {
|
|
|
2136
2615
|
return { type, found: false, searched };
|
|
2137
2616
|
}
|
|
2138
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
|
+
|
|
2139
2632
|
// ../../core/mcp/remote/node/remote-session.ts
|
|
2140
2633
|
var RemoteSession = class _RemoteSession {
|
|
2141
2634
|
sessionId;
|
|
@@ -2255,30 +2748,54 @@ var RemoteSession = class _RemoteSession {
|
|
|
2255
2748
|
*/
|
|
2256
2749
|
waitForRequestResponse(requestId, timeoutMs = 6e4) {
|
|
2257
2750
|
return new Promise((resolve9, reject) => {
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
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();
|
|
2266
2770
|
this.requestWaits.set(requestId, {
|
|
2267
2771
|
resolve: () => {
|
|
2268
|
-
|
|
2269
|
-
clearTimeout(timer);
|
|
2270
|
-
}
|
|
2772
|
+
clear();
|
|
2271
2773
|
resolve9();
|
|
2272
2774
|
},
|
|
2273
2775
|
reject: (error) => {
|
|
2274
|
-
|
|
2275
|
-
clearTimeout(timer);
|
|
2276
|
-
}
|
|
2776
|
+
clear();
|
|
2277
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();
|
|
2278
2787
|
}
|
|
2279
2788
|
});
|
|
2280
2789
|
});
|
|
2281
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
|
+
}
|
|
2282
2799
|
cancelRequestWait(requestId) {
|
|
2283
2800
|
const wait = this.requestWaits.get(requestId);
|
|
2284
2801
|
if (!wait) {
|
|
@@ -2333,6 +2850,10 @@ var RemoteSession = class _RemoteSession {
|
|
|
2333
2850
|
if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) {
|
|
2334
2851
|
this.settleRequestWait(message.id);
|
|
2335
2852
|
}
|
|
2853
|
+
const progressToken = progressTokenOf(message);
|
|
2854
|
+
if (progressToken !== void 0) {
|
|
2855
|
+
this.noteRequestProgress(progressToken);
|
|
2856
|
+
}
|
|
2336
2857
|
this.pushEvent({ type: "message", data: message });
|
|
2337
2858
|
}
|
|
2338
2859
|
onFetchRequest(entry) {
|
|
@@ -2350,6 +2871,16 @@ var RemoteSession = class _RemoteSession {
|
|
|
2350
2871
|
data: { id, responseBody }
|
|
2351
2872
|
});
|
|
2352
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
|
+
}
|
|
2353
2884
|
onStderr(entry) {
|
|
2354
2885
|
this.pushEvent({
|
|
2355
2886
|
type: "stdio_log",
|
|
@@ -3816,7 +4347,7 @@ function defaultSecretStore() {
|
|
|
3816
4347
|
}
|
|
3817
4348
|
|
|
3818
4349
|
// ../../core/client/config-parse.ts
|
|
3819
|
-
import { z } from "zod";
|
|
4350
|
+
import { z as z2 } from "zod";
|
|
3820
4351
|
function isAbsoluteHttpUrl(value) {
|
|
3821
4352
|
let url;
|
|
3822
4353
|
try {
|
|
@@ -3833,17 +4364,21 @@ function isRealHost(hostname) {
|
|
|
3833
4364
|
const labels = hostname.split(".");
|
|
3834
4365
|
return labels.length >= 2 && labels.every((label) => label !== "");
|
|
3835
4366
|
}
|
|
3836
|
-
var HttpUrlStringSchema =
|
|
4367
|
+
var HttpUrlStringSchema = z2.string().min(1).superRefine((val, ctx) => {
|
|
3837
4368
|
const trimmed = val.trim();
|
|
3838
4369
|
if (!isAbsoluteHttpUrl(trimmed)) {
|
|
3839
4370
|
ctx.addIssue({
|
|
3840
|
-
code:
|
|
4371
|
+
code: z2.ZodIssueCode.custom,
|
|
3841
4372
|
message: `Invalid URL: "${trimmed}" \u2014 must be an http(s) URL (e.g. https://idp.example.com)`
|
|
3842
4373
|
});
|
|
3843
4374
|
}
|
|
3844
4375
|
});
|
|
3845
4376
|
var CIMD_METADATA_URL_INVALID_ERROR = "Must be a valid URL, like https://example.com/oauth/client.json";
|
|
3846
|
-
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
|
+
}
|
|
3847
4382
|
var CIMD_METADATA_URL_PATH_ERROR = "Must include a path (not the site root), like https://example.com/oauth/client.json";
|
|
3848
4383
|
function getCimdClientMetadataUrlError(value) {
|
|
3849
4384
|
const trimmed = value.trim();
|
|
@@ -3853,7 +4388,7 @@ function getCimdClientMetadataUrlError(value) {
|
|
|
3853
4388
|
}
|
|
3854
4389
|
try {
|
|
3855
4390
|
const url = new URL(trimmed);
|
|
3856
|
-
if (url.protocol !== "https:") {
|
|
4391
|
+
if (url.protocol !== "https:" && !isCimdHttpExemptHost(url.hostname)) {
|
|
3857
4392
|
return CIMD_METADATA_URL_HTTPS_ERROR;
|
|
3858
4393
|
}
|
|
3859
4394
|
if (url.pathname === "/" || url.pathname === "") {
|
|
@@ -3869,7 +4404,7 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
3869
4404
|
if (trimmed === "") {
|
|
3870
4405
|
if (required) {
|
|
3871
4406
|
ctx.addIssue({
|
|
3872
|
-
code:
|
|
4407
|
+
code: z2.ZodIssueCode.custom,
|
|
3873
4408
|
message: "CIMD client metadata URL is required when CIMD is enabled"
|
|
3874
4409
|
});
|
|
3875
4410
|
}
|
|
@@ -3878,25 +4413,25 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
3878
4413
|
const error = getCimdClientMetadataUrlError(trimmed);
|
|
3879
4414
|
if (error) {
|
|
3880
4415
|
ctx.addIssue({
|
|
3881
|
-
code:
|
|
4416
|
+
code: z2.ZodIssueCode.custom,
|
|
3882
4417
|
message: error
|
|
3883
4418
|
});
|
|
3884
4419
|
}
|
|
3885
4420
|
}
|
|
3886
|
-
var CimdConfigSchema =
|
|
3887
|
-
enabled:
|
|
3888
|
-
clientMetadataUrl:
|
|
4421
|
+
var CimdConfigSchema = z2.object({
|
|
4422
|
+
enabled: z2.boolean(),
|
|
4423
|
+
clientMetadataUrl: z2.string()
|
|
3889
4424
|
}).superRefine((data, ctx) => {
|
|
3890
4425
|
refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
|
|
3891
4426
|
});
|
|
3892
|
-
var EnterpriseManagedAuthIdpConfigSchema =
|
|
4427
|
+
var EnterpriseManagedAuthIdpConfigSchema = z2.object({
|
|
3893
4428
|
issuer: HttpUrlStringSchema,
|
|
3894
|
-
clientId:
|
|
3895
|
-
clientSecret:
|
|
4429
|
+
clientId: z2.string().min(1),
|
|
4430
|
+
clientSecret: z2.string().optional()
|
|
3896
4431
|
});
|
|
3897
|
-
var ClientConfigSchema =
|
|
3898
|
-
enterpriseManagedAuth:
|
|
3899
|
-
enabled:
|
|
4432
|
+
var ClientConfigSchema = z2.object({
|
|
4433
|
+
enterpriseManagedAuth: z2.object({
|
|
4434
|
+
enabled: z2.boolean().optional(),
|
|
3900
4435
|
idp: EnterpriseManagedAuthIdpConfigSchema
|
|
3901
4436
|
}).optional(),
|
|
3902
4437
|
cimd: CimdConfigSchema.optional()
|
|
@@ -3905,7 +4440,7 @@ function parseClientConfig(raw) {
|
|
|
3905
4440
|
return ClientConfigSchema.parse(raw);
|
|
3906
4441
|
}
|
|
3907
4442
|
function formatClientConfigLoadError(error) {
|
|
3908
|
-
if (error instanceof
|
|
4443
|
+
if (error instanceof z2.ZodError) {
|
|
3909
4444
|
const issue = error.issues[0];
|
|
3910
4445
|
if (issue) {
|
|
3911
4446
|
const field = issue.path.length > 0 ? issue.path.map(String).join(".") : "config";
|
|
@@ -4390,6 +4925,7 @@ function createRemoteApp(options) {
|
|
|
4390
4925
|
onStderr: (entry) => session.onStderr(entry),
|
|
4391
4926
|
onFetchRequest: (entry) => session.onFetchRequest(entry),
|
|
4392
4927
|
onFetchResponseBody: (id, body2) => session.onFetchResponseBody(id, body2),
|
|
4928
|
+
onFetchStreamUpdate: (id, stream) => session.onFetchStreamUpdate(id, stream),
|
|
4393
4929
|
authProvider: authHandle?.provider,
|
|
4394
4930
|
settings: body.settings,
|
|
4395
4931
|
// Always intercept 401/403 on the node MCP transport. Without this, the
|
|
@@ -4624,15 +5160,36 @@ function createRemoteApp(options) {
|
|
|
4624
5160
|
} catch {
|
|
4625
5161
|
return c.json({ error: "Invalid JSON body" }, 400);
|
|
4626
5162
|
}
|
|
4627
|
-
const {
|
|
5163
|
+
const {
|
|
5164
|
+
url,
|
|
5165
|
+
method = "GET",
|
|
5166
|
+
headers = {},
|
|
5167
|
+
body: reqBody,
|
|
5168
|
+
timeoutMs: requestedTimeoutMs
|
|
5169
|
+
} = body;
|
|
4628
5170
|
if (!url) {
|
|
4629
5171
|
return c.json({ error: "Missing url" }, 400);
|
|
4630
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);
|
|
4631
5187
|
try {
|
|
4632
5188
|
const res = await (createProxyFetch() ?? fetch)(url, {
|
|
4633
5189
|
method,
|
|
4634
5190
|
headers: new Headers(headers),
|
|
4635
|
-
body: reqBody
|
|
5191
|
+
body: reqBody,
|
|
5192
|
+
signal
|
|
4636
5193
|
});
|
|
4637
5194
|
const resHeaders = {};
|
|
4638
5195
|
res.headers.forEach((v, k) => {
|
|
@@ -4643,6 +5200,9 @@ function createRemoteApp(options) {
|
|
|
4643
5200
|
let resBody;
|
|
4644
5201
|
if (!isStream && res.body) {
|
|
4645
5202
|
resBody = await res.text();
|
|
5203
|
+
} else if (isStream) {
|
|
5204
|
+
void res.body?.cancel().catch(() => {
|
|
5205
|
+
});
|
|
4646
5206
|
}
|
|
4647
5207
|
return c.json({
|
|
4648
5208
|
ok: res.ok,
|
|
@@ -4653,7 +5213,20 @@ function createRemoteApp(options) {
|
|
|
4653
5213
|
});
|
|
4654
5214
|
} catch (err) {
|
|
4655
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
|
+
}
|
|
4656
5227
|
return c.json({ error: msg }, 500);
|
|
5228
|
+
} finally {
|
|
5229
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4657
5230
|
}
|
|
4658
5231
|
});
|
|
4659
5232
|
app.post("/api/log", async (c) => {
|
|
@@ -4849,6 +5422,18 @@ function createRemoteApp(options) {
|
|
|
4849
5422
|
);
|
|
4850
5423
|
delete valObj.maxFetchRequests;
|
|
4851
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
|
+
}
|
|
4852
5437
|
if ("oauth" in valObj && !isOauthObject(valObj.oauth)) {
|
|
4853
5438
|
logWarn(
|
|
4854
5439
|
{ route: "/api/servers", id, droppedKey: "oauth" },
|
|
@@ -5005,6 +5590,17 @@ function createRemoteApp(options) {
|
|
|
5005
5590
|
error: "settings.maxFetchRequests must be a non-negative number"
|
|
5006
5591
|
};
|
|
5007
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
|
+
}
|
|
5008
5604
|
for (const optional of [
|
|
5009
5605
|
"oauthClientId",
|
|
5010
5606
|
"oauthClientSecret",
|
|
@@ -5100,6 +5696,12 @@ function createRemoteApp(options) {
|
|
|
5100
5696
|
// writes no spurious `roots` field.
|
|
5101
5697
|
roots: isRootArray(obj.roots) ? obj.roots : []
|
|
5102
5698
|
};
|
|
5699
|
+
if (isSkillCatalogLimit(obj.skillCatalogMaxSkills)) {
|
|
5700
|
+
value.skillCatalogMaxSkills = obj.skillCatalogMaxSkills;
|
|
5701
|
+
}
|
|
5702
|
+
if (isSkillCatalogLimit(obj.skillCatalogMaxBytes)) {
|
|
5703
|
+
value.skillCatalogMaxBytes = obj.skillCatalogMaxBytes;
|
|
5704
|
+
}
|
|
5103
5705
|
if (typeof obj.oauthClientId === "string" && obj.oauthClientId !== "") {
|
|
5104
5706
|
value.oauthClientId = obj.oauthClientId;
|
|
5105
5707
|
}
|
|
@@ -5662,12 +6264,14 @@ function honoMiddlewarePlugin(config) {
|
|
|
5662
6264
|
const sandboxController = createSandboxController({
|
|
5663
6265
|
port: config.sandboxPort,
|
|
5664
6266
|
host: config.sandboxHost,
|
|
5665
|
-
allowedOrigins: config.allowedOrigins
|
|
6267
|
+
allowedOrigins: config.allowedOrigins,
|
|
6268
|
+
publicUrl: config.sandboxPublicUrl
|
|
5666
6269
|
});
|
|
5667
6270
|
await sandboxController.start();
|
|
5668
6271
|
const appOriginController = createAppOriginController({
|
|
5669
6272
|
port: config.appOriginPort,
|
|
5670
6273
|
host: config.sandboxHost,
|
|
6274
|
+
publicOrigin: config.appOriginPublicOrigin,
|
|
5671
6275
|
embedderOrigins: appDocumentEmbedders(
|
|
5672
6276
|
sandboxController.getUrl(),
|
|
5673
6277
|
config.allowedOrigins
|
|
@@ -5885,6 +6489,33 @@ function clearViteDepsCache(clientWebRoot) {
|
|
|
5885
6489
|
// ../../vitest.shared.mts
|
|
5886
6490
|
import path5 from "path";
|
|
5887
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
|
+
});
|
|
5888
6519
|
function vitestSharedPaths(clientDir) {
|
|
5889
6520
|
const dirname10 = path5.resolve(clientDir);
|
|
5890
6521
|
const repoRoot = path5.resolve(dirname10, "../..");
|
|
@@ -5940,9 +6571,15 @@ function vitestSharedPaths(clientDir) {
|
|
|
5940
6571
|
// is a statement about the installed tree, not about the manifests — check
|
|
5941
6572
|
// the tree before adding or removing an entry.
|
|
5942
6573
|
//
|
|
5943
|
-
// `express`
|
|
5944
|
-
//
|
|
5945
|
-
//
|
|
6574
|
+
// `express` is reached only through `test-servers/src` (the http/oauth
|
|
6575
|
+
// servers), which is root-owned code with no manifest of its own.
|
|
6576
|
+
//
|
|
6577
|
+
// `yaml` was too — `load-config.ts` — but is now also a `core/` runtime
|
|
6578
|
+
// import: `core/mcp/skillFile.ts` parses a served SKILL.md's frontmatter
|
|
6579
|
+
// for the SEP-2640 cross-check (#2248). That matters to anyone revisiting
|
|
6580
|
+
// this pin: it is no longer removable by retiring a test-server path, and
|
|
6581
|
+
// as a dependency `core/` imports it is additionally named in all three
|
|
6582
|
+
// bundler `external` lists.
|
|
5946
6583
|
//
|
|
5947
6584
|
// Pointing these at `<client>/node_modules` is what broke when the MCP
|
|
5948
6585
|
// packages moved to the root (#1970): express was never declared by a client
|
|
@@ -6121,12 +6758,14 @@ async function startHonoServer(config) {
|
|
|
6121
6758
|
const sandboxController = createSandboxController({
|
|
6122
6759
|
port: config.sandboxPort,
|
|
6123
6760
|
host: config.sandboxHost,
|
|
6124
|
-
allowedOrigins: config.allowedOrigins
|
|
6761
|
+
allowedOrigins: config.allowedOrigins,
|
|
6762
|
+
publicUrl: config.sandboxPublicUrl
|
|
6125
6763
|
});
|
|
6126
6764
|
await sandboxController.start();
|
|
6127
6765
|
const appOriginController = createAppOriginController({
|
|
6128
6766
|
port: config.appOriginPort,
|
|
6129
6767
|
host: config.sandboxHost,
|
|
6768
|
+
publicOrigin: config.appOriginPublicOrigin,
|
|
6130
6769
|
embedderOrigins: appDocumentEmbedders(
|
|
6131
6770
|
sandboxController.getUrl(),
|
|
6132
6771
|
config.allowedOrigins
|
|
@@ -6299,6 +6938,10 @@ async function runWeb(argv) {
|
|
|
6299
6938
|
'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
|
|
6300
6939
|
parseHeaderPair,
|
|
6301
6940
|
{}
|
|
6941
|
+
).option(
|
|
6942
|
+
"--protocol-era <era>",
|
|
6943
|
+
"protocol era for the ad-hoc server: legacy, auto, or modern (default legacy)",
|
|
6944
|
+
parseProtocolEra
|
|
6302
6945
|
).option("--dev", "run in development mode (Vite)").parse(preArgs);
|
|
6303
6946
|
const opts = program.opts();
|
|
6304
6947
|
const args = program.args;
|
|
@@ -6308,8 +6951,9 @@ async function runWeb(argv) {
|
|
|
6308
6951
|
const hasCatalog = !!catalogPath;
|
|
6309
6952
|
const hasConfig = !!opts.config;
|
|
6310
6953
|
const hasHeaders = !!opts.header && Object.keys(opts.header).length > 0;
|
|
6954
|
+
const hasProtocolEra = opts.protocolEra !== void 0;
|
|
6311
6955
|
const hasAdHocServer = target.length > 0 || !!opts.serverUrl || !!opts.transport && opts.transport !== "stdio";
|
|
6312
|
-
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;
|
|
6313
6957
|
if (conflict) {
|
|
6314
6958
|
console.error(`Error: ${conflict}`);
|
|
6315
6959
|
process.exit(1);
|
|
@@ -6374,6 +7018,9 @@ async function runWeb(argv) {
|
|
|
6374
7018
|
}
|
|
6375
7019
|
initialMcpConfig = config;
|
|
6376
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
|
+
}
|
|
6377
7024
|
initialServers = {
|
|
6378
7025
|
mcpServers: { [deriveSeedServerId(config)]: entry }
|
|
6379
7026
|
};
|
|
@@ -6383,6 +7030,11 @@ async function runWeb(argv) {
|
|
|
6383
7030
|
"Error: --header requires an ad-hoc --server-url or command target."
|
|
6384
7031
|
);
|
|
6385
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);
|
|
6386
7038
|
}
|
|
6387
7039
|
let webConfig;
|
|
6388
7040
|
try {
|