@modelcontextprotocol/inspector 2.6.0 → 2.8.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/LICENSE +216 -0
- package/README.md +9 -3
- package/clients/cli/build/index.js +5834 -4829
- package/clients/tui/build/index.js +5501 -4466
- package/clients/web/build/index.js +886 -82
- package/clients/web/dist/assets/index-9ZWu1ACP.js +1598 -0
- package/clients/web/dist/assets/{prism-light-9SaQe5zv.js → prism-light-CGBvpchP.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/clients/web/static/sandbox_proxy.html +30 -4
- package/package.json +14 -10
- package/clients/web/dist/assets/index-30pI79Cf.js +0 -1595
|
@@ -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";
|
|
@@ -108,6 +109,140 @@ var MODERN_LOG_LEVELS = [
|
|
|
108
109
|
function isModernLogLevel(value) {
|
|
109
110
|
return typeof value === "string" && MODERN_LOG_LEVELS.includes(value);
|
|
110
111
|
}
|
|
112
|
+
var MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
113
|
+
|
|
114
|
+
// ../../core/mcp/skillsSchemas.ts
|
|
115
|
+
import { z } from "zod/v4";
|
|
116
|
+
import { ResourceSchema } from "@modelcontextprotocol/core";
|
|
117
|
+
var DYNAMIC_RESOURCES = "dynamic";
|
|
118
|
+
var SkillFrontmatterSchema = z.looseObject({
|
|
119
|
+
name: z.string().optional(),
|
|
120
|
+
description: z.string().optional()
|
|
121
|
+
});
|
|
122
|
+
var SkillResourceSchema = z.looseObject({
|
|
123
|
+
uri: z.string(),
|
|
124
|
+
digest: z.string().optional(),
|
|
125
|
+
size: z.number().optional()
|
|
126
|
+
});
|
|
127
|
+
var SkillEntrySchema = z.looseObject({
|
|
128
|
+
uri: z.string(),
|
|
129
|
+
frontmatter: SkillFrontmatterSchema,
|
|
130
|
+
resources: z.union([
|
|
131
|
+
z.literal(DYNAMIC_RESOURCES),
|
|
132
|
+
z.array(SkillResourceSchema)
|
|
133
|
+
])
|
|
134
|
+
});
|
|
135
|
+
var ListSkillsResultSchema = z.looseObject({
|
|
136
|
+
skills: z.array(SkillEntrySchema),
|
|
137
|
+
nextCursor: z.string().optional()
|
|
138
|
+
});
|
|
139
|
+
var ModernListSkillsResultSchema = ListSkillsResultSchema.extend({
|
|
140
|
+
// A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
|
|
141
|
+
// envelope violation, and accepting it here would hide exactly the kind of
|
|
142
|
+
// defect this schema exists to surface.
|
|
143
|
+
ttlMs: z.int().min(0),
|
|
144
|
+
cacheScope: z.enum(["public", "private"])
|
|
145
|
+
});
|
|
146
|
+
var GetSkillEnvelopeSchema = z.looseObject({
|
|
147
|
+
skill: SkillEntrySchema
|
|
148
|
+
});
|
|
149
|
+
var GetSkillResultSchema = GetSkillEnvelopeSchema.transform(
|
|
150
|
+
(result) => result.skill
|
|
151
|
+
);
|
|
152
|
+
var DirectoryChildSchema = ResourceSchema;
|
|
153
|
+
var DirectoryReadResultSchema = z.looseObject({
|
|
154
|
+
resources: z.array(DirectoryChildSchema),
|
|
155
|
+
nextCursor: z.string().optional()
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ../../core/mcp/sha256.ts
|
|
159
|
+
var K = new Uint32Array([
|
|
160
|
+
1116352408,
|
|
161
|
+
1899447441,
|
|
162
|
+
3049323471,
|
|
163
|
+
3921009573,
|
|
164
|
+
961987163,
|
|
165
|
+
1508970993,
|
|
166
|
+
2453635748,
|
|
167
|
+
2870763221,
|
|
168
|
+
3624381080,
|
|
169
|
+
310598401,
|
|
170
|
+
607225278,
|
|
171
|
+
1426881987,
|
|
172
|
+
1925078388,
|
|
173
|
+
2162078206,
|
|
174
|
+
2614888103,
|
|
175
|
+
3248222580,
|
|
176
|
+
3835390401,
|
|
177
|
+
4022224774,
|
|
178
|
+
264347078,
|
|
179
|
+
604807628,
|
|
180
|
+
770255983,
|
|
181
|
+
1249150122,
|
|
182
|
+
1555081692,
|
|
183
|
+
1996064986,
|
|
184
|
+
2554220882,
|
|
185
|
+
2821834349,
|
|
186
|
+
2952996808,
|
|
187
|
+
3210313671,
|
|
188
|
+
3336571891,
|
|
189
|
+
3584528711,
|
|
190
|
+
113926993,
|
|
191
|
+
338241895,
|
|
192
|
+
666307205,
|
|
193
|
+
773529912,
|
|
194
|
+
1294757372,
|
|
195
|
+
1396182291,
|
|
196
|
+
1695183700,
|
|
197
|
+
1986661051,
|
|
198
|
+
2177026350,
|
|
199
|
+
2456956037,
|
|
200
|
+
2730485921,
|
|
201
|
+
2820302411,
|
|
202
|
+
3259730800,
|
|
203
|
+
3345764771,
|
|
204
|
+
3516065817,
|
|
205
|
+
3600352804,
|
|
206
|
+
4094571909,
|
|
207
|
+
275423344,
|
|
208
|
+
430227734,
|
|
209
|
+
506948616,
|
|
210
|
+
659060556,
|
|
211
|
+
883997877,
|
|
212
|
+
958139571,
|
|
213
|
+
1322822218,
|
|
214
|
+
1537002063,
|
|
215
|
+
1747873779,
|
|
216
|
+
1955562222,
|
|
217
|
+
2024104815,
|
|
218
|
+
2227730452,
|
|
219
|
+
2361852424,
|
|
220
|
+
2428436474,
|
|
221
|
+
2756734187,
|
|
222
|
+
3204031479,
|
|
223
|
+
3329325298
|
|
224
|
+
]);
|
|
225
|
+
var H0 = new Uint32Array([
|
|
226
|
+
1779033703,
|
|
227
|
+
3144134277,
|
|
228
|
+
1013904242,
|
|
229
|
+
2773480762,
|
|
230
|
+
1359893119,
|
|
231
|
+
2600822924,
|
|
232
|
+
528734635,
|
|
233
|
+
1541459225
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
// ../../core/mcp/skillFile.ts
|
|
237
|
+
import { parse as parseYaml } from "yaml";
|
|
238
|
+
|
|
239
|
+
// ../../core/mcp/skills.ts
|
|
240
|
+
var SKILL_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
|
|
241
|
+
var SKILL_MAX_CATALOG_SKILLS = 256;
|
|
242
|
+
var SKILL_MAX_CATALOG_BYTES = 64 * 1024 * 1024;
|
|
243
|
+
function isSkillCatalogLimit(value) {
|
|
244
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
245
|
+
}
|
|
111
246
|
|
|
112
247
|
// ../../core/auth/secret-fields.ts
|
|
113
248
|
var SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
|
|
@@ -290,16 +425,19 @@ function cleanEndpointOverride(value, field) {
|
|
|
290
425
|
return value.trim() || void 0;
|
|
291
426
|
}
|
|
292
427
|
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;
|
|
428
|
+
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.suppressNotificationStream !== 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
429
|
if (!hasAny) return void 0;
|
|
295
430
|
const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
|
|
296
431
|
const settings = {
|
|
297
432
|
headers: headersPairs,
|
|
298
433
|
env: envRecordToPairs(stored.env),
|
|
299
434
|
metadata: normalizeStoredMetadata(stored.metadata),
|
|
300
|
-
|
|
435
|
+
// Concrete product default, like taskTtl below: an absent field reads back
|
|
436
|
+
// as 30 s so the form shows the bound that will actually apply. An explicit
|
|
437
|
+
// 0 is preserved as-is — it is the user's opt-out, not a sentinel (#2320).
|
|
438
|
+
connectionTimeout: stored.connectionTimeout ?? DEFAULT_CONNECTION_TIMEOUT_MS,
|
|
301
439
|
requestTimeout: stored.requestTimeout ?? 0,
|
|
302
|
-
// Unlike
|
|
440
|
+
// Unlike requestTimeout (0 = "SDK default"), task TTL has a concrete product
|
|
303
441
|
// default so the form shows it and "Run as task" has a value to send.
|
|
304
442
|
taskTtl: stored.taskTtl ?? DEFAULT_TASK_TTL_MS,
|
|
305
443
|
autoRefreshOnListChanged: stored.autoRefreshOnListChanged ?? false,
|
|
@@ -313,6 +451,15 @@ function storedFieldsToInspectorSettings(stored) {
|
|
|
313
451
|
// `[]`, which `inspectorSettingsToStoredFields` then omits on write.
|
|
314
452
|
roots: stored.roots ?? []
|
|
315
453
|
};
|
|
454
|
+
if (isSkillCatalogLimit(stored.skillCatalogMaxSkills)) {
|
|
455
|
+
settings.skillCatalogMaxSkills = stored.skillCatalogMaxSkills;
|
|
456
|
+
}
|
|
457
|
+
if (isSkillCatalogLimit(stored.skillCatalogMaxBytes)) {
|
|
458
|
+
settings.skillCatalogMaxBytes = stored.skillCatalogMaxBytes;
|
|
459
|
+
}
|
|
460
|
+
if (stored.suppressNotificationStream === true) {
|
|
461
|
+
settings.suppressNotificationStream = true;
|
|
462
|
+
}
|
|
316
463
|
if (isProtocolEra(stored.protocolEra)) {
|
|
317
464
|
settings.protocolEra = stored.protocolEra;
|
|
318
465
|
}
|
|
@@ -367,7 +514,7 @@ function inspectorSettingsToStoredFields(settings) {
|
|
|
367
514
|
if (Object.keys(settings.metadata).length > 0) {
|
|
368
515
|
out.metadata = settings.metadata;
|
|
369
516
|
}
|
|
370
|
-
if (settings.connectionTimeout
|
|
517
|
+
if (settings.connectionTimeout !== DEFAULT_CONNECTION_TIMEOUT_MS) {
|
|
371
518
|
out.connectionTimeout = settings.connectionTimeout;
|
|
372
519
|
}
|
|
373
520
|
if (settings.requestTimeout > 0) {
|
|
@@ -382,6 +529,9 @@ function inspectorSettingsToStoredFields(settings) {
|
|
|
382
529
|
if (settings.paginatedLists) {
|
|
383
530
|
out.paginatedLists = true;
|
|
384
531
|
}
|
|
532
|
+
if (settings.suppressNotificationStream) {
|
|
533
|
+
out.suppressNotificationStream = true;
|
|
534
|
+
}
|
|
385
535
|
if (settings.advertisedExtensions && Object.keys(settings.advertisedExtensions).length > 0) {
|
|
386
536
|
out.advertisedExtensions = { ...settings.advertisedExtensions };
|
|
387
537
|
}
|
|
@@ -394,6 +544,12 @@ function inspectorSettingsToStoredFields(settings) {
|
|
|
394
544
|
if (settings.maxFetchRequests !== DEFAULT_MAX_FETCH_REQUESTS) {
|
|
395
545
|
out.maxFetchRequests = settings.maxFetchRequests;
|
|
396
546
|
}
|
|
547
|
+
if (isSkillCatalogLimit(settings.skillCatalogMaxSkills) && settings.skillCatalogMaxSkills !== SKILL_MAX_CATALOG_SKILLS) {
|
|
548
|
+
out.skillCatalogMaxSkills = settings.skillCatalogMaxSkills;
|
|
549
|
+
}
|
|
550
|
+
if (isSkillCatalogLimit(settings.skillCatalogMaxBytes) && settings.skillCatalogMaxBytes !== SKILL_MAX_CATALOG_BYTES) {
|
|
551
|
+
out.skillCatalogMaxBytes = settings.skillCatalogMaxBytes;
|
|
552
|
+
}
|
|
397
553
|
const oauthFields = {};
|
|
398
554
|
if (settings.oauthClientId) oauthFields.clientId = settings.oauthClientId;
|
|
399
555
|
if (settings.oauthClientSecret)
|
|
@@ -442,8 +598,11 @@ var INSPECTOR_FIELD_KEY_MAP = {
|
|
|
442
598
|
taskTtl: true,
|
|
443
599
|
autoRefreshOnListChanged: true,
|
|
444
600
|
paginatedLists: true,
|
|
601
|
+
suppressNotificationStream: true,
|
|
445
602
|
advertisedExtensions: true,
|
|
446
603
|
maxFetchRequests: true,
|
|
604
|
+
skillCatalogMaxSkills: true,
|
|
605
|
+
skillCatalogMaxBytes: true,
|
|
447
606
|
oauth: true,
|
|
448
607
|
roots: true
|
|
449
608
|
};
|
|
@@ -565,6 +724,14 @@ function parseHeaderPair(value, previous = {}) {
|
|
|
565
724
|
}
|
|
566
725
|
return { ...previous, [key]: val };
|
|
567
726
|
}
|
|
727
|
+
function parseProtocolEra(value) {
|
|
728
|
+
if (!isProtocolEra(value)) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
`Invalid protocol era: ${value}. Valid eras are: legacy, auto, modern`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
return value;
|
|
734
|
+
}
|
|
568
735
|
var EMPTY_CATALOG_CONTENT = `${JSON.stringify({ mcpServers: {} }, null, 2)}
|
|
569
736
|
`;
|
|
570
737
|
function seedEmptyCatalog(resolvedPath) {
|
|
@@ -795,7 +962,7 @@ function secretStorageCaveat(info) {
|
|
|
795
962
|
return `The secrets file could not be read (${info.encryptionUnknown}). Saving a secret will fail rather than overwrite it.`;
|
|
796
963
|
}
|
|
797
964
|
if (info.plaintext) {
|
|
798
|
-
return info.pendingEncryption ? "Existing secrets in this file are still unencrypted (file mode 0600). They are re-encrypted the next time a secret is saved." : "Secrets are stored unencrypted (file mode 0600). Set MCP_INSPECTOR_SECRET_KEY to encrypt them.";
|
|
965
|
+
return info.pendingEncryption ? "Existing secrets in this file are still unencrypted (file mode 0600). They are re-encrypted the next time a secret is saved." : "Secrets are stored unencrypted (file mode 0600). Set MCP_INSPECTOR_SECRET_KEY or MCP_INSPECTOR_SECRET_KEY_FILE to encrypt them.";
|
|
799
966
|
}
|
|
800
967
|
return void 0;
|
|
801
968
|
}
|
|
@@ -899,14 +1066,14 @@ function isAllInterfacesHost(host) {
|
|
|
899
1066
|
|
|
900
1067
|
// server/resolve-bind-host.ts
|
|
901
1068
|
var BIND_ALL_INTERFACES_ENV = "DANGEROUSLY_BIND_ALL_INTERFACES";
|
|
902
|
-
function
|
|
1069
|
+
function isEnvFlagEnabled(value) {
|
|
903
1070
|
const v = value?.trim().toLowerCase();
|
|
904
1071
|
return v === "true" || v === "1";
|
|
905
1072
|
}
|
|
906
1073
|
var DEFAULT_BIND_HOST = "127.0.0.1";
|
|
907
1074
|
function resolveBindHostname(env = process.env) {
|
|
908
1075
|
const host = (env.HOST ?? DEFAULT_BIND_HOST).trim();
|
|
909
|
-
if (isAllInterfacesHost(host) && !
|
|
1076
|
+
if (isAllInterfacesHost(host) && !isEnvFlagEnabled(env[BIND_ALL_INTERFACES_ENV])) {
|
|
910
1077
|
const resolved2 = canonicalUrlHost(host);
|
|
911
1078
|
const shown = stripBrackets(resolved2) === stripBrackets(host.toLowerCase()) ? `HOST="${host}"` : `HOST="${host}" (resolves to ${resolved2})`;
|
|
912
1079
|
throw new Error(
|
|
@@ -924,10 +1091,25 @@ function sandboxFrameAncestors(allowedOrigins) {
|
|
|
924
1091
|
return frameAncestorsDirective(allowedOrigins);
|
|
925
1092
|
}
|
|
926
1093
|
function frameAncestorsDirective(origins) {
|
|
927
|
-
const valid = (origins
|
|
1094
|
+
const valid = validHostSources(origins);
|
|
928
1095
|
const sources = valid.length > 0 ? valid : LOOPBACK_FRAME_ANCESTORS;
|
|
929
1096
|
return `frame-ancestors ${sources.join(" ")}`;
|
|
930
1097
|
}
|
|
1098
|
+
function validHostSources(origins) {
|
|
1099
|
+
return (origins ?? []).filter((o) => CSP_HOST_SOURCE.test(o));
|
|
1100
|
+
}
|
|
1101
|
+
var EMBEDDER_ORIGINS_PLACEHOLDER = "/*INSPECTOR_EMBEDDER_ORIGINS*/ null";
|
|
1102
|
+
function embedderOriginsLiteral(origins) {
|
|
1103
|
+
const valid = validHostSources(origins);
|
|
1104
|
+
if (valid.length === 0) return "null";
|
|
1105
|
+
return JSON.stringify(valid).replace(/</g, "\\u003c");
|
|
1106
|
+
}
|
|
1107
|
+
function renderSandboxProxyHtml(html, origins) {
|
|
1108
|
+
return html.replace(
|
|
1109
|
+
EMBEDDER_ORIGINS_PLACEHOLDER,
|
|
1110
|
+
() => embedderOriginsLiteral(origins)
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
931
1113
|
function parseListenPort(raw) {
|
|
932
1114
|
const v = raw?.trim();
|
|
933
1115
|
if (!v || !/^\d+$/.test(v)) return void 0;
|
|
@@ -945,22 +1127,25 @@ function resolveSandboxPort() {
|
|
|
945
1127
|
return fromSandbox ?? parseListenPort(process.env.SERVER_PORT) ?? DEFAULT_SANDBOX_PORT;
|
|
946
1128
|
}
|
|
947
1129
|
function createSandboxController(options) {
|
|
948
|
-
const { port, host = DEFAULT_BIND_HOST, allowedOrigins } = options;
|
|
1130
|
+
const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl } = options;
|
|
949
1131
|
let server = null;
|
|
950
1132
|
let sandboxUrl = null;
|
|
1133
|
+
let boundPort = 0;
|
|
951
1134
|
const SANDBOX_PROXY_CSP = sandboxFrameAncestors(allowedOrigins);
|
|
952
1135
|
let sandboxHtml;
|
|
953
1136
|
try {
|
|
954
1137
|
const sandboxHtmlPath = join3(__dirname, "../static/sandbox_proxy.html");
|
|
955
|
-
sandboxHtml =
|
|
1138
|
+
sandboxHtml = renderSandboxProxyHtml(
|
|
1139
|
+
readFileSync3(sandboxHtmlPath, "utf-8"),
|
|
1140
|
+
allowedOrigins
|
|
1141
|
+
);
|
|
956
1142
|
} catch (e) {
|
|
957
1143
|
sandboxHtml = "<!DOCTYPE html><html><body>Sandbox not loaded: " + String(e.message) + "</body></html>";
|
|
958
1144
|
}
|
|
959
1145
|
return {
|
|
960
1146
|
async start() {
|
|
961
1147
|
if (server && sandboxUrl) {
|
|
962
|
-
|
|
963
|
-
return { port: p, url: sandboxUrl };
|
|
1148
|
+
return { port: boundPort, url: sandboxUrl };
|
|
964
1149
|
}
|
|
965
1150
|
return new Promise((resolve9) => {
|
|
966
1151
|
let settled = false;
|
|
@@ -1014,7 +1199,15 @@ function createSandboxController(options) {
|
|
|
1014
1199
|
);
|
|
1015
1200
|
const canonicalHost = canonicalUrlHost(host);
|
|
1016
1201
|
const urlHost = isAllInterfacesHost(canonicalHost) ? "localhost" : canonicalHost;
|
|
1017
|
-
|
|
1202
|
+
const derivedUrl = `http://${urlHost}:${actualPort}/sandbox`;
|
|
1203
|
+
const onFixedPort = port !== 0 && !retriedDynamic;
|
|
1204
|
+
if (publicUrl && !onFixedPort) {
|
|
1205
|
+
console.warn(
|
|
1206
|
+
`Sandbox: not advertising MCP_SANDBOX_FULL_ADDRESS, since the sandbox is not on a fixed port for it to route to; advertising ${derivedUrl} instead.`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
sandboxUrl = publicUrl && onFixedPort ? publicUrl : derivedUrl;
|
|
1210
|
+
boundPort = actualPort;
|
|
1018
1211
|
settle({ port: actualPort, url: sandboxUrl });
|
|
1019
1212
|
});
|
|
1020
1213
|
});
|
|
@@ -1075,9 +1268,15 @@ function parseDocumentId(url) {
|
|
|
1075
1268
|
return /^[0-9a-f]{32}$/.test(id ?? "") ? id ?? null : null;
|
|
1076
1269
|
}
|
|
1077
1270
|
function createAppOriginController(options) {
|
|
1078
|
-
const {
|
|
1271
|
+
const {
|
|
1272
|
+
port,
|
|
1273
|
+
host = DEFAULT_BIND_HOST,
|
|
1274
|
+
embedderOrigins,
|
|
1275
|
+
publicOrigin
|
|
1276
|
+
} = options;
|
|
1079
1277
|
let server = null;
|
|
1080
1278
|
let origin = null;
|
|
1279
|
+
let boundPort = 0;
|
|
1081
1280
|
const documents = /* @__PURE__ */ new Map();
|
|
1082
1281
|
let retainedChars = 0;
|
|
1083
1282
|
function drop(id) {
|
|
@@ -1095,7 +1294,7 @@ function createAppOriginController(options) {
|
|
|
1095
1294
|
return {
|
|
1096
1295
|
async start() {
|
|
1097
1296
|
if (server && origin) {
|
|
1098
|
-
return { port:
|
|
1297
|
+
return { port: boundPort, url: origin };
|
|
1099
1298
|
}
|
|
1100
1299
|
return new Promise((resolve9) => {
|
|
1101
1300
|
let settled = false;
|
|
@@ -1129,6 +1328,22 @@ function createAppOriginController(options) {
|
|
|
1129
1328
|
res.end(doc.html);
|
|
1130
1329
|
});
|
|
1131
1330
|
let retriedDynamic = false;
|
|
1331
|
+
const adoptedOrigin = (derived) => {
|
|
1332
|
+
if (!publicOrigin) return derived;
|
|
1333
|
+
if (port === 0 || retriedDynamic) {
|
|
1334
|
+
console.warn(
|
|
1335
|
+
`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.`
|
|
1336
|
+
);
|
|
1337
|
+
return derived;
|
|
1338
|
+
}
|
|
1339
|
+
if (embedderOrigins?.includes(publicOrigin)) {
|
|
1340
|
+
console.warn(
|
|
1341
|
+
`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.`
|
|
1342
|
+
);
|
|
1343
|
+
return derived;
|
|
1344
|
+
}
|
|
1345
|
+
return publicOrigin;
|
|
1346
|
+
};
|
|
1132
1347
|
server.on("error", (err) => {
|
|
1133
1348
|
if (err.code === "EADDRINUSE") {
|
|
1134
1349
|
if (!retriedDynamic && port !== 0) {
|
|
@@ -1160,7 +1375,8 @@ function createAppOriginController(options) {
|
|
|
1160
1375
|
const actualPort = addr.port;
|
|
1161
1376
|
const canonicalHost = canonicalUrlHost(host);
|
|
1162
1377
|
const urlHost = isAllInterfacesHost(canonicalHost) ? "localhost" : canonicalHost;
|
|
1163
|
-
origin = `http://${urlHost}:${actualPort}
|
|
1378
|
+
origin = adoptedOrigin(`http://${urlHost}:${actualPort}`);
|
|
1379
|
+
boundPort = actualPort;
|
|
1164
1380
|
settle({ port: actualPort, url: origin });
|
|
1165
1381
|
});
|
|
1166
1382
|
});
|
|
@@ -1198,6 +1414,107 @@ function createAppOriginController(options) {
|
|
|
1198
1414
|
};
|
|
1199
1415
|
}
|
|
1200
1416
|
|
|
1417
|
+
// server/public-address.ts
|
|
1418
|
+
var SANDBOX_FULL_ADDRESS_ENV = "MCP_SANDBOX_FULL_ADDRESS";
|
|
1419
|
+
var APP_ORIGIN_FULL_ADDRESS_ENV = "MCP_APP_ORIGIN_FULL_ADDRESS";
|
|
1420
|
+
var SANDBOX_PATH = "/sandbox";
|
|
1421
|
+
function parsePublicUrl(raw) {
|
|
1422
|
+
let url;
|
|
1423
|
+
try {
|
|
1424
|
+
url = new URL(raw);
|
|
1425
|
+
} catch {
|
|
1426
|
+
return { reason: "it is not an absolute URL" };
|
|
1427
|
+
}
|
|
1428
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
1429
|
+
return { reason: "it needs an http:// or https:// scheme" };
|
|
1430
|
+
}
|
|
1431
|
+
if (url.username || url.password) {
|
|
1432
|
+
return { reason: "it must not carry credentials" };
|
|
1433
|
+
}
|
|
1434
|
+
if (url.hostname.includes("*")) {
|
|
1435
|
+
return { reason: "it must not contain a wildcard" };
|
|
1436
|
+
}
|
|
1437
|
+
if (url.hostname.startsWith("[")) {
|
|
1438
|
+
return {
|
|
1439
|
+
reason: "a bracketed IPv6 literal is not a valid CSP host-source \u2014 use a hostname or an IPv4 address"
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
if (url.search || url.hash) {
|
|
1443
|
+
return { reason: "it must not carry a query string or fragment" };
|
|
1444
|
+
}
|
|
1445
|
+
return { url };
|
|
1446
|
+
}
|
|
1447
|
+
function describeForLog(raw) {
|
|
1448
|
+
try {
|
|
1449
|
+
const u = new URL(raw);
|
|
1450
|
+
return `"${u.protocol}${u.host ? `//${u.host}` : ""}"`;
|
|
1451
|
+
} catch {
|
|
1452
|
+
return "(unparseable value, not echoed)";
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function warnIgnored(env, raw, reason) {
|
|
1456
|
+
console.warn(
|
|
1457
|
+
`Ignoring ${env}=${describeForLog(raw)}: ${reason}. Advertising the bind-derived address instead.`
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
function warnMixedContent(env, url, allowedOrigins) {
|
|
1461
|
+
if (url.protocol === "http:" && allowedOrigins.some((o) => o.startsWith("https:"))) {
|
|
1462
|
+
console.warn(
|
|
1463
|
+
`${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.`
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function resolveSandboxPublicUrl(raw, allowedOrigins) {
|
|
1468
|
+
const value = raw?.trim();
|
|
1469
|
+
if (!value) return void 0;
|
|
1470
|
+
const parsed = parsePublicUrl(value);
|
|
1471
|
+
if ("reason" in parsed) {
|
|
1472
|
+
warnIgnored(SANDBOX_FULL_ADDRESS_ENV, value, parsed.reason);
|
|
1473
|
+
return void 0;
|
|
1474
|
+
}
|
|
1475
|
+
const { url } = parsed;
|
|
1476
|
+
if (url.pathname === "/") url.pathname = SANDBOX_PATH;
|
|
1477
|
+
if (allowedOrigins.includes(url.origin)) {
|
|
1478
|
+
warnIgnored(
|
|
1479
|
+
SANDBOX_FULL_ADDRESS_ENV,
|
|
1480
|
+
value,
|
|
1481
|
+
`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`
|
|
1482
|
+
);
|
|
1483
|
+
return void 0;
|
|
1484
|
+
}
|
|
1485
|
+
warnMixedContent(SANDBOX_FULL_ADDRESS_ENV, url, allowedOrigins);
|
|
1486
|
+
return url.href;
|
|
1487
|
+
}
|
|
1488
|
+
function resolveAppOriginPublicOrigin(raw, allowedOrigins, sandboxUrl) {
|
|
1489
|
+
const value = raw?.trim();
|
|
1490
|
+
if (!value) return void 0;
|
|
1491
|
+
const parsed = parsePublicUrl(value);
|
|
1492
|
+
if ("reason" in parsed) {
|
|
1493
|
+
warnIgnored(APP_ORIGIN_FULL_ADDRESS_ENV, value, parsed.reason);
|
|
1494
|
+
return void 0;
|
|
1495
|
+
}
|
|
1496
|
+
const { url } = parsed;
|
|
1497
|
+
if (url.pathname !== "/") {
|
|
1498
|
+
warnIgnored(
|
|
1499
|
+
APP_ORIGIN_FULL_ADDRESS_ENV,
|
|
1500
|
+
value,
|
|
1501
|
+
"it must be an origin only, with no path \u2014 app documents are served at <origin>/app-document/<id>"
|
|
1502
|
+
);
|
|
1503
|
+
return void 0;
|
|
1504
|
+
}
|
|
1505
|
+
const trusted = sandboxUrl ? [...allowedOrigins, new URL(sandboxUrl).origin] : allowedOrigins;
|
|
1506
|
+
if (trusted.includes(url.origin)) {
|
|
1507
|
+
warnIgnored(
|
|
1508
|
+
APP_ORIGIN_FULL_ADDRESS_ENV,
|
|
1509
|
+
value,
|
|
1510
|
+
`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`
|
|
1511
|
+
);
|
|
1512
|
+
return void 0;
|
|
1513
|
+
}
|
|
1514
|
+
warnMixedContent(APP_ORIGIN_FULL_ADDRESS_ENV, url, allowedOrigins);
|
|
1515
|
+
return url.origin;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1201
1518
|
// server/web-server-config.ts
|
|
1202
1519
|
var inspectorVersion = readInspectorVersionSafe(import.meta.url);
|
|
1203
1520
|
function defaultEnvironmentFromProcess(extra) {
|
|
@@ -1349,7 +1666,9 @@ function buildWebServerConfig(options = {}) {
|
|
|
1349
1666
|
);
|
|
1350
1667
|
}
|
|
1351
1668
|
const hostname = resolveBindHostname();
|
|
1352
|
-
const dangerouslyOmitAuth =
|
|
1669
|
+
const dangerouslyOmitAuth = isEnvFlagEnabled(
|
|
1670
|
+
process.env.DANGEROUSLY_OMIT_AUTH
|
|
1671
|
+
);
|
|
1353
1672
|
const authToken = dangerouslyOmitAuth ? "" : process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] ?? process.env[LEGACY_AUTH_TOKEN_ENV] ?? "";
|
|
1354
1673
|
const sandboxPort = resolveSandboxPort();
|
|
1355
1674
|
const requestedAppOriginPort = resolveAppOriginPort();
|
|
@@ -1382,6 +1701,16 @@ function buildWebServerConfig(options = {}) {
|
|
|
1382
1701
|
return null;
|
|
1383
1702
|
}
|
|
1384
1703
|
}).filter((o) => o !== null);
|
|
1704
|
+
const allowedOrigins = configuredOrigins?.length ? configuredOrigins : defaultAllowedOrigins(hostname, port);
|
|
1705
|
+
const sandboxPublicUrl = resolveSandboxPublicUrl(
|
|
1706
|
+
process.env[SANDBOX_FULL_ADDRESS_ENV],
|
|
1707
|
+
allowedOrigins
|
|
1708
|
+
);
|
|
1709
|
+
const appOriginPublicOrigin = resolveAppOriginPublicOrigin(
|
|
1710
|
+
process.env[APP_ORIGIN_FULL_ADDRESS_ENV],
|
|
1711
|
+
allowedOrigins,
|
|
1712
|
+
sandboxPublicUrl
|
|
1713
|
+
);
|
|
1385
1714
|
return {
|
|
1386
1715
|
port,
|
|
1387
1716
|
hostname,
|
|
@@ -1392,10 +1721,12 @@ function buildWebServerConfig(options = {}) {
|
|
|
1392
1721
|
writable,
|
|
1393
1722
|
initialServers,
|
|
1394
1723
|
storageDir: process.env.MCP_STORAGE_DIR,
|
|
1395
|
-
allowedOrigins
|
|
1724
|
+
allowedOrigins,
|
|
1396
1725
|
sandboxPort,
|
|
1397
1726
|
sandboxHost: hostname,
|
|
1398
1727
|
appOriginPort,
|
|
1728
|
+
sandboxPublicUrl,
|
|
1729
|
+
appOriginPublicOrigin,
|
|
1399
1730
|
logger,
|
|
1400
1731
|
autoOpen: resolveAutoOpen()
|
|
1401
1732
|
};
|
|
@@ -1415,7 +1746,7 @@ import react from "@vitejs/plugin-react";
|
|
|
1415
1746
|
|
|
1416
1747
|
// ../../core/mcp/remote/node/server.ts
|
|
1417
1748
|
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
1418
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1749
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
1419
1750
|
import { stat as fsStat } from "fs/promises";
|
|
1420
1751
|
import { homedir } from "os";
|
|
1421
1752
|
import { Hono } from "hono";
|
|
@@ -1547,6 +1878,7 @@ function redactBody(body, contentType) {
|
|
|
1547
1878
|
return body;
|
|
1548
1879
|
}
|
|
1549
1880
|
function findHeader(headers, name) {
|
|
1881
|
+
if (!headers) return void 0;
|
|
1550
1882
|
const target = name.toLowerCase();
|
|
1551
1883
|
for (const [key, value] of Object.entries(headers)) {
|
|
1552
1884
|
if (key.toLowerCase() === target) return value;
|
|
@@ -1554,9 +1886,146 @@ function findHeader(headers, name) {
|
|
|
1554
1886
|
return void 0;
|
|
1555
1887
|
}
|
|
1556
1888
|
function isLongLivedStreamResponse(method, contentType) {
|
|
1557
|
-
if (method !== "GET") return false;
|
|
1889
|
+
if (method.toUpperCase() !== "GET") return false;
|
|
1558
1890
|
if (!contentType) return false;
|
|
1559
|
-
|
|
1891
|
+
const type = contentType.toLowerCase();
|
|
1892
|
+
return type.includes("text/event-stream") || type.includes("application/x-ndjson");
|
|
1893
|
+
}
|
|
1894
|
+
var CHALLENGE_BODY_MAX_BYTES = 64 * 1024;
|
|
1895
|
+
var CHALLENGE_BODY_READ_MS = 2e3;
|
|
1896
|
+
var TRUNCATED_BODY_SUFFIX = "\n[truncated]";
|
|
1897
|
+
var WITHHELD_TRUNCATED_JSON_BODY = "[truncated JSON body withheld: an incomplete body cannot be redacted]";
|
|
1898
|
+
function recordableChallengeBody(read, contentType) {
|
|
1899
|
+
if (!read.truncated) return redactBody(read.text, contentType);
|
|
1900
|
+
const looksLikeJson = (contentType ?? "").toLowerCase().includes("json") || /^\s*[{[]/.test(read.text);
|
|
1901
|
+
if (looksLikeJson) return WITHHELD_TRUNCATED_JSON_BODY;
|
|
1902
|
+
return redactBody(read.text, contentType) + TRUNCATED_BODY_SUFFIX;
|
|
1903
|
+
}
|
|
1904
|
+
async function readChallengeBody(response) {
|
|
1905
|
+
let reader;
|
|
1906
|
+
try {
|
|
1907
|
+
const body = response.clone().body;
|
|
1908
|
+
if (!body) return void 0;
|
|
1909
|
+
reader = body.getReader();
|
|
1910
|
+
} catch {
|
|
1911
|
+
return void 0;
|
|
1912
|
+
}
|
|
1913
|
+
const decoder = new TextDecoder();
|
|
1914
|
+
let text = "";
|
|
1915
|
+
let bytes = 0;
|
|
1916
|
+
let timer;
|
|
1917
|
+
const deadline = new Promise((resolve9) => {
|
|
1918
|
+
timer = setTimeout(() => resolve9("timeout"), CHALLENGE_BODY_READ_MS);
|
|
1919
|
+
});
|
|
1920
|
+
try {
|
|
1921
|
+
while (bytes < CHALLENGE_BODY_MAX_BYTES) {
|
|
1922
|
+
const next = await Promise.race([reader.read(), deadline]);
|
|
1923
|
+
if (next === "timeout") break;
|
|
1924
|
+
if (next.done) return { text: text + decoder.decode(), truncated: false };
|
|
1925
|
+
const chunk = next.value.subarray(0, CHALLENGE_BODY_MAX_BYTES - bytes);
|
|
1926
|
+
bytes += chunk.byteLength;
|
|
1927
|
+
text += decoder.decode(chunk, { stream: true });
|
|
1928
|
+
}
|
|
1929
|
+
reader.cancel().catch(() => {
|
|
1930
|
+
});
|
|
1931
|
+
return { text: text + decoder.decode(), truncated: true };
|
|
1932
|
+
} catch {
|
|
1933
|
+
return void 0;
|
|
1934
|
+
} finally {
|
|
1935
|
+
clearTimeout(timer);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
var DEFAULT_STREAM_UPDATE_INTERVAL_MS = 250;
|
|
1939
|
+
function longLivedStreamFraming(contentType) {
|
|
1940
|
+
return contentType?.toLowerCase().includes("application/x-ndjson") ? "ndjson" : "sse";
|
|
1941
|
+
}
|
|
1942
|
+
function watchLongLivedStream(response, id, framing, callbacks) {
|
|
1943
|
+
const listener = callbacks.updateStream;
|
|
1944
|
+
if (!listener) return;
|
|
1945
|
+
const report = (state) => {
|
|
1946
|
+
try {
|
|
1947
|
+
listener(id, state);
|
|
1948
|
+
} catch {
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
const intervalMs = callbacks.streamUpdateIntervalMs ?? DEFAULT_STREAM_UPDATE_INTERVAL_MS;
|
|
1952
|
+
let body;
|
|
1953
|
+
try {
|
|
1954
|
+
body = response.clone().body;
|
|
1955
|
+
} catch {
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (!body) return;
|
|
1959
|
+
const reader = body.getReader();
|
|
1960
|
+
const decoder = new TextDecoder();
|
|
1961
|
+
let eventCount = 0;
|
|
1962
|
+
let pending = "";
|
|
1963
|
+
let blockHasData = false;
|
|
1964
|
+
let reportTimer = null;
|
|
1965
|
+
const reportCount = () => {
|
|
1966
|
+
if (intervalMs <= 0) {
|
|
1967
|
+
report({ eventCount });
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
if (reportTimer !== null) return;
|
|
1971
|
+
reportTimer = setTimeout(() => {
|
|
1972
|
+
reportTimer = null;
|
|
1973
|
+
report({ eventCount });
|
|
1974
|
+
}, intervalMs);
|
|
1975
|
+
};
|
|
1976
|
+
report({ eventCount });
|
|
1977
|
+
const consumeLine = (line) => {
|
|
1978
|
+
if (framing === "ndjson") {
|
|
1979
|
+
if (line.trim() === "") return;
|
|
1980
|
+
eventCount += 1;
|
|
1981
|
+
reportCount();
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
if (line === "") {
|
|
1985
|
+
if (blockHasData) {
|
|
1986
|
+
eventCount += 1;
|
|
1987
|
+
blockHasData = false;
|
|
1988
|
+
reportCount();
|
|
1989
|
+
}
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
if (line === "data" || line.startsWith("data:")) blockHasData = true;
|
|
1993
|
+
};
|
|
1994
|
+
const consumeCompleteLines = () => {
|
|
1995
|
+
for (; ; ) {
|
|
1996
|
+
const cr = pending.indexOf("\r");
|
|
1997
|
+
const lf = pending.indexOf("\n");
|
|
1998
|
+
const end = cr === -1 ? lf : lf === -1 ? cr : Math.min(cr, lf);
|
|
1999
|
+
if (end === -1) return;
|
|
2000
|
+
if (pending[end] === "\r") {
|
|
2001
|
+
if (end === pending.length - 1) return;
|
|
2002
|
+
const width = pending[end + 1] === "\n" ? 2 : 1;
|
|
2003
|
+
consumeLine(pending.slice(0, end));
|
|
2004
|
+
pending = pending.slice(end + width);
|
|
2005
|
+
} else {
|
|
2006
|
+
consumeLine(pending.slice(0, end));
|
|
2007
|
+
pending = pending.slice(end + 1);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
2011
|
+
const pump = async () => {
|
|
2012
|
+
try {
|
|
2013
|
+
for (; ; ) {
|
|
2014
|
+
const { value, done } = await reader.read();
|
|
2015
|
+
if (done) break;
|
|
2016
|
+
pending += decoder.decode(value, { stream: true });
|
|
2017
|
+
consumeCompleteLines();
|
|
2018
|
+
}
|
|
2019
|
+
if (pending.endsWith("\r")) consumeLine(pending.slice(0, -1));
|
|
2020
|
+
} catch {
|
|
2021
|
+
}
|
|
2022
|
+
if (reportTimer !== null) {
|
|
2023
|
+
clearTimeout(reportTimer);
|
|
2024
|
+
reportTimer = null;
|
|
2025
|
+
}
|
|
2026
|
+
report({ eventCount, closedAt: /* @__PURE__ */ new Date() });
|
|
2027
|
+
};
|
|
2028
|
+
void pump();
|
|
1560
2029
|
}
|
|
1561
2030
|
function createFetchTracker(baseFetch, callbacks) {
|
|
1562
2031
|
return async (input, init) => {
|
|
@@ -1564,7 +2033,7 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
1564
2033
|
const timestamp = /* @__PURE__ */ new Date();
|
|
1565
2034
|
const id = `${timestamp.getTime()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1566
2035
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
1567
|
-
const method = init?.method
|
|
2036
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
1568
2037
|
const rawRequestHeaders = {};
|
|
1569
2038
|
if (input instanceof Request) {
|
|
1570
2039
|
input.headers.forEach((value, key) => {
|
|
@@ -1644,7 +2113,21 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
1644
2113
|
responseBody: void 0,
|
|
1645
2114
|
duration
|
|
1646
2115
|
};
|
|
2116
|
+
if ((responseStatus === 401 || responseStatus === 403) && response.body && !response.bodyUsed) {
|
|
2117
|
+
const read = await readChallengeBody(response);
|
|
2118
|
+
entry.responseBody = read === void 0 ? void 0 : recordableChallengeBody(read, response.headers.get("content-type"));
|
|
2119
|
+
callbacks.trackRequest?.(entry);
|
|
2120
|
+
return response;
|
|
2121
|
+
}
|
|
1647
2122
|
callbacks.trackRequest?.(entry);
|
|
2123
|
+
if (isLongLivedStream && response.body && !response.bodyUsed) {
|
|
2124
|
+
watchLongLivedStream(
|
|
2125
|
+
response,
|
|
2126
|
+
id,
|
|
2127
|
+
longLivedStreamFraming(response.headers.get("content-type")),
|
|
2128
|
+
callbacks
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
1648
2131
|
if (!isLongLivedStream && response.body && !response.bodyUsed) {
|
|
1649
2132
|
const responseContentType = response.headers.get("content-type");
|
|
1650
2133
|
try {
|
|
@@ -1756,7 +2239,7 @@ function createAuthChallengeInterceptFetch(baseFetch) {
|
|
|
1756
2239
|
if (!challenge) {
|
|
1757
2240
|
return response;
|
|
1758
2241
|
}
|
|
1759
|
-
|
|
2242
|
+
void response.body?.cancel().catch(() => {
|
|
1760
2243
|
});
|
|
1761
2244
|
throw new AuthChallengeError(
|
|
1762
2245
|
challenge,
|
|
@@ -1886,6 +2369,73 @@ function createProxyFetch() {
|
|
|
1886
2369
|
};
|
|
1887
2370
|
}
|
|
1888
2371
|
|
|
2372
|
+
// ../../core/mcp/node/notificationHeadersFetch.ts
|
|
2373
|
+
import { PROTOCOL_VERSION_META_KEY } from "@modelcontextprotocol/client";
|
|
2374
|
+
function createNotificationHeadersFetch(baseFetch) {
|
|
2375
|
+
return (input, init) => {
|
|
2376
|
+
const method = modernNotificationMethod(init);
|
|
2377
|
+
if (method === void 0) return baseFetch(input, init);
|
|
2378
|
+
const headers = new Headers(init?.headers);
|
|
2379
|
+
headers.set("mcp-protocol-version", method.version);
|
|
2380
|
+
headers.set("mcp-method", method.method);
|
|
2381
|
+
return baseFetch(input, { ...init, headers });
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
function modernNotificationMethod(init) {
|
|
2385
|
+
if (init?.method?.toUpperCase() !== "POST") return void 0;
|
|
2386
|
+
if (typeof init.body !== "string") return void 0;
|
|
2387
|
+
let message;
|
|
2388
|
+
try {
|
|
2389
|
+
message = JSON.parse(init.body);
|
|
2390
|
+
} catch {
|
|
2391
|
+
return void 0;
|
|
2392
|
+
}
|
|
2393
|
+
if (!isRecord(message) || "id" in message) return void 0;
|
|
2394
|
+
if (typeof message.method !== "string") return void 0;
|
|
2395
|
+
const meta = isRecord(message.params) ? message.params._meta : void 0;
|
|
2396
|
+
const version = isRecord(meta) ? meta[PROTOCOL_VERSION_META_KEY] : void 0;
|
|
2397
|
+
if (typeof version !== "string" || version < MODERN_PROTOCOL_VERSION) {
|
|
2398
|
+
return void 0;
|
|
2399
|
+
}
|
|
2400
|
+
return { method: message.method, version };
|
|
2401
|
+
}
|
|
2402
|
+
function isRecord(value) {
|
|
2403
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// ../../core/mcp/node/suppressNotificationStreamFetch.ts
|
|
2407
|
+
function createSuppressNotificationStreamFetch(baseFetch, endpoint) {
|
|
2408
|
+
return async (input, init) => {
|
|
2409
|
+
if (isStandaloneStreamRequest(input, init, endpoint)) {
|
|
2410
|
+
return new Response(null, {
|
|
2411
|
+
status: 405,
|
|
2412
|
+
statusText: "Method Not Allowed"
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
return baseFetch(input, init);
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
function requestUrl(input) {
|
|
2419
|
+
const raw = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
|
|
2420
|
+
try {
|
|
2421
|
+
return new URL(raw);
|
|
2422
|
+
} catch {
|
|
2423
|
+
return void 0;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
function isStandaloneStreamRequest(input, init, endpoint) {
|
|
2427
|
+
const request = input instanceof Request ? input : void 0;
|
|
2428
|
+
const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
|
|
2429
|
+
if (method !== "GET") return false;
|
|
2430
|
+
const url = requestUrl(input);
|
|
2431
|
+
if (!url || url.origin !== endpoint.origin || url.pathname !== endpoint.pathname || url.search !== endpoint.search) {
|
|
2432
|
+
return false;
|
|
2433
|
+
}
|
|
2434
|
+
const headers = new Headers(init?.headers ?? request?.headers);
|
|
2435
|
+
const accept = headers.get("accept")?.toLowerCase() ?? "";
|
|
2436
|
+
return accept.includes("text/event-stream") && !headers.has("last-event-id");
|
|
2437
|
+
}
|
|
2438
|
+
|
|
1889
2439
|
// ../../core/mcp/node/transport.ts
|
|
1890
2440
|
function headersFromSettings(settings) {
|
|
1891
2441
|
if (!settings || settings.headers.length === 0) return void 0;
|
|
@@ -1904,14 +2454,24 @@ function createTransportNode(config, options = {}) {
|
|
|
1904
2454
|
pipeStderr = false,
|
|
1905
2455
|
onFetchRequest,
|
|
1906
2456
|
onFetchResponseBody,
|
|
2457
|
+
onFetchStreamUpdate,
|
|
1907
2458
|
authProvider,
|
|
1908
2459
|
settings,
|
|
1909
2460
|
interceptAuthChallenges = false,
|
|
1910
2461
|
onAuthChallengeObserved
|
|
1911
2462
|
} = options;
|
|
2463
|
+
const wantsFetchTracking = Boolean(
|
|
2464
|
+
onFetchRequest || onFetchResponseBody || onFetchStreamUpdate
|
|
2465
|
+
);
|
|
1912
2466
|
const baseFetch = optionsFetchFn ?? createProxyFetch() ?? globalThis.fetch;
|
|
1913
2467
|
const withChallengeObserver = (inner) => onAuthChallengeObserved ? createAuthChallengeObserverFetch(inner, onAuthChallengeObserved) : inner;
|
|
1914
|
-
const
|
|
2468
|
+
const withTracking = (inner) => wantsFetchTracking ? createFetchTracker(inner, {
|
|
2469
|
+
trackRequest: onFetchRequest,
|
|
2470
|
+
updateResponseBody: onFetchResponseBody,
|
|
2471
|
+
updateStream: onFetchStreamUpdate
|
|
2472
|
+
}) : inner;
|
|
2473
|
+
const trackedBase = withTracking(withChallengeObserver(baseFetch));
|
|
2474
|
+
const fetchWithOptionalAuthIntercept = interceptAuthChallenges ? createAuthChallengeInterceptFetch(trackedBase) : trackedBase;
|
|
1915
2475
|
if (serverType === "stdio") {
|
|
1916
2476
|
const stdioConfig = config;
|
|
1917
2477
|
const transport = new StdioClientTransport({
|
|
@@ -1937,11 +2497,7 @@ function createTransportNode(config, options = {}) {
|
|
|
1937
2497
|
const sseConfig = config;
|
|
1938
2498
|
const url = new URL(sseConfig.url);
|
|
1939
2499
|
const configuredSseFetch = sseConfig.eventSourceInit?.fetch;
|
|
1940
|
-
const
|
|
1941
|
-
const trackedFetch = onFetchRequest ? createFetchTracker(sseFetch, {
|
|
1942
|
-
trackRequest: onFetchRequest,
|
|
1943
|
-
updateResponseBody: onFetchResponseBody
|
|
1944
|
-
}) : sseFetch;
|
|
2500
|
+
const trackedFetch = configuredSseFetch ? withTracking(withChallengeObserver(configuredSseFetch)) : fetchWithOptionalAuthIntercept;
|
|
1945
2501
|
const headers = headersFromSettings(settings);
|
|
1946
2502
|
const eventSourceInit = {
|
|
1947
2503
|
...sseConfig.eventSourceInit,
|
|
@@ -1952,15 +2508,11 @@ function createTransportNode(config, options = {}) {
|
|
|
1952
2508
|
...sseConfig.requestInit,
|
|
1953
2509
|
...headers && { headers }
|
|
1954
2510
|
};
|
|
1955
|
-
const postFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
|
|
1956
|
-
trackRequest: onFetchRequest,
|
|
1957
|
-
updateResponseBody: onFetchResponseBody
|
|
1958
|
-
}) : fetchWithOptionalAuthIntercept;
|
|
1959
2511
|
const transport = new SSEClientTransport(url, {
|
|
1960
2512
|
authProvider,
|
|
1961
2513
|
eventSourceInit,
|
|
1962
2514
|
requestInit,
|
|
1963
|
-
fetch:
|
|
2515
|
+
fetch: fetchWithOptionalAuthIntercept
|
|
1964
2516
|
});
|
|
1965
2517
|
return { transport };
|
|
1966
2518
|
} else {
|
|
@@ -1971,14 +2523,14 @@ function createTransportNode(config, options = {}) {
|
|
|
1971
2523
|
...httpConfig.requestInit,
|
|
1972
2524
|
...headers && { headers }
|
|
1973
2525
|
};
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2526
|
+
const stampedFetch = createNotificationHeadersFetch(
|
|
2527
|
+
fetchWithOptionalAuthIntercept
|
|
2528
|
+
);
|
|
2529
|
+
const httpFetch = settings?.suppressNotificationStream === true ? createSuppressNotificationStreamFetch(stampedFetch, url) : stampedFetch;
|
|
1978
2530
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
1979
2531
|
authProvider,
|
|
1980
2532
|
requestInit,
|
|
1981
|
-
fetch:
|
|
2533
|
+
fetch: httpFetch,
|
|
1982
2534
|
// SEP-2350: how the transport reacts to a `403 insufficient_scope`
|
|
1983
2535
|
// challenge. Defaults to the SDK's `reauthorize` when unset.
|
|
1984
2536
|
...settings?.oauthOnInsufficientScope && {
|
|
@@ -1989,6 +2541,9 @@ function createTransportNode(config, options = {}) {
|
|
|
1989
2541
|
}
|
|
1990
2542
|
}
|
|
1991
2543
|
|
|
2544
|
+
// ../../core/auth/requestTimeout.ts
|
|
2545
|
+
var OAUTH_TIMEOUT_WIRE_CODE = "oauth_request_timeout";
|
|
2546
|
+
|
|
1992
2547
|
// ../../core/json/xMcpHeader.ts
|
|
1993
2548
|
var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
|
|
1994
2549
|
|
|
@@ -2141,6 +2696,20 @@ function resolveImportSource(type, platform, homeDir, readFile2) {
|
|
|
2141
2696
|
return { type, found: false, searched };
|
|
2142
2697
|
}
|
|
2143
2698
|
|
|
2699
|
+
// ../../core/mcp/remote/progressToken.ts
|
|
2700
|
+
import { ProgressTokenSchema } from "@modelcontextprotocol/core";
|
|
2701
|
+
function progressTokenOf(message) {
|
|
2702
|
+
if ("method" in message && message.method === "notifications/progress" && "params" in message && typeof message.params === "object" && message.params !== null) {
|
|
2703
|
+
const parsed = ProgressTokenSchema.safeParse(
|
|
2704
|
+
message.params.progressToken
|
|
2705
|
+
);
|
|
2706
|
+
if (parsed.success) {
|
|
2707
|
+
return parsed.data;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
return void 0;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2144
2713
|
// ../../core/mcp/remote/node/remote-session.ts
|
|
2145
2714
|
var RemoteSession = class _RemoteSession {
|
|
2146
2715
|
sessionId;
|
|
@@ -2260,30 +2829,54 @@ var RemoteSession = class _RemoteSession {
|
|
|
2260
2829
|
*/
|
|
2261
2830
|
waitForRequestResponse(requestId, timeoutMs = 6e4) {
|
|
2262
2831
|
return new Promise((resolve9, reject) => {
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2832
|
+
let timer;
|
|
2833
|
+
const arm = () => {
|
|
2834
|
+
if (timeoutMs <= 0) return;
|
|
2835
|
+
timer = setTimeout(() => {
|
|
2836
|
+
this.requestWaits.delete(requestId);
|
|
2837
|
+
reject(
|
|
2838
|
+
new Error(
|
|
2839
|
+
`MCP request ${String(requestId)} timed out after ${timeoutMs}ms`
|
|
2840
|
+
)
|
|
2841
|
+
);
|
|
2842
|
+
}, timeoutMs);
|
|
2843
|
+
};
|
|
2844
|
+
const clear = () => {
|
|
2845
|
+
if (timer !== void 0) {
|
|
2846
|
+
clearTimeout(timer);
|
|
2847
|
+
timer = void 0;
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
arm();
|
|
2271
2851
|
this.requestWaits.set(requestId, {
|
|
2272
2852
|
resolve: () => {
|
|
2273
|
-
|
|
2274
|
-
clearTimeout(timer);
|
|
2275
|
-
}
|
|
2853
|
+
clear();
|
|
2276
2854
|
resolve9();
|
|
2277
2855
|
},
|
|
2278
2856
|
reject: (error) => {
|
|
2279
|
-
|
|
2280
|
-
clearTimeout(timer);
|
|
2281
|
-
}
|
|
2857
|
+
clear();
|
|
2282
2858
|
reject(error);
|
|
2859
|
+
},
|
|
2860
|
+
// A progress notification for this request re-arms the full window, so
|
|
2861
|
+
// a tool that keeps reporting progress never trips the relay's own
|
|
2862
|
+
// deadline — the same guarantee the SDK client's `resetTimeoutOnProgress`
|
|
2863
|
+
// gives on a direct transport (#2028).
|
|
2864
|
+
resetTimeout: () => {
|
|
2865
|
+
if (timer === void 0) return;
|
|
2866
|
+
clear();
|
|
2867
|
+
arm();
|
|
2283
2868
|
}
|
|
2284
2869
|
});
|
|
2285
2870
|
});
|
|
2286
2871
|
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Re-arm a pending request's wait timeout. Invoked from {@link onMessage} when
|
|
2874
|
+
* a `notifications/progress` carrying this request's `progressToken` arrives.
|
|
2875
|
+
* A no-op when no wait is pending for the id.
|
|
2876
|
+
*/
|
|
2877
|
+
noteRequestProgress(requestId) {
|
|
2878
|
+
this.requestWaits.get(requestId)?.resetTimeout();
|
|
2879
|
+
}
|
|
2287
2880
|
cancelRequestWait(requestId) {
|
|
2288
2881
|
const wait = this.requestWaits.get(requestId);
|
|
2289
2882
|
if (!wait) {
|
|
@@ -2338,6 +2931,10 @@ var RemoteSession = class _RemoteSession {
|
|
|
2338
2931
|
if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) {
|
|
2339
2932
|
this.settleRequestWait(message.id);
|
|
2340
2933
|
}
|
|
2934
|
+
const progressToken = progressTokenOf(message);
|
|
2935
|
+
if (progressToken !== void 0) {
|
|
2936
|
+
this.noteRequestProgress(progressToken);
|
|
2937
|
+
}
|
|
2341
2938
|
this.pushEvent({ type: "message", data: message });
|
|
2342
2939
|
}
|
|
2343
2940
|
onFetchRequest(entry) {
|
|
@@ -2355,6 +2952,16 @@ var RemoteSession = class _RemoteSession {
|
|
|
2355
2952
|
data: { id, responseBody }
|
|
2356
2953
|
});
|
|
2357
2954
|
}
|
|
2955
|
+
onFetchStreamUpdate(id, stream) {
|
|
2956
|
+
this.pushEvent({
|
|
2957
|
+
type: "fetch_stream_update",
|
|
2958
|
+
data: {
|
|
2959
|
+
id,
|
|
2960
|
+
eventCount: stream.eventCount,
|
|
2961
|
+
...stream.closedAt && { closedAt: stream.closedAt.toISOString() }
|
|
2962
|
+
}
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2358
2965
|
onStderr(entry) {
|
|
2359
2966
|
this.pushEvent({
|
|
2360
2967
|
type: "stdio_log",
|
|
@@ -2688,6 +3295,7 @@ import * as path4 from "path";
|
|
|
2688
3295
|
|
|
2689
3296
|
// ../../core/auth/node/file-secret-store.ts
|
|
2690
3297
|
import * as crypto2 from "crypto";
|
|
3298
|
+
import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
2691
3299
|
import * as fs3 from "fs/promises";
|
|
2692
3300
|
import * as path3 from "path";
|
|
2693
3301
|
|
|
@@ -2890,6 +3498,55 @@ var SCRYPT_N = 16384;
|
|
|
2890
3498
|
var SCRYPT_R = 8;
|
|
2891
3499
|
var SCRYPT_P = 1;
|
|
2892
3500
|
var SECRET_KEY_ENV = "MCP_INSPECTOR_SECRET_KEY";
|
|
3501
|
+
var SECRET_KEY_FILE_ENV = "MCP_INSPECTOR_SECRET_KEY_FILE";
|
|
3502
|
+
function isSameFile(a, b) {
|
|
3503
|
+
if (path3.resolve(a) === path3.resolve(b)) return true;
|
|
3504
|
+
try {
|
|
3505
|
+
const sa = statSync2(a);
|
|
3506
|
+
const sb = statSync2(b);
|
|
3507
|
+
return sa.dev === sb.dev && sa.ino === sb.ino;
|
|
3508
|
+
} catch {
|
|
3509
|
+
return false;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
function resolveSecretPassphrase(env = process.env, secretFilePath) {
|
|
3513
|
+
const direct = env[SECRET_KEY_ENV];
|
|
3514
|
+
const hasDirect = direct !== void 0 && direct.trim() !== "";
|
|
3515
|
+
const rawKeyFile = env[SECRET_KEY_FILE_ENV];
|
|
3516
|
+
const wantsKeyFile = rawKeyFile !== void 0;
|
|
3517
|
+
const keyFile = rawKeyFile?.trim();
|
|
3518
|
+
if (hasDirect && wantsKeyFile) {
|
|
3519
|
+
return {
|
|
3520
|
+
problem: `both ${SECRET_KEY_ENV} and ${SECRET_KEY_FILE_ENV} are set; set only one`
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
if (hasDirect) return { passphrase: direct };
|
|
3524
|
+
if (!wantsKeyFile) return {};
|
|
3525
|
+
if (!keyFile) {
|
|
3526
|
+
return { problem: `${SECRET_KEY_FILE_ENV} is set but empty` };
|
|
3527
|
+
}
|
|
3528
|
+
const resolved2 = path3.resolve(keyFile);
|
|
3529
|
+
if (secretFilePath !== void 0 && isSameFile(resolved2, secretFilePath)) {
|
|
3530
|
+
return {
|
|
3531
|
+
problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is the secrets file itself; point it at a separate key file`
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
let contents;
|
|
3535
|
+
try {
|
|
3536
|
+
contents = readFileSync4(resolved2, "utf-8");
|
|
3537
|
+
} catch (err) {
|
|
3538
|
+
return {
|
|
3539
|
+
// `String(err)` rather than `.message`: `readFileSync` only ever throws
|
|
3540
|
+
// an `Error`, so an `instanceof` guard would be a branch no test can take.
|
|
3541
|
+
problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) could not be read: ${String(err)}`
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
const passphrase = contents.replace(/[\r\n]+$/, "");
|
|
3545
|
+
if (passphrase.trim() === "") {
|
|
3546
|
+
return { problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is empty` };
|
|
3547
|
+
}
|
|
3548
|
+
return { passphrase };
|
|
3549
|
+
}
|
|
2893
3550
|
var buildAccount2 = (serverId, field) => `${serverId}:${field}`;
|
|
2894
3551
|
function asSecretMap(value, filePath) {
|
|
2895
3552
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
@@ -2909,7 +3566,7 @@ function asSecretMap(value, filePath) {
|
|
|
2909
3566
|
var SecretFileKeyMismatchError = class extends SecretStoreUnavailableError {
|
|
2910
3567
|
constructor(filePath, hasKey) {
|
|
2911
3568
|
super(
|
|
2912
|
-
hasKey ? `The secrets file at ${filePath} could not be decrypted with the current ${SECRET_KEY_ENV}. Refusing to write, which would overwrite the existing secrets. Restore the original passphrase, or delete the file to start over.` : `The secrets file at ${filePath} is encrypted but ${SECRET_KEY_ENV} is not set. Refusing to write, which would overwrite the existing secrets. Set the passphrase this file was written with, or delete the file to start over.`
|
|
3569
|
+
hasKey ? `The secrets file at ${filePath} could not be decrypted with the current ${SECRET_KEY_ENV} (or ${SECRET_KEY_FILE_ENV}). Refusing to write, which would overwrite the existing secrets. Restore the original passphrase, or delete the file to start over.` : `The secrets file at ${filePath} is encrypted but ${SECRET_KEY_ENV} is not set (nor ${SECRET_KEY_FILE_ENV}). Refusing to write, which would overwrite the existing secrets. Set the passphrase this file was written with, or delete the file to start over.`
|
|
2913
3570
|
);
|
|
2914
3571
|
this.name = "SecretFileKeyMismatchError";
|
|
2915
3572
|
}
|
|
@@ -2967,10 +3624,17 @@ function encryptedEnvelopeProblem(parsed) {
|
|
|
2967
3624
|
var FileSecretStore = class {
|
|
2968
3625
|
filePath;
|
|
2969
3626
|
passphrase;
|
|
3627
|
+
/**
|
|
3628
|
+
* Why a configured key could not be obtained. While set, every read throws
|
|
3629
|
+
* and every write refuses — see {@link SecretPassphrase}.
|
|
3630
|
+
*/
|
|
3631
|
+
keyProblem;
|
|
2970
3632
|
constructor(options) {
|
|
2971
3633
|
this.filePath = options.filePath;
|
|
2972
|
-
const
|
|
3634
|
+
const resolved2 = options.passphrase !== void 0 ? { passphrase: options.passphrase } : resolveSecretPassphrase(process.env, this.filePath);
|
|
3635
|
+
const raw = resolved2.passphrase;
|
|
2973
3636
|
this.passphrase = raw && raw.trim() ? raw : void 0;
|
|
3637
|
+
this.keyProblem = resolved2.problem;
|
|
2974
3638
|
}
|
|
2975
3639
|
/**
|
|
2976
3640
|
* True when the *next write* will encrypt. This is the configured policy,
|
|
@@ -2985,7 +3649,8 @@ var FileSecretStore = class {
|
|
|
2985
3649
|
* no file yet, or when it cannot be read or parsed.
|
|
2986
3650
|
*
|
|
2987
3651
|
* Separate from {@link encrypted} because the two genuinely disagree for a
|
|
2988
|
-
* whole session: adding `MCP_INSPECTOR_SECRET_KEY`
|
|
3652
|
+
* whole session: adding a passphrase (`MCP_INSPECTOR_SECRET_KEY` or
|
|
3653
|
+
* `MCP_INSPECTOR_SECRET_KEY_FILE`) to an install that
|
|
2989
3654
|
* already has a plaintext file flips `encrypted` to true immediately,
|
|
2990
3655
|
* while the bytes stay readable until the next `set`. A descriptor built
|
|
2991
3656
|
* from the policy would tell that user "File (encrypted)" while their
|
|
@@ -3009,6 +3674,9 @@ var FileSecretStore = class {
|
|
|
3009
3674
|
* `set` is in fact about to refuse.
|
|
3010
3675
|
*/
|
|
3011
3676
|
async readOnDiskEncryption() {
|
|
3677
|
+
if (this.keyProblem !== void 0) {
|
|
3678
|
+
return { state: "unreadable", detail: this.keyProblem };
|
|
3679
|
+
}
|
|
3012
3680
|
let raw;
|
|
3013
3681
|
try {
|
|
3014
3682
|
raw = await readStoreFile(this.filePath);
|
|
@@ -3046,7 +3714,7 @@ var FileSecretStore = class {
|
|
|
3046
3714
|
} catch (err) {
|
|
3047
3715
|
return {
|
|
3048
3716
|
state: "unreadable",
|
|
3049
|
-
detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV}` : err instanceof Error ? err.message : String(err)
|
|
3717
|
+
detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV} (or ${SECRET_KEY_FILE_ENV})` : err instanceof Error ? err.message : String(err)
|
|
3050
3718
|
};
|
|
3051
3719
|
}
|
|
3052
3720
|
return { state: "encrypted" };
|
|
@@ -3097,6 +3765,11 @@ var FileSecretStore = class {
|
|
|
3097
3765
|
return this.serialize(() => this.readMap());
|
|
3098
3766
|
}
|
|
3099
3767
|
async readMap() {
|
|
3768
|
+
if (this.keyProblem !== void 0) {
|
|
3769
|
+
throw new SecretStoreUnavailableError(
|
|
3770
|
+
`The secrets file at ${this.filePath} cannot be used: ${this.keyProblem}. Refusing to read or write it rather than store secrets unencrypted.`
|
|
3771
|
+
);
|
|
3772
|
+
}
|
|
3100
3773
|
const raw = await readStoreFile(this.filePath);
|
|
3101
3774
|
if (raw === null) return null;
|
|
3102
3775
|
let parsed;
|
|
@@ -3452,6 +4125,7 @@ async function readSecretFilePermissions(filePath) {
|
|
|
3452
4125
|
var SECRET_STORE_ENV = "MCP_INSPECTOR_SECRET_STORE";
|
|
3453
4126
|
var SECRET_FILE_ENV = "MCP_INSPECTOR_SECRET_FILE";
|
|
3454
4127
|
var STORAGE_DIR_ENV = "MCP_STORAGE_DIR";
|
|
4128
|
+
var SECRET_STORAGE_DOCS_URL = "https://github.com/modelcontextprotocol/inspector/blob/main/docs/secret-storage.md";
|
|
3455
4129
|
var KINDS = ["keyring", "file", "memory"];
|
|
3456
4130
|
function parseSecretStoreEnv(raw) {
|
|
3457
4131
|
const value = raw?.trim().toLowerCase();
|
|
@@ -3594,6 +4268,11 @@ function warnAboutSecretStorage(info) {
|
|
|
3594
4268
|
}
|
|
3595
4269
|
const caveat = secretStorageCaveat(info);
|
|
3596
4270
|
if (caveat) console.warn(`[mcp-inspector] ${caveat}`);
|
|
4271
|
+
if (info.reason === "fallback" || caveat) {
|
|
4272
|
+
console.warn(
|
|
4273
|
+
`[mcp-inspector] How the secret store is chosen, and how to secure it: ${SECRET_STORAGE_DOCS_URL}`
|
|
4274
|
+
);
|
|
4275
|
+
}
|
|
3597
4276
|
}
|
|
3598
4277
|
var resolved;
|
|
3599
4278
|
function resolveSecretStore() {
|
|
@@ -3821,7 +4500,7 @@ function defaultSecretStore() {
|
|
|
3821
4500
|
}
|
|
3822
4501
|
|
|
3823
4502
|
// ../../core/client/config-parse.ts
|
|
3824
|
-
import { z } from "zod";
|
|
4503
|
+
import { z as z2 } from "zod";
|
|
3825
4504
|
function isAbsoluteHttpUrl(value) {
|
|
3826
4505
|
let url;
|
|
3827
4506
|
try {
|
|
@@ -3838,17 +4517,21 @@ function isRealHost(hostname) {
|
|
|
3838
4517
|
const labels = hostname.split(".");
|
|
3839
4518
|
return labels.length >= 2 && labels.every((label) => label !== "");
|
|
3840
4519
|
}
|
|
3841
|
-
var HttpUrlStringSchema =
|
|
4520
|
+
var HttpUrlStringSchema = z2.string().min(1).superRefine((val, ctx) => {
|
|
3842
4521
|
const trimmed = val.trim();
|
|
3843
4522
|
if (!isAbsoluteHttpUrl(trimmed)) {
|
|
3844
4523
|
ctx.addIssue({
|
|
3845
|
-
code:
|
|
4524
|
+
code: z2.ZodIssueCode.custom,
|
|
3846
4525
|
message: `Invalid URL: "${trimmed}" \u2014 must be an http(s) URL (e.g. https://idp.example.com)`
|
|
3847
4526
|
});
|
|
3848
4527
|
}
|
|
3849
4528
|
});
|
|
3850
4529
|
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";
|
|
4530
|
+
var CIMD_METADATA_URL_HTTPS_ERROR = "CIMD client metadata URL must use HTTPS, except on localhost, 127.0.0.1 or [::1]";
|
|
4531
|
+
var CIMD_HTTP_EXEMPT_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
4532
|
+
function isCimdHttpExemptHost(hostname) {
|
|
4533
|
+
return CIMD_HTTP_EXEMPT_HOSTS.has(hostname);
|
|
4534
|
+
}
|
|
3852
4535
|
var CIMD_METADATA_URL_PATH_ERROR = "Must include a path (not the site root), like https://example.com/oauth/client.json";
|
|
3853
4536
|
function getCimdClientMetadataUrlError(value) {
|
|
3854
4537
|
const trimmed = value.trim();
|
|
@@ -3858,7 +4541,7 @@ function getCimdClientMetadataUrlError(value) {
|
|
|
3858
4541
|
}
|
|
3859
4542
|
try {
|
|
3860
4543
|
const url = new URL(trimmed);
|
|
3861
|
-
if (url.protocol !== "https:") {
|
|
4544
|
+
if (url.protocol !== "https:" && !isCimdHttpExemptHost(url.hostname)) {
|
|
3862
4545
|
return CIMD_METADATA_URL_HTTPS_ERROR;
|
|
3863
4546
|
}
|
|
3864
4547
|
if (url.pathname === "/" || url.pathname === "") {
|
|
@@ -3874,7 +4557,7 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
3874
4557
|
if (trimmed === "") {
|
|
3875
4558
|
if (required) {
|
|
3876
4559
|
ctx.addIssue({
|
|
3877
|
-
code:
|
|
4560
|
+
code: z2.ZodIssueCode.custom,
|
|
3878
4561
|
message: "CIMD client metadata URL is required when CIMD is enabled"
|
|
3879
4562
|
});
|
|
3880
4563
|
}
|
|
@@ -3883,25 +4566,25 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
3883
4566
|
const error = getCimdClientMetadataUrlError(trimmed);
|
|
3884
4567
|
if (error) {
|
|
3885
4568
|
ctx.addIssue({
|
|
3886
|
-
code:
|
|
4569
|
+
code: z2.ZodIssueCode.custom,
|
|
3887
4570
|
message: error
|
|
3888
4571
|
});
|
|
3889
4572
|
}
|
|
3890
4573
|
}
|
|
3891
|
-
var CimdConfigSchema =
|
|
3892
|
-
enabled:
|
|
3893
|
-
clientMetadataUrl:
|
|
4574
|
+
var CimdConfigSchema = z2.object({
|
|
4575
|
+
enabled: z2.boolean(),
|
|
4576
|
+
clientMetadataUrl: z2.string()
|
|
3894
4577
|
}).superRefine((data, ctx) => {
|
|
3895
4578
|
refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
|
|
3896
4579
|
});
|
|
3897
|
-
var EnterpriseManagedAuthIdpConfigSchema =
|
|
4580
|
+
var EnterpriseManagedAuthIdpConfigSchema = z2.object({
|
|
3898
4581
|
issuer: HttpUrlStringSchema,
|
|
3899
|
-
clientId:
|
|
3900
|
-
clientSecret:
|
|
4582
|
+
clientId: z2.string().min(1),
|
|
4583
|
+
clientSecret: z2.string().optional()
|
|
3901
4584
|
});
|
|
3902
|
-
var ClientConfigSchema =
|
|
3903
|
-
enterpriseManagedAuth:
|
|
3904
|
-
enabled:
|
|
4585
|
+
var ClientConfigSchema = z2.object({
|
|
4586
|
+
enterpriseManagedAuth: z2.object({
|
|
4587
|
+
enabled: z2.boolean().optional(),
|
|
3905
4588
|
idp: EnterpriseManagedAuthIdpConfigSchema
|
|
3906
4589
|
}).optional(),
|
|
3907
4590
|
cimd: CimdConfigSchema.optional()
|
|
@@ -3910,7 +4593,7 @@ function parseClientConfig(raw) {
|
|
|
3910
4593
|
return ClientConfigSchema.parse(raw);
|
|
3911
4594
|
}
|
|
3912
4595
|
function formatClientConfigLoadError(error) {
|
|
3913
|
-
if (error instanceof
|
|
4596
|
+
if (error instanceof z2.ZodError) {
|
|
3914
4597
|
const issue = error.issues[0];
|
|
3915
4598
|
if (issue) {
|
|
3916
4599
|
const field = issue.path.length > 0 ? issue.path.map(String).join(".") : "config";
|
|
@@ -4395,6 +5078,7 @@ function createRemoteApp(options) {
|
|
|
4395
5078
|
onStderr: (entry) => session.onStderr(entry),
|
|
4396
5079
|
onFetchRequest: (entry) => session.onFetchRequest(entry),
|
|
4397
5080
|
onFetchResponseBody: (id, body2) => session.onFetchResponseBody(id, body2),
|
|
5081
|
+
onFetchStreamUpdate: (id, stream) => session.onFetchStreamUpdate(id, stream),
|
|
4398
5082
|
authProvider: authHandle?.provider,
|
|
4399
5083
|
settings: body.settings,
|
|
4400
5084
|
// Always intercept 401/403 on the node MCP transport. Without this, the
|
|
@@ -4629,15 +5313,36 @@ function createRemoteApp(options) {
|
|
|
4629
5313
|
} catch {
|
|
4630
5314
|
return c.json({ error: "Invalid JSON body" }, 400);
|
|
4631
5315
|
}
|
|
4632
|
-
const {
|
|
5316
|
+
const {
|
|
5317
|
+
url,
|
|
5318
|
+
method = "GET",
|
|
5319
|
+
headers = {},
|
|
5320
|
+
body: reqBody,
|
|
5321
|
+
timeoutMs: requestedTimeoutMs
|
|
5322
|
+
} = body;
|
|
4633
5323
|
if (!url) {
|
|
4634
5324
|
return c.json({ error: "Missing url" }, 400);
|
|
4635
5325
|
}
|
|
5326
|
+
const controller = new AbortController();
|
|
5327
|
+
let deadlineFired = false;
|
|
5328
|
+
const clientSignal = c.req.raw.signal;
|
|
5329
|
+
const signal = clientSignal ? AbortSignal.any([controller.signal, clientSignal]) : controller.signal;
|
|
5330
|
+
const deadlineMs = typeof requestedTimeoutMs === "number" && Number.isFinite(requestedTimeoutMs) ? Math.min(2147483647, Math.max(0, Math.round(requestedTimeoutMs))) : void 0;
|
|
5331
|
+
const safeUrl = redactUrlQuery(url);
|
|
5332
|
+
const timer = deadlineMs === void 0 ? void 0 : setTimeout(() => {
|
|
5333
|
+
deadlineFired = true;
|
|
5334
|
+
controller.abort(
|
|
5335
|
+
new Error(
|
|
5336
|
+
`proxied request to ${safeUrl} timed out after ${deadlineMs}ms`
|
|
5337
|
+
)
|
|
5338
|
+
);
|
|
5339
|
+
}, deadlineMs);
|
|
4636
5340
|
try {
|
|
4637
5341
|
const res = await (createProxyFetch() ?? fetch)(url, {
|
|
4638
5342
|
method,
|
|
4639
5343
|
headers: new Headers(headers),
|
|
4640
|
-
body: reqBody
|
|
5344
|
+
body: reqBody,
|
|
5345
|
+
signal
|
|
4641
5346
|
});
|
|
4642
5347
|
const resHeaders = {};
|
|
4643
5348
|
res.headers.forEach((v, k) => {
|
|
@@ -4648,6 +5353,9 @@ function createRemoteApp(options) {
|
|
|
4648
5353
|
let resBody;
|
|
4649
5354
|
if (!isStream && res.body) {
|
|
4650
5355
|
resBody = await res.text();
|
|
5356
|
+
} else if (isStream) {
|
|
5357
|
+
void res.body?.cancel().catch(() => {
|
|
5358
|
+
});
|
|
4651
5359
|
}
|
|
4652
5360
|
return c.json({
|
|
4653
5361
|
ok: res.ok,
|
|
@@ -4658,7 +5366,20 @@ function createRemoteApp(options) {
|
|
|
4658
5366
|
});
|
|
4659
5367
|
} catch (err) {
|
|
4660
5368
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5369
|
+
if (deadlineFired) {
|
|
5370
|
+
return c.json(
|
|
5371
|
+
{
|
|
5372
|
+
error: msg,
|
|
5373
|
+
code: OAUTH_TIMEOUT_WIRE_CODE,
|
|
5374
|
+
url: safeUrl,
|
|
5375
|
+
timeoutMs: deadlineMs
|
|
5376
|
+
},
|
|
5377
|
+
504
|
|
5378
|
+
);
|
|
5379
|
+
}
|
|
4661
5380
|
return c.json({ error: msg }, 500);
|
|
5381
|
+
} finally {
|
|
5382
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4662
5383
|
}
|
|
4663
5384
|
});
|
|
4664
5385
|
app.post("/api/log", async (c) => {
|
|
@@ -4854,6 +5575,18 @@ function createRemoteApp(options) {
|
|
|
4854
5575
|
);
|
|
4855
5576
|
delete valObj.maxFetchRequests;
|
|
4856
5577
|
}
|
|
5578
|
+
for (const key of [
|
|
5579
|
+
"skillCatalogMaxSkills",
|
|
5580
|
+
"skillCatalogMaxBytes"
|
|
5581
|
+
]) {
|
|
5582
|
+
if (key in valObj && !isSkillCatalogLimit(valObj[key])) {
|
|
5583
|
+
logWarn(
|
|
5584
|
+
{ route: "/api/servers", id, droppedKey: key },
|
|
5585
|
+
`Dropping malformed \`${key}\` field \u2014 expected a positive integer.`
|
|
5586
|
+
);
|
|
5587
|
+
delete valObj[key];
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
4857
5590
|
if ("oauth" in valObj && !isOauthObject(valObj.oauth)) {
|
|
4858
5591
|
logWarn(
|
|
4859
5592
|
{ route: "/api/servers", id, droppedKey: "oauth" },
|
|
@@ -5004,12 +5737,29 @@ function createRemoteApp(options) {
|
|
|
5004
5737
|
error: "settings.paginatedLists must be a boolean"
|
|
5005
5738
|
};
|
|
5006
5739
|
}
|
|
5740
|
+
if (obj.suppressNotificationStream !== void 0 && typeof obj.suppressNotificationStream !== "boolean") {
|
|
5741
|
+
return {
|
|
5742
|
+
ok: false,
|
|
5743
|
+
error: "settings.suppressNotificationStream must be a boolean"
|
|
5744
|
+
};
|
|
5745
|
+
}
|
|
5007
5746
|
if (obj.maxFetchRequests !== void 0 && (typeof obj.maxFetchRequests !== "number" || obj.maxFetchRequests < 0)) {
|
|
5008
5747
|
return {
|
|
5009
5748
|
ok: false,
|
|
5010
5749
|
error: "settings.maxFetchRequests must be a non-negative number"
|
|
5011
5750
|
};
|
|
5012
5751
|
}
|
|
5752
|
+
for (const key of [
|
|
5753
|
+
"skillCatalogMaxSkills",
|
|
5754
|
+
"skillCatalogMaxBytes"
|
|
5755
|
+
]) {
|
|
5756
|
+
if (obj[key] !== void 0 && !isSkillCatalogLimit(obj[key])) {
|
|
5757
|
+
return {
|
|
5758
|
+
ok: false,
|
|
5759
|
+
error: `settings.${key} must be a positive integer`
|
|
5760
|
+
};
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5013
5763
|
for (const optional of [
|
|
5014
5764
|
"oauthClientId",
|
|
5015
5765
|
"oauthClientSecret",
|
|
@@ -5095,6 +5845,10 @@ function createRemoteApp(options) {
|
|
|
5095
5845
|
autoRefreshOnListChanged: obj.autoRefreshOnListChanged === true,
|
|
5096
5846
|
// Absent → false, matching the read side (omit-on-false on the write side).
|
|
5097
5847
|
paginatedLists: obj.paginatedLists === true,
|
|
5848
|
+
// Absent → unset (off); only an explicit true is carried (#2317).
|
|
5849
|
+
...obj.suppressNotificationStream === true && {
|
|
5850
|
+
suppressNotificationStream: true
|
|
5851
|
+
},
|
|
5098
5852
|
// Absent → product default, matching the read side. The default is the
|
|
5099
5853
|
// omit-sentinel in inspectorSettingsToStoredFields, so a client that
|
|
5100
5854
|
// didn't send one writes no spurious maxFetchRequests to disk.
|
|
@@ -5105,6 +5859,12 @@ function createRemoteApp(options) {
|
|
|
5105
5859
|
// writes no spurious `roots` field.
|
|
5106
5860
|
roots: isRootArray(obj.roots) ? obj.roots : []
|
|
5107
5861
|
};
|
|
5862
|
+
if (isSkillCatalogLimit(obj.skillCatalogMaxSkills)) {
|
|
5863
|
+
value.skillCatalogMaxSkills = obj.skillCatalogMaxSkills;
|
|
5864
|
+
}
|
|
5865
|
+
if (isSkillCatalogLimit(obj.skillCatalogMaxBytes)) {
|
|
5866
|
+
value.skillCatalogMaxBytes = obj.skillCatalogMaxBytes;
|
|
5867
|
+
}
|
|
5108
5868
|
if (typeof obj.oauthClientId === "string" && obj.oauthClientId !== "") {
|
|
5109
5869
|
value.oauthClientId = obj.oauthClientId;
|
|
5110
5870
|
}
|
|
@@ -5339,7 +6099,7 @@ function createRemoteApp(options) {
|
|
|
5339
6099
|
type,
|
|
5340
6100
|
process.platform,
|
|
5341
6101
|
homedir(),
|
|
5342
|
-
(path6) => existsSync3(path6) ?
|
|
6102
|
+
(path6) => existsSync3(path6) ? readFileSync6(path6, "utf-8") : null
|
|
5343
6103
|
);
|
|
5344
6104
|
if (!result) {
|
|
5345
6105
|
return c.json(
|
|
@@ -5667,12 +6427,14 @@ function honoMiddlewarePlugin(config) {
|
|
|
5667
6427
|
const sandboxController = createSandboxController({
|
|
5668
6428
|
port: config.sandboxPort,
|
|
5669
6429
|
host: config.sandboxHost,
|
|
5670
|
-
allowedOrigins: config.allowedOrigins
|
|
6430
|
+
allowedOrigins: config.allowedOrigins,
|
|
6431
|
+
publicUrl: config.sandboxPublicUrl
|
|
5671
6432
|
});
|
|
5672
6433
|
await sandboxController.start();
|
|
5673
6434
|
const appOriginController = createAppOriginController({
|
|
5674
6435
|
port: config.appOriginPort,
|
|
5675
6436
|
host: config.sandboxHost,
|
|
6437
|
+
publicOrigin: config.appOriginPublicOrigin,
|
|
5676
6438
|
embedderOrigins: appDocumentEmbedders(
|
|
5677
6439
|
sandboxController.getUrl(),
|
|
5678
6440
|
config.allowedOrigins
|
|
@@ -5890,6 +6652,33 @@ function clearViteDepsCache(clientWebRoot) {
|
|
|
5890
6652
|
// ../../vitest.shared.mts
|
|
5891
6653
|
import path5 from "path";
|
|
5892
6654
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6655
|
+
var NO_RETRY_SETUP = path5.resolve(
|
|
6656
|
+
path5.dirname(fileURLToPath3(import.meta.url)),
|
|
6657
|
+
"vitest.setup.shared.mts"
|
|
6658
|
+
);
|
|
6659
|
+
var TIMEOUTS = Object.freeze({
|
|
6660
|
+
/**
|
|
6661
|
+
* 3x Vitest's default. Covers the measured load; past this a genuinely hung
|
|
6662
|
+
* unit test costs the whole budget to discover. Both projects that had
|
|
6663
|
+
* already answered this question by hand — `clients/cli` and web's
|
|
6664
|
+
* `storybook` (#2292) — independently arrived at 15000, which is why it is
|
|
6665
|
+
* the shared value rather than a new one.
|
|
6666
|
+
*/
|
|
6667
|
+
testTimeout: 15e3,
|
|
6668
|
+
/**
|
|
6669
|
+
* Hooks do the setup and teardown a test's own budget never covers —
|
|
6670
|
+
* spawning servers, provisioning temp dirs, unlinking filesystem-backed
|
|
6671
|
+
* storage. They are also where the real-transitions auto-settle in
|
|
6672
|
+
* `clients/web/src/test/renderWithMantine.tsx` awaits, so this is the one
|
|
6673
|
+
* budget in the repo that actually governs a deliberate wait.
|
|
6674
|
+
*/
|
|
6675
|
+
hookTimeout: 3e4,
|
|
6676
|
+
teardownTimeout: 3e4
|
|
6677
|
+
});
|
|
6678
|
+
var INTEGRATION_TIMEOUTS = Object.freeze({
|
|
6679
|
+
...TIMEOUTS,
|
|
6680
|
+
testTimeout: 3e4
|
|
6681
|
+
});
|
|
5893
6682
|
function vitestSharedPaths(clientDir) {
|
|
5894
6683
|
const dirname10 = path5.resolve(clientDir);
|
|
5895
6684
|
const repoRoot = path5.resolve(dirname10, "../..");
|
|
@@ -6119,7 +6908,7 @@ async function startViteDevServer(config) {
|
|
|
6119
6908
|
}
|
|
6120
6909
|
|
|
6121
6910
|
// server/server.ts
|
|
6122
|
-
import { readFileSync as
|
|
6911
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
6123
6912
|
import { join as join8, dirname as dirname8 } from "path";
|
|
6124
6913
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
6125
6914
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
@@ -6132,12 +6921,14 @@ async function startHonoServer(config) {
|
|
|
6132
6921
|
const sandboxController = createSandboxController({
|
|
6133
6922
|
port: config.sandboxPort,
|
|
6134
6923
|
host: config.sandboxHost,
|
|
6135
|
-
allowedOrigins: config.allowedOrigins
|
|
6924
|
+
allowedOrigins: config.allowedOrigins,
|
|
6925
|
+
publicUrl: config.sandboxPublicUrl
|
|
6136
6926
|
});
|
|
6137
6927
|
await sandboxController.start();
|
|
6138
6928
|
const appOriginController = createAppOriginController({
|
|
6139
6929
|
port: config.appOriginPort,
|
|
6140
6930
|
host: config.sandboxHost,
|
|
6931
|
+
publicOrigin: config.appOriginPublicOrigin,
|
|
6141
6932
|
embedderOrigins: appDocumentEmbedders(
|
|
6142
6933
|
sandboxController.getUrl(),
|
|
6143
6934
|
config.allowedOrigins
|
|
@@ -6171,7 +6962,7 @@ async function startHonoServer(config) {
|
|
|
6171
6962
|
});
|
|
6172
6963
|
const serveIndexHtml = (c) => {
|
|
6173
6964
|
const indexPath = join8(rootPath, "index.html");
|
|
6174
|
-
const html =
|
|
6965
|
+
const html = readFileSync7(indexPath, "utf-8");
|
|
6175
6966
|
c.header("Cache-Control", "no-store");
|
|
6176
6967
|
return c.html(injectAuthToken(html, resolvedAuthToken));
|
|
6177
6968
|
};
|
|
@@ -6310,6 +7101,10 @@ async function runWeb(argv) {
|
|
|
6310
7101
|
'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
|
|
6311
7102
|
parseHeaderPair,
|
|
6312
7103
|
{}
|
|
7104
|
+
).option(
|
|
7105
|
+
"--protocol-era <era>",
|
|
7106
|
+
"protocol era for the ad-hoc server: legacy, auto, or modern (default legacy)",
|
|
7107
|
+
parseProtocolEra
|
|
6313
7108
|
).option("--dev", "run in development mode (Vite)").parse(preArgs);
|
|
6314
7109
|
const opts = program.opts();
|
|
6315
7110
|
const args = program.args;
|
|
@@ -6319,8 +7114,9 @@ async function runWeb(argv) {
|
|
|
6319
7114
|
const hasCatalog = !!catalogPath;
|
|
6320
7115
|
const hasConfig = !!opts.config;
|
|
6321
7116
|
const hasHeaders = !!opts.header && Object.keys(opts.header).length > 0;
|
|
7117
|
+
const hasProtocolEra = opts.protocolEra !== void 0;
|
|
6322
7118
|
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;
|
|
7119
|
+
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
7120
|
if (conflict) {
|
|
6325
7121
|
console.error(`Error: ${conflict}`);
|
|
6326
7122
|
process.exit(1);
|
|
@@ -6385,6 +7181,9 @@ async function runWeb(argv) {
|
|
|
6385
7181
|
}
|
|
6386
7182
|
initialMcpConfig = config;
|
|
6387
7183
|
const entry = hasHeaders && config.type !== "stdio" && config.type !== void 0 ? { ...config, headers: opts.header } : { ...config };
|
|
7184
|
+
if (opts.protocolEra !== void 0) {
|
|
7185
|
+
entry.protocolEra = opts.protocolEra;
|
|
7186
|
+
}
|
|
6388
7187
|
initialServers = {
|
|
6389
7188
|
mcpServers: { [deriveSeedServerId(config)]: entry }
|
|
6390
7189
|
};
|
|
@@ -6394,6 +7193,11 @@ async function runWeb(argv) {
|
|
|
6394
7193
|
"Error: --header requires an ad-hoc --server-url or command target."
|
|
6395
7194
|
);
|
|
6396
7195
|
process.exit(1);
|
|
7196
|
+
} else if (hasProtocolEra) {
|
|
7197
|
+
console.error(
|
|
7198
|
+
"Error: --protocol-era requires an ad-hoc --server-url or command target."
|
|
7199
|
+
);
|
|
7200
|
+
process.exit(1);
|
|
6397
7201
|
}
|
|
6398
7202
|
let webConfig;
|
|
6399
7203
|
try {
|