@modelcontextprotocol/inspector 2.13.0 → 2.15.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 +1 -1
- package/clients/cli/build/index.js +5481 -1847
- package/clients/mcpdo/build/mcp-bin.js +925 -186
- package/clients/mcpdo/build/mcpdod.js +2 -3
- package/clients/mcpdo/build/{server-BHuKGkMj.js → server-B-CUVIBj.js} +3544 -995
- package/clients/tui/build/index.js +2126 -570
- package/clients/web/build/index.js +459 -280
- package/clients/web/dist/assets/fredoka-normal-hebrew-CjvktpUN.woff2 +0 -0
- package/clients/web/dist/assets/fredoka-normal-latin-DOQG_Jwn.woff2 +0 -0
- package/clients/web/dist/assets/fredoka-normal-latin-ext-tYNUQHO6.woff2 +0 -0
- package/clients/web/dist/assets/index-BZ94R6fh.js +1599 -0
- package/clients/web/dist/assets/{index-dqp8Sd8W.css → index-tBehH5iF.css} +1 -1
- package/clients/web/dist/assets/{prism-light-BIhJtJkv.js → prism-light-Crps--E8.js} +1 -1
- package/clients/web/dist/assets/roboto-mono-italic-cyrillic-C1E63de8.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-italic-cyrillic-ext-DR9ovEqT.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-italic-greek-Cs2u2nVh.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-italic-latin-D_P-kX1t.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-italic-latin-ext-CErTcBzw.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-italic-vietnamese-D4UvzJMM.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-normal-cyrillic-DfzWIUPd.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-normal-cyrillic-ext-D8QBEkUe.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-normal-greek-DlC4X1lQ.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-normal-latin-BAKpfWyj.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-normal-latin-ext-CSSVNFqV.woff2 +0 -0
- package/clients/web/dist/assets/roboto-mono-normal-vietnamese-DFkHeOHB.woff2 +0 -0
- package/clients/web/dist/assets/worker-json-DEGsizKa.js +7859 -0
- package/clients/web/dist/fonts/Fredoka-OFL.txt +93 -0
- package/clients/web/dist/fonts/RobotoMono-OFL.txt +93 -0
- package/clients/web/dist/index.html +2 -5
- package/clients/web/static/sandbox_proxy.html +177 -6
- package/package.json +8 -4
- package/skills/mcpdo/SKILL.md +63 -2
- package/clients/web/dist/assets/index-BqEbhCDX.js +0 -1587
- package/clients/web/dist/assets/worker-json-8CDXFfik.js +0 -1631
|
@@ -22,7 +22,7 @@ import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypt
|
|
|
22
22
|
import { createSecureContext } from "node:tls";
|
|
23
23
|
import { createServer as createServer$1 } from "vite";
|
|
24
24
|
import react from "@vitejs/plugin-react";
|
|
25
|
-
import { homedir } from "node:os";
|
|
25
|
+
import { homedir, tmpdir } from "node:os";
|
|
26
26
|
import { Hono } from "hono";
|
|
27
27
|
import { streamSSE } from "hono/streaming";
|
|
28
28
|
import { bodyLimit } from "hono/body-limit";
|
|
@@ -37,20 +37,18 @@ import { createServer as createServer$2 } from "node:https";
|
|
|
37
37
|
import { serve } from "@hono/node-server";
|
|
38
38
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
39
39
|
import { spawnSync } from "node:child_process";
|
|
40
|
-
|
|
40
|
+
// ../../core/storage/store-id.ts
|
|
41
41
|
function validateStoreId(storeId) {
|
|
42
42
|
return /^[a-zA-Z0-9_-]+$/.test(storeId) && storeId !== "__proto__";
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
//#region ../../core/storage/store-serialize.ts
|
|
44
|
+
// ../../core/storage/store-serialize.ts
|
|
46
45
|
function serializeStore(data) {
|
|
47
46
|
return JSON.stringify(data, null, 2);
|
|
48
47
|
}
|
|
49
48
|
function parseStore(raw) {
|
|
50
49
|
return JSON.parse(raw);
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
//#region ../../core/storage/store-io.ts
|
|
51
|
+
// ../../core/storage/store-io.ts
|
|
54
52
|
function getDefaultStorageDir() {
|
|
55
53
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
56
54
|
return path$1.join(homeDir, ".mcp-inspector", "storage");
|
|
@@ -96,8 +94,7 @@ async function deleteStoreFile(filePath) {
|
|
|
96
94
|
if (error.code !== "ENOENT") throw error;
|
|
97
95
|
}
|
|
98
96
|
}
|
|
99
|
-
|
|
100
|
-
//#region ../../core/mcp/types.ts
|
|
97
|
+
// ../../core/mcp/types.ts
|
|
101
98
|
const DEFAULT_TASK_TTL_MS = 6e4;
|
|
102
99
|
const DEFAULT_MAX_FETCH_REQUESTS = 1e3;
|
|
103
100
|
const MODERN_LOG_LEVELS = [
|
|
@@ -114,8 +111,7 @@ const MODERN_LOG_LEVELS = [
|
|
|
114
111
|
function isModernLogLevel(value) {
|
|
115
112
|
return typeof value === "string" && MODERN_LOG_LEVELS.includes(value);
|
|
116
113
|
}
|
|
117
|
-
|
|
118
|
-
//#region ../../core/extension/skills/schemas.ts
|
|
114
|
+
// ../../core/extension/skills/schemas.ts
|
|
119
115
|
const CACHE_FIELDS = {
|
|
120
116
|
ttlMs: ReadResourceDirectoryResultSchema.shape.ttlMs,
|
|
121
117
|
cacheScope: SkillCacheScopeSchema
|
|
@@ -227,13 +223,11 @@ SKILL_LIMITS.bytes;
|
|
|
227
223
|
function isSkillCatalogLimit(value) {
|
|
228
224
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
229
225
|
}
|
|
230
|
-
|
|
231
|
-
//#region ../../core/auth/secret-fields.ts
|
|
226
|
+
// ../../core/auth/secret-fields.ts
|
|
232
227
|
const SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
|
|
233
228
|
const SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
|
|
234
229
|
const envSecretField = (envKey) => `env:${envKey}`;
|
|
235
|
-
|
|
236
|
-
//#region ../../core/json/jsonUtils.ts
|
|
230
|
+
// ../../core/json/jsonUtils.ts
|
|
237
231
|
function isSerializableJson(value) {
|
|
238
232
|
if (value === null) return true;
|
|
239
233
|
switch (typeof value) {
|
|
@@ -247,8 +241,7 @@ function isSerializableJson(value) {
|
|
|
247
241
|
function toRecord(value) {
|
|
248
242
|
return value;
|
|
249
243
|
}
|
|
250
|
-
|
|
251
|
-
//#region ../../core/mcp/serverList.ts
|
|
244
|
+
// ../../core/mcp/serverList.ts
|
|
252
245
|
const VALID_SERVER_TYPES = /* @__PURE__ */ new Set([
|
|
253
246
|
"stdio",
|
|
254
247
|
"sse",
|
|
@@ -556,28 +549,38 @@ function expectedSecretFields(stored) {
|
|
|
556
549
|
if (isStdioStored(stored) && stored.env) for (const k of Object.keys(stored.env)) fields.push(envSecretField(k));
|
|
557
550
|
return fields;
|
|
558
551
|
}
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
"@modelcontextprotocol/server-filesystem",
|
|
566
|
-
"/tmp"
|
|
567
|
-
]
|
|
568
|
-
},
|
|
569
|
-
"everything-server-default": {
|
|
570
|
-
type: "stdio",
|
|
571
|
-
command: "npx",
|
|
572
|
-
args: ["-y", "@modelcontextprotocol/server-everything"]
|
|
573
|
-
},
|
|
574
|
-
"example-server-default": {
|
|
575
|
-
type: "streamable-http",
|
|
576
|
-
url: "https://example-server.modelcontextprotocol.io/mcp"
|
|
552
|
+
const EXAMPLE_SERVER_URL = "https://example-server.modelcontextprotocol.io/mcp";
|
|
553
|
+
function adHocServerName(config) {
|
|
554
|
+
if (config.type === "sse" || config.type === "streamable-http") try {
|
|
555
|
+
return new URL(config.url).host || "server";
|
|
556
|
+
} catch {
|
|
557
|
+
return "server";
|
|
577
558
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
559
|
+
return config.command?.split(/[\\/]/).pop() || "server";
|
|
560
|
+
}
|
|
561
|
+
function defaultSeedConfig(tempDir) {
|
|
562
|
+
return { mcpServers: {
|
|
563
|
+
"filesystem-server-default": {
|
|
564
|
+
type: "stdio",
|
|
565
|
+
command: "npx",
|
|
566
|
+
args: [
|
|
567
|
+
"-y",
|
|
568
|
+
"@modelcontextprotocol/server-filesystem",
|
|
569
|
+
tempDir
|
|
570
|
+
]
|
|
571
|
+
},
|
|
572
|
+
"everything-server-default": {
|
|
573
|
+
type: "stdio",
|
|
574
|
+
command: "npx",
|
|
575
|
+
args: ["-y", "@modelcontextprotocol/server-everything"]
|
|
576
|
+
},
|
|
577
|
+
"example-server-default": {
|
|
578
|
+
type: "streamable-http",
|
|
579
|
+
url: EXAMPLE_SERVER_URL
|
|
580
|
+
}
|
|
581
|
+
} };
|
|
582
|
+
}
|
|
583
|
+
// ../../core/storage/own-entry.ts
|
|
581
584
|
function setOwnEntry(map, key, value) {
|
|
582
585
|
Object.defineProperty(map, key, {
|
|
583
586
|
value,
|
|
@@ -589,8 +592,7 @@ function setOwnEntry(map, key, value) {
|
|
|
589
592
|
function getOwnEntry(map, key) {
|
|
590
593
|
return map !== void 0 && Object.hasOwn(map, key) ? map[key] : void 0;
|
|
591
594
|
}
|
|
592
|
-
|
|
593
|
-
//#region ../../core/mcp/node/config.ts
|
|
595
|
+
// ../../core/mcp/node/config.ts
|
|
594
596
|
function parseKeyValuePair(value, previous = {}) {
|
|
595
597
|
const parts = value.split("=");
|
|
596
598
|
const key = parts[0] ?? "";
|
|
@@ -759,8 +761,7 @@ function resolveServerConfigs(options, mode) {
|
|
|
759
761
|
}
|
|
760
762
|
const API_SERVER_ENV_VARS = { AUTH_TOKEN: "MCP_INSPECTOR_API_TOKEN" };
|
|
761
763
|
const INSPECTOR_API_TOKEN_GLOBAL = "__INSPECTOR_API_TOKEN__";
|
|
762
|
-
|
|
763
|
-
//#region ../../core/auth/secret-storage-info.ts
|
|
764
|
+
// ../../core/auth/secret-storage-info.ts
|
|
764
765
|
function secretStorageLabel(info) {
|
|
765
766
|
switch (info.kind) {
|
|
766
767
|
case "keyring": return "OS keychain";
|
|
@@ -787,8 +788,7 @@ function secretStorageSummary(info) {
|
|
|
787
788
|
if (info.reason === "fallback") parts.push("— fell back from the OS keychain");
|
|
788
789
|
return parts.join(" ");
|
|
789
790
|
}
|
|
790
|
-
|
|
791
|
-
//#region ../../core/node/version.ts
|
|
791
|
+
// ../../core/node/version.ts
|
|
792
792
|
const ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
|
793
793
|
function readInspectorVersion(callerUrl) {
|
|
794
794
|
const startDir = dirname(fileURLToPath(callerUrl));
|
|
@@ -812,8 +812,7 @@ function readInspectorVersionSafe(callerUrl) {
|
|
|
812
812
|
return;
|
|
813
813
|
}
|
|
814
814
|
}
|
|
815
|
-
|
|
816
|
-
//#region ../../core/node/openUrl.ts
|
|
815
|
+
// ../../core/node/openUrl.ts
|
|
817
816
|
const OPEN_URL_TIMEOUT_MS = 5e3;
|
|
818
817
|
const OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
819
818
|
function toOpenableHref(href) {
|
|
@@ -841,8 +840,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
|
841
840
|
clearTimeout(timer);
|
|
842
841
|
}
|
|
843
842
|
}
|
|
844
|
-
|
|
845
|
-
//#region ../../core/node/hostUrl.ts
|
|
843
|
+
// ../../core/node/hostUrl.ts
|
|
846
844
|
function stripBrackets(host) {
|
|
847
845
|
return host.replace(/^\[(.*)\]$/, "$1");
|
|
848
846
|
}
|
|
@@ -895,8 +893,7 @@ function isLoopbackHost(host) {
|
|
|
895
893
|
const h = canonicalUrlHost(host).replace(/\.$/, "");
|
|
896
894
|
return h === "localhost" || h === "[::1]" || /^127(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/.test(h);
|
|
897
895
|
}
|
|
898
|
-
|
|
899
|
-
//#region server/resolve-bind-host.ts
|
|
896
|
+
// server/resolve-bind-host.ts
|
|
900
897
|
const BIND_ALL_INTERFACES_ENV = "DANGEROUSLY_BIND_ALL_INTERFACES";
|
|
901
898
|
function isEnvFlagEnabled(value) {
|
|
902
899
|
const v = value?.trim().toLowerCase();
|
|
@@ -912,9 +909,102 @@ function resolveBindHostname(env = process.env) {
|
|
|
912
909
|
}
|
|
913
910
|
return stripBrackets(host);
|
|
914
911
|
}
|
|
915
|
-
|
|
916
|
-
|
|
912
|
+
// server/host-allow-list.ts
|
|
913
|
+
const ALLOWED_HOSTS_ENV = "MCP_INSPECTOR_ALLOWED_HOSTS";
|
|
914
|
+
const HOSTNAME_RE = /^[a-z0-9._-]+$/;
|
|
915
|
+
function normalizeHostname(hostname) {
|
|
916
|
+
return hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
917
|
+
}
|
|
918
|
+
function parseHostHeader(value) {
|
|
919
|
+
const raw = value.trim().toLowerCase();
|
|
920
|
+
let hostname;
|
|
921
|
+
let port;
|
|
922
|
+
if (raw.startsWith("[")) {
|
|
923
|
+
const end = raw.indexOf("]");
|
|
924
|
+
if (end < 0) return null;
|
|
925
|
+
hostname = raw.slice(0, end + 1);
|
|
926
|
+
if (!isIPv6(hostname.slice(1, -1))) return null;
|
|
927
|
+
const rest = raw.slice(end + 1);
|
|
928
|
+
if (rest !== "") {
|
|
929
|
+
if (!rest.startsWith(":")) return null;
|
|
930
|
+
port = rest.slice(1);
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
const colon = raw.indexOf(":");
|
|
934
|
+
hostname = colon < 0 ? raw : raw.slice(0, colon);
|
|
935
|
+
port = colon < 0 ? void 0 : raw.slice(colon + 1);
|
|
936
|
+
}
|
|
937
|
+
if (port !== void 0) {
|
|
938
|
+
if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return null;
|
|
939
|
+
}
|
|
940
|
+
if (hostname.startsWith("[")) return hostname;
|
|
941
|
+
const name = normalizeHostname(hostname);
|
|
942
|
+
return HOSTNAME_RE.test(name) ? name : null;
|
|
943
|
+
}
|
|
944
|
+
function parseAllowedHostsEnv(value) {
|
|
945
|
+
if (!value) return [];
|
|
946
|
+
const out = [];
|
|
947
|
+
for (const entry of value.split(",")) {
|
|
948
|
+
const trimmed = entry.trim();
|
|
949
|
+
if (trimmed === "") continue;
|
|
950
|
+
const domain = trimmed.startsWith(".") ? "." : "";
|
|
951
|
+
const ascii = /[\s/:*@]/.test(trimmed) ? "" : domainToASCII(normalizeHostname(trimmed.slice(domain.length)));
|
|
952
|
+
if (!HOSTNAME_RE.test(ascii)) {
|
|
953
|
+
console.warn(`Ignoring invalid ${ALLOWED_HOSTS_ENV} entry: ${trimmed} (list bare hostnames, e.g. inspector.example.com or .example.com)`);
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
out.push(domain + ascii);
|
|
957
|
+
}
|
|
958
|
+
return out;
|
|
959
|
+
}
|
|
960
|
+
function configuredAllowedHosts(options) {
|
|
961
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
962
|
+
if (!isAllInterfacesHost(options.bindHost)) hosts.add(normalizeHostname(canonicalUrlHost(options.bindHost)));
|
|
963
|
+
for (const origin of options.allowedOrigins) try {
|
|
964
|
+
const { hostname } = new URL(origin);
|
|
965
|
+
if (hostname !== "") hosts.add(normalizeHostname(hostname));
|
|
966
|
+
} catch {}
|
|
967
|
+
for (const extra of options.extraHosts ?? []) hosts.add(normalizeHostname(extra));
|
|
968
|
+
return [...hosts];
|
|
969
|
+
}
|
|
970
|
+
function createHostAllowList(options) {
|
|
971
|
+
const configured = configuredAllowedHosts(options);
|
|
972
|
+
return (hostHeader) => {
|
|
973
|
+
if (hostHeader === void 0) return false;
|
|
974
|
+
const hostname = parseHostHeader(hostHeader);
|
|
975
|
+
if (hostname === null) return false;
|
|
976
|
+
if (hostname.startsWith("[")) return true;
|
|
977
|
+
if (isIPv4(hostname)) return true;
|
|
978
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
|
979
|
+
for (const allowed of configured) {
|
|
980
|
+
if (allowed === hostname) return true;
|
|
981
|
+
if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) return true;
|
|
982
|
+
}
|
|
983
|
+
return false;
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function hostAllowListMiddleware(options) {
|
|
987
|
+
const isAllowed = createHostAllowList(options);
|
|
988
|
+
return async (c, next) => {
|
|
989
|
+
const host = c.req.header("host");
|
|
990
|
+
if (!isAllowed(host)) return c.text(`Forbidden: Host "${host ?? ""}" is not allowed. To serve the Inspector under this name, add it to ${ALLOWED_HOSTS_ENV} (or to ALLOWED_ORIGINS).`, 403);
|
|
991
|
+
await next();
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
// server/sandbox-controller.ts
|
|
917
995
|
const __dirname$3 = dirname(fileURLToPath(import.meta.url));
|
|
996
|
+
function isSandboxPath(url) {
|
|
997
|
+
const path = (url ?? "").split("?", 1)[0];
|
|
998
|
+
return path === "/sandbox" || path === "/sandbox/";
|
|
999
|
+
}
|
|
1000
|
+
function publicUrlHosts(publicUrl) {
|
|
1001
|
+
if (!publicUrl) return [];
|
|
1002
|
+
try {
|
|
1003
|
+
return [new URL(publicUrl).hostname];
|
|
1004
|
+
} catch {
|
|
1005
|
+
return [];
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
918
1008
|
const LOOPBACK_FRAME_ANCESTORS = ["http://127.0.0.1:*", "http://localhost:*"];
|
|
919
1009
|
const CSP_HOST_SOURCE = /^[a-z][a-z0-9+.-]*:\/\/[^\s;,'"[\]*]+$/i;
|
|
920
1010
|
function sandboxFrameAncestors(allowedOrigins) {
|
|
@@ -948,7 +1038,12 @@ function resolveSandboxPort() {
|
|
|
948
1038
|
return fromSandbox ?? parseListenPort$1(process.env.SERVER_PORT) ?? 6275;
|
|
949
1039
|
}
|
|
950
1040
|
function createSandboxController(options) {
|
|
951
|
-
const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl } = options;
|
|
1041
|
+
const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl, allowedHosts } = options;
|
|
1042
|
+
const isAllowedHost = createHostAllowList({
|
|
1043
|
+
bindHost: host,
|
|
1044
|
+
allowedOrigins: allowedOrigins ?? [],
|
|
1045
|
+
extraHosts: [...allowedHosts ?? [], ...publicUrlHosts(publicUrl)]
|
|
1046
|
+
});
|
|
952
1047
|
let server = null;
|
|
953
1048
|
let sandboxUrl = null;
|
|
954
1049
|
let boundPort = 0;
|
|
@@ -977,18 +1072,25 @@ function createSandboxController(options) {
|
|
|
977
1072
|
resolve(value);
|
|
978
1073
|
};
|
|
979
1074
|
server = createServer((req, res) => {
|
|
980
|
-
if (req.
|
|
1075
|
+
if (!isAllowedHost(req.headers.host)) {
|
|
1076
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
1077
|
+
res.end(`Forbidden: Host "${req.headers.host ?? ""}" is not allowed. To serve the sandbox under this name, add it to ${ALLOWED_HOSTS_ENV} (or to ALLOWED_ORIGINS).`);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const isHead = req.method === "HEAD";
|
|
1081
|
+
if (req.method !== "GET" && !isHead || !isSandboxPath(req.url)) {
|
|
981
1082
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
982
1083
|
res.end("Not Found");
|
|
983
1084
|
return;
|
|
984
1085
|
}
|
|
985
1086
|
res.writeHead(200, {
|
|
986
1087
|
"Content-Type": "text/html; charset=utf-8",
|
|
1088
|
+
"Content-Length": Buffer.byteLength(sandboxHtml),
|
|
987
1089
|
"Cache-Control": "no-store, no-cache, must-revalidate",
|
|
988
1090
|
Pragma: "no-cache",
|
|
989
1091
|
"Content-Security-Policy": SANDBOX_PROXY_CSP
|
|
990
1092
|
});
|
|
991
|
-
res.end(sandboxHtml);
|
|
1093
|
+
res.end(isHead ? void 0 : sandboxHtml);
|
|
992
1094
|
});
|
|
993
1095
|
let retriedDynamic = false;
|
|
994
1096
|
server.on("error", (err) => {
|
|
@@ -1210,8 +1312,7 @@ function createAppOriginController(options) {
|
|
|
1210
1312
|
}
|
|
1211
1313
|
};
|
|
1212
1314
|
}
|
|
1213
|
-
|
|
1214
|
-
//#region server/public-address.ts
|
|
1315
|
+
// server/public-address.ts
|
|
1215
1316
|
const SANDBOX_FULL_ADDRESS_ENV = "MCP_SANDBOX_FULL_ADDRESS";
|
|
1216
1317
|
const APP_ORIGIN_FULL_ADDRESS_ENV = "MCP_APP_ORIGIN_FULL_ADDRESS";
|
|
1217
1318
|
const SANDBOX_PATH = "/sandbox";
|
|
@@ -1280,8 +1381,7 @@ function resolveAppOriginPublicOrigin(raw, allowedOrigins, sandboxUrl) {
|
|
|
1280
1381
|
warnMixedContent(APP_ORIGIN_FULL_ADDRESS_ENV, url, allowedOrigins);
|
|
1281
1382
|
return url.origin;
|
|
1282
1383
|
}
|
|
1283
|
-
|
|
1284
|
-
//#region server/tls.ts
|
|
1384
|
+
// server/tls.ts
|
|
1285
1385
|
const TLS_KEY_PATH_ENV = "INSPECTOR_TLS_KEY_PATH";
|
|
1286
1386
|
const TLS_CERT_PATH_ENV = "INSPECTOR_TLS_CERT_PATH";
|
|
1287
1387
|
function webScheme(tls) {
|
|
@@ -1317,91 +1417,7 @@ function resolveTlsConfig(env = process.env) {
|
|
|
1317
1417
|
cert
|
|
1318
1418
|
};
|
|
1319
1419
|
}
|
|
1320
|
-
|
|
1321
|
-
//#region server/host-allow-list.ts
|
|
1322
|
-
const ALLOWED_HOSTS_ENV = "MCP_INSPECTOR_ALLOWED_HOSTS";
|
|
1323
|
-
const HOSTNAME_RE = /^[a-z0-9._-]+$/;
|
|
1324
|
-
function normalizeHostname(hostname) {
|
|
1325
|
-
return hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
1326
|
-
}
|
|
1327
|
-
function parseHostHeader(value) {
|
|
1328
|
-
const raw = value.trim().toLowerCase();
|
|
1329
|
-
let hostname;
|
|
1330
|
-
let port;
|
|
1331
|
-
if (raw.startsWith("[")) {
|
|
1332
|
-
const end = raw.indexOf("]");
|
|
1333
|
-
if (end < 0) return null;
|
|
1334
|
-
hostname = raw.slice(0, end + 1);
|
|
1335
|
-
if (!isIPv6(hostname.slice(1, -1))) return null;
|
|
1336
|
-
const rest = raw.slice(end + 1);
|
|
1337
|
-
if (rest !== "") {
|
|
1338
|
-
if (!rest.startsWith(":")) return null;
|
|
1339
|
-
port = rest.slice(1);
|
|
1340
|
-
}
|
|
1341
|
-
} else {
|
|
1342
|
-
const colon = raw.indexOf(":");
|
|
1343
|
-
hostname = colon < 0 ? raw : raw.slice(0, colon);
|
|
1344
|
-
port = colon < 0 ? void 0 : raw.slice(colon + 1);
|
|
1345
|
-
}
|
|
1346
|
-
if (port !== void 0) {
|
|
1347
|
-
if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return null;
|
|
1348
|
-
}
|
|
1349
|
-
if (hostname.startsWith("[")) return hostname;
|
|
1350
|
-
const name = normalizeHostname(hostname);
|
|
1351
|
-
return HOSTNAME_RE.test(name) ? name : null;
|
|
1352
|
-
}
|
|
1353
|
-
function parseAllowedHostsEnv(value) {
|
|
1354
|
-
if (!value) return [];
|
|
1355
|
-
const out = [];
|
|
1356
|
-
for (const entry of value.split(",")) {
|
|
1357
|
-
const trimmed = entry.trim();
|
|
1358
|
-
if (trimmed === "") continue;
|
|
1359
|
-
const domain = trimmed.startsWith(".") ? "." : "";
|
|
1360
|
-
const ascii = /[\s/:*@]/.test(trimmed) ? "" : domainToASCII(normalizeHostname(trimmed.slice(domain.length)));
|
|
1361
|
-
if (!HOSTNAME_RE.test(ascii)) {
|
|
1362
|
-
console.warn(`Ignoring invalid ${ALLOWED_HOSTS_ENV} entry: ${trimmed} (list bare hostnames, e.g. inspector.example.com or .example.com)`);
|
|
1363
|
-
continue;
|
|
1364
|
-
}
|
|
1365
|
-
out.push(domain + ascii);
|
|
1366
|
-
}
|
|
1367
|
-
return out;
|
|
1368
|
-
}
|
|
1369
|
-
function configuredAllowedHosts(options) {
|
|
1370
|
-
const hosts = /* @__PURE__ */ new Set();
|
|
1371
|
-
if (!isAllInterfacesHost(options.bindHost)) hosts.add(normalizeHostname(canonicalUrlHost(options.bindHost)));
|
|
1372
|
-
for (const origin of options.allowedOrigins) try {
|
|
1373
|
-
const { hostname } = new URL(origin);
|
|
1374
|
-
if (hostname !== "") hosts.add(normalizeHostname(hostname));
|
|
1375
|
-
} catch {}
|
|
1376
|
-
for (const extra of options.extraHosts ?? []) hosts.add(normalizeHostname(extra));
|
|
1377
|
-
return [...hosts];
|
|
1378
|
-
}
|
|
1379
|
-
function createHostAllowList(options) {
|
|
1380
|
-
const configured = configuredAllowedHosts(options);
|
|
1381
|
-
return (hostHeader) => {
|
|
1382
|
-
if (hostHeader === void 0) return false;
|
|
1383
|
-
const hostname = parseHostHeader(hostHeader);
|
|
1384
|
-
if (hostname === null) return false;
|
|
1385
|
-
if (hostname.startsWith("[")) return true;
|
|
1386
|
-
if (isIPv4(hostname)) return true;
|
|
1387
|
-
if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
|
1388
|
-
for (const allowed of configured) {
|
|
1389
|
-
if (allowed === hostname) return true;
|
|
1390
|
-
if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) return true;
|
|
1391
|
-
}
|
|
1392
|
-
return false;
|
|
1393
|
-
};
|
|
1394
|
-
}
|
|
1395
|
-
function hostAllowListMiddleware(options) {
|
|
1396
|
-
const isAllowed = createHostAllowList(options);
|
|
1397
|
-
return async (c, next) => {
|
|
1398
|
-
const host = c.req.header("host");
|
|
1399
|
-
if (!isAllowed(host)) return c.text(`Forbidden: Host "${host ?? ""}" is not allowed. To serve the Inspector under this name, add it to ${ALLOWED_HOSTS_ENV} (or to ALLOWED_ORIGINS).`, 403);
|
|
1400
|
-
await next();
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
//#endregion
|
|
1404
|
-
//#region server/web-server-config.ts
|
|
1420
|
+
// server/web-server-config.ts
|
|
1405
1421
|
const inspectorVersion = readInspectorVersionSafe(import.meta.url);
|
|
1406
1422
|
function defaultEnvironmentFromProcess(extra) {
|
|
1407
1423
|
const keys = process.platform === "win32" ? [
|
|
@@ -1522,6 +1538,10 @@ function defaultAllowedOrigins(hostname, port, scheme = "http") {
|
|
|
1522
1538
|
];
|
|
1523
1539
|
return [webOrigin(scheme, h, port)];
|
|
1524
1540
|
}
|
|
1541
|
+
function weakAuthTokenWarning(token) {
|
|
1542
|
+
if (token === "" || token.length >= 32) return null;
|
|
1543
|
+
return `Warning: the custom API token (${API_SERVER_ENV_VARS.AUTH_TOKEN}) is ${token.length} character${token.length === 1 ? "" : "s"} long; use at least 32 random characters (e.g. \`openssl rand -hex 32\`), or unset it to have one generated.`;
|
|
1544
|
+
}
|
|
1525
1545
|
function buildWebServerConfig(options = {}) {
|
|
1526
1546
|
const { initialMcpConfig = null, mcpConfigPath, writable = true, initialServers = null } = options;
|
|
1527
1547
|
const rawPort = process.env.CLIENT_PORT?.trim() || "6274";
|
|
@@ -1531,6 +1551,8 @@ function buildWebServerConfig(options = {}) {
|
|
|
1531
1551
|
const tls = resolveTlsConfig();
|
|
1532
1552
|
const dangerouslyOmitAuth = isEnvFlagEnabled(process.env.DANGEROUSLY_OMIT_AUTH);
|
|
1533
1553
|
const authToken = dangerouslyOmitAuth ? "" : process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] ?? process.env["MCP_PROXY_AUTH_TOKEN"] ?? "";
|
|
1554
|
+
const weakToken = weakAuthTokenWarning(authToken);
|
|
1555
|
+
if (weakToken) console.warn(weakToken);
|
|
1534
1556
|
const sandboxPort = resolveSandboxPort();
|
|
1535
1557
|
const requestedAppOriginPort = resolveAppOriginPort();
|
|
1536
1558
|
const appOriginCollidesWith = requestedAppOriginPort !== 0 && (requestedAppOriginPort === port ? "the web server (CLIENT_PORT)" : requestedAppOriginPort === sandboxPort ? "the MCP Apps sandbox (MCP_SANDBOX_PORT)" : null);
|
|
@@ -1584,8 +1606,7 @@ function resolveAutoOpen() {
|
|
|
1584
1606
|
if (flag === "false") return false;
|
|
1585
1607
|
return !process.env.VITEST;
|
|
1586
1608
|
}
|
|
1587
|
-
|
|
1588
|
-
//#region ../../core/mcp/config.ts
|
|
1609
|
+
// ../../core/mcp/config.ts
|
|
1589
1610
|
function getServerType(config) {
|
|
1590
1611
|
if (!("type" in config) || config.type === void 0) return "stdio";
|
|
1591
1612
|
const type = config.type;
|
|
@@ -1594,8 +1615,7 @@ function getServerType(config) {
|
|
|
1594
1615
|
if (type === "streamable-http") return "streamable-http";
|
|
1595
1616
|
throw new Error(`Invalid server type: ${type}. Valid types are: stdio, sse, streamable-http`);
|
|
1596
1617
|
}
|
|
1597
|
-
|
|
1598
|
-
//#region ../../core/mcp/fetchTracking.ts
|
|
1618
|
+
// ../../core/mcp/fetchTracking.ts
|
|
1599
1619
|
const SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
1600
1620
|
"authorization",
|
|
1601
1621
|
"cookie",
|
|
@@ -1704,14 +1724,15 @@ function isLongLivedStreamResponse(method, contentType) {
|
|
|
1704
1724
|
}
|
|
1705
1725
|
const CHALLENGE_BODY_MAX_BYTES = 65536;
|
|
1706
1726
|
const CHALLENGE_BODY_READ_MS = 2e3;
|
|
1727
|
+
const TRACKED_BODY_MAX_BYTES = 1048576;
|
|
1707
1728
|
const TRUNCATED_BODY_SUFFIX = "\n[truncated]";
|
|
1708
1729
|
const WITHHELD_TRUNCATED_JSON_BODY = "[truncated JSON body withheld: an incomplete body cannot be redacted]";
|
|
1709
|
-
function
|
|
1730
|
+
function recordableBody(read, contentType) {
|
|
1710
1731
|
if (!read.truncated) return redactBody(read.text, contentType);
|
|
1711
1732
|
if ((contentType ?? "").toLowerCase().includes("json") || /^\s*[{[]/.test(read.text)) return WITHHELD_TRUNCATED_JSON_BODY;
|
|
1712
1733
|
return redactBody(read.text, contentType) + TRUNCATED_BODY_SUFFIX;
|
|
1713
1734
|
}
|
|
1714
|
-
async function
|
|
1735
|
+
async function readCappedBody(response, maxBytes, timeoutMs) {
|
|
1715
1736
|
let reader;
|
|
1716
1737
|
try {
|
|
1717
1738
|
const body = response.clone().body;
|
|
@@ -1725,20 +1746,23 @@ async function readChallengeBody(response) {
|
|
|
1725
1746
|
let text = "";
|
|
1726
1747
|
let bytes = 0;
|
|
1727
1748
|
let timer;
|
|
1728
|
-
const deadline = new Promise((resolve) => {
|
|
1729
|
-
timer = setTimeout(() => resolve("timeout"),
|
|
1749
|
+
const deadline = timeoutMs === void 0 ? void 0 : new Promise((resolve) => {
|
|
1750
|
+
timer = setTimeout(() => resolve("timeout"), timeoutMs);
|
|
1730
1751
|
});
|
|
1731
1752
|
try {
|
|
1732
|
-
|
|
1733
|
-
const next = await Promise.race([reader.read(), deadline]);
|
|
1753
|
+
for (;;) {
|
|
1754
|
+
const next = await (deadline ? Promise.race([reader.read(), deadline]) : reader.read());
|
|
1734
1755
|
if (next === "timeout") break;
|
|
1735
1756
|
if (next.done) return {
|
|
1736
1757
|
text: text + decoder.decode(),
|
|
1737
1758
|
truncated: false
|
|
1738
1759
|
};
|
|
1739
|
-
|
|
1760
|
+
if (next.value.byteLength === 0) continue;
|
|
1761
|
+
if (bytes >= maxBytes) break;
|
|
1762
|
+
const chunk = next.value.subarray(0, maxBytes - bytes);
|
|
1740
1763
|
bytes += chunk.byteLength;
|
|
1741
1764
|
text += decoder.decode(chunk, { stream: true });
|
|
1765
|
+
if (chunk.byteLength < next.value.byteLength) break;
|
|
1742
1766
|
}
|
|
1743
1767
|
reader.cancel().catch(() => {});
|
|
1744
1768
|
return {
|
|
@@ -1751,6 +1775,9 @@ async function readChallengeBody(response) {
|
|
|
1751
1775
|
clearTimeout(timer);
|
|
1752
1776
|
}
|
|
1753
1777
|
}
|
|
1778
|
+
function readChallengeBody(response) {
|
|
1779
|
+
return readCappedBody(response, CHALLENGE_BODY_MAX_BYTES, CHALLENGE_BODY_READ_MS);
|
|
1780
|
+
}
|
|
1754
1781
|
function longLivedStreamFraming(contentType) {
|
|
1755
1782
|
return contentType?.toLowerCase().includes("application/x-ndjson") ? "ndjson" : "sse";
|
|
1756
1783
|
}
|
|
@@ -1916,7 +1943,7 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
1916
1943
|
};
|
|
1917
1944
|
if ((responseStatus === 401 || responseStatus === 403) && response.body && !response.bodyUsed) {
|
|
1918
1945
|
const read = await readChallengeBody(response);
|
|
1919
|
-
entry.responseBody = read === void 0 ? void 0 :
|
|
1946
|
+
entry.responseBody = read === void 0 ? void 0 : recordableBody(read, response.headers.get("content-type"));
|
|
1920
1947
|
callbacks.trackRequest?.(entry);
|
|
1921
1948
|
return response;
|
|
1922
1949
|
}
|
|
@@ -1924,11 +1951,12 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
1924
1951
|
if (isLongLivedStream && response.body && !response.bodyUsed) watchLongLivedStream(response, id, longLivedStreamFraming(response.headers.get("content-type")), callbacks);
|
|
1925
1952
|
if (!isLongLivedStream && response.body && !response.bodyUsed) {
|
|
1926
1953
|
const responseContentType = response.headers.get("content-type");
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1954
|
+
readCappedBody(response, TRACKED_BODY_MAX_BYTES).then((read) => {
|
|
1955
|
+
if (read === void 0) return;
|
|
1956
|
+
try {
|
|
1957
|
+
callbacks.updateResponseBody?.(id, recordableBody(read, responseContentType));
|
|
1958
|
+
} catch {}
|
|
1959
|
+
});
|
|
1932
1960
|
}
|
|
1933
1961
|
return response;
|
|
1934
1962
|
};
|
|
@@ -1994,8 +2022,7 @@ function parseAuthChallengeFromResponse(response, context) {
|
|
|
1994
2022
|
}
|
|
1995
2023
|
};
|
|
1996
2024
|
}
|
|
1997
|
-
|
|
1998
|
-
//#region ../../core/mcp/node/authChallengeFetch.ts
|
|
2025
|
+
// ../../core/mcp/node/authChallengeFetch.ts
|
|
1999
2026
|
function createAuthChallengeInterceptFetch(baseFetch) {
|
|
2000
2027
|
return async (input, init) => {
|
|
2001
2028
|
const response = await baseFetch(input, init);
|
|
@@ -2016,8 +2043,7 @@ function createAuthChallengeObserverFetch(baseFetch, onChallenge) {
|
|
|
2016
2043
|
return response;
|
|
2017
2044
|
};
|
|
2018
2045
|
}
|
|
2019
|
-
|
|
2020
|
-
//#region ../../core/mcp/node/proxyFetch.ts
|
|
2046
|
+
// ../../core/mcp/node/proxyFetch.ts
|
|
2021
2047
|
function asUndiciInit(init) {
|
|
2022
2048
|
return init;
|
|
2023
2049
|
}
|
|
@@ -2107,8 +2133,7 @@ function createProxyFetch() {
|
|
|
2107
2133
|
return (await loadedProxiedFetch)(input, init);
|
|
2108
2134
|
};
|
|
2109
2135
|
}
|
|
2110
|
-
|
|
2111
|
-
//#region ../../core/auth/requestTimeout.ts
|
|
2136
|
+
// ../../core/auth/requestTimeout.ts
|
|
2112
2137
|
const OAUTH_TIMEOUT_WIRE_CODE = "oauth_request_timeout";
|
|
2113
2138
|
function readNegotiatesEndpoint(hint) {
|
|
2114
2139
|
if (!hint) return true;
|
|
@@ -2155,11 +2180,23 @@ function callerSignalOf(input, init) {
|
|
|
2155
2180
|
if (explicit !== void 0) return explicit ?? void 0;
|
|
2156
2181
|
if (typeof input !== "string" && !(input instanceof URL)) return input.signal;
|
|
2157
2182
|
}
|
|
2158
|
-
|
|
2159
|
-
//#region ../../core/mcp/node/fetchGuard.ts
|
|
2183
|
+
// ../../core/auth/fetchRefusal.ts
|
|
2160
2184
|
var FetchTargetError = class extends Error {
|
|
2161
2185
|
name = "FetchTargetError";
|
|
2186
|
+
url;
|
|
2187
|
+
constructor(message, url) {
|
|
2188
|
+
super(message);
|
|
2189
|
+
if (url !== void 0) this.url = url;
|
|
2190
|
+
}
|
|
2162
2191
|
};
|
|
2192
|
+
function findFetchTargetError(err) {
|
|
2193
|
+
let current = err;
|
|
2194
|
+
for (let depth = 0; depth < 8 && current instanceof Error; depth++) {
|
|
2195
|
+
if (current instanceof FetchTargetError) return current;
|
|
2196
|
+
current = current.cause;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
const FETCH_TARGET_REFUSED_WIRE_CODE = "fetch_target_refused";
|
|
2163
2200
|
const REDIRECT_STATUSES = /* @__PURE__ */ new Set([
|
|
2164
2201
|
301,
|
|
2165
2202
|
302,
|
|
@@ -2168,11 +2205,12 @@ const REDIRECT_STATUSES = /* @__PURE__ */ new Set([
|
|
|
2168
2205
|
308
|
|
2169
2206
|
]);
|
|
2170
2207
|
const resolveWithSystemDns = async (hostname) => (await lookup(hostname, { all: true })).map((r) => r.address);
|
|
2208
|
+
const METADATA_IPV4_ADDRESSES = /* @__PURE__ */ new Set(["100.100.100.200", "192.0.0.192"]);
|
|
2171
2209
|
function isBlockedAddress(ip) {
|
|
2172
2210
|
const kind = isIP(ip);
|
|
2173
2211
|
if (kind === 4) {
|
|
2174
2212
|
const [a, b] = ip.split(".").map(Number);
|
|
2175
|
-
return a === 169 && b === 254;
|
|
2213
|
+
return a === 169 && b === 254 || METADATA_IPV4_ADDRESSES.has(ip);
|
|
2176
2214
|
}
|
|
2177
2215
|
if (kind !== 6) return false;
|
|
2178
2216
|
const addr = ip.toLowerCase();
|
|
@@ -2335,27 +2373,35 @@ async function guardedFetch(url, init, options = {}) {
|
|
|
2335
2373
|
headers: init.headers
|
|
2336
2374
|
};
|
|
2337
2375
|
for (let hop = 0; hop <= 5; hop++) {
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2376
|
+
let res;
|
|
2377
|
+
try {
|
|
2378
|
+
await assertAllowedTarget(request.url, resolveHost, viaProxy, init.signal);
|
|
2379
|
+
res = await fetchFn(request.url.href, {
|
|
2380
|
+
method: request.method,
|
|
2381
|
+
headers: new Headers(request.headers),
|
|
2382
|
+
body: request.body,
|
|
2383
|
+
signal: init.signal,
|
|
2384
|
+
redirect: "manual"
|
|
2385
|
+
});
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
stampRefusedUrl(err, request.url);
|
|
2388
|
+
throw err;
|
|
2389
|
+
}
|
|
2346
2390
|
const location = res.headers.get("location");
|
|
2347
2391
|
if (!REDIRECT_STATUSES.has(res.status) || location === null) return res;
|
|
2348
2392
|
res.body?.cancel().catch(() => {});
|
|
2349
|
-
|
|
2393
|
+
try {
|
|
2394
|
+
request = nextRedirectRequest(request, res.status, location);
|
|
2395
|
+
} catch (err) {
|
|
2396
|
+
stampRefusedUrl(err, request.url);
|
|
2397
|
+
throw err;
|
|
2398
|
+
}
|
|
2350
2399
|
}
|
|
2351
|
-
throw new FetchTargetError(`Refusing to follow more than 5 redirects
|
|
2400
|
+
throw new FetchTargetError(`Refusing to follow more than 5 redirects`, request.url.href);
|
|
2352
2401
|
}
|
|
2353
|
-
function
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
if (current instanceof FetchTargetError) return current;
|
|
2357
|
-
current = current.cause;
|
|
2358
|
-
}
|
|
2402
|
+
function stampRefusedUrl(err, url) {
|
|
2403
|
+
const refused = findFetchTargetError(err);
|
|
2404
|
+
if (refused && refused.url === void 0) refused.url = url.href;
|
|
2359
2405
|
}
|
|
2360
2406
|
async function bodyText(input, init) {
|
|
2361
2407
|
const body = init?.body !== void 0 ? init.body : input instanceof Request && input.body !== null ? await input.clone().text() : void 0;
|
|
@@ -2382,8 +2428,7 @@ function createGuardedFetch(options = {}) {
|
|
|
2382
2428
|
}
|
|
2383
2429
|
};
|
|
2384
2430
|
}
|
|
2385
|
-
|
|
2386
|
-
//#region ../../core/auth/routeOAuthRequests.ts
|
|
2431
|
+
// ../../core/auth/routeOAuthRequests.ts
|
|
2387
2432
|
function isJsonRpcMessage(value) {
|
|
2388
2433
|
return typeof value === "object" && value !== null && value.jsonrpc === "2.0";
|
|
2389
2434
|
}
|
|
@@ -2407,8 +2452,7 @@ function isMcpShapedRequest(input, init) {
|
|
|
2407
2452
|
function routeOAuthRequests(mcpFetch, oauthFetch, isMcpEndpoint) {
|
|
2408
2453
|
return (input, init) => isMcpShapedRequest(input, init) && isMcpEndpoint(requestUrlOf(input), requestHeadersOf(input, init)) ? mcpFetch(input, init) : oauthFetch(input, init);
|
|
2409
2454
|
}
|
|
2410
|
-
|
|
2411
|
-
//#region ../../core/mcp/node/notificationHeadersFetch.ts
|
|
2455
|
+
// ../../core/mcp/node/notificationHeadersFetch.ts
|
|
2412
2456
|
function createNotificationHeadersFetch(baseFetch) {
|
|
2413
2457
|
return (input, init) => {
|
|
2414
2458
|
const method = modernNotificationMethod(init);
|
|
@@ -2444,8 +2488,7 @@ function modernNotificationMethod(init) {
|
|
|
2444
2488
|
function isRecord$2(value) {
|
|
2445
2489
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2446
2490
|
}
|
|
2447
|
-
|
|
2448
|
-
//#region ../../core/mcp/node/suppressNotificationStreamFetch.ts
|
|
2491
|
+
// ../../core/mcp/node/suppressNotificationStreamFetch.ts
|
|
2449
2492
|
function createSuppressNotificationStreamFetch(baseFetch, endpoint) {
|
|
2450
2493
|
return async (input, init) => {
|
|
2451
2494
|
if (isStandaloneStreamRequest(input, init, endpoint)) return new Response(null, {
|
|
@@ -2471,8 +2514,7 @@ function isStandaloneStreamRequest(input, init, endpoint) {
|
|
|
2471
2514
|
const headers = new Headers(init?.headers ?? request?.headers);
|
|
2472
2515
|
return (headers.get("accept")?.toLowerCase() ?? "").includes("text/event-stream") && !headers.has("last-event-id");
|
|
2473
2516
|
}
|
|
2474
|
-
|
|
2475
|
-
//#region ../../core/mcp/node/transport.ts
|
|
2517
|
+
// ../../core/mcp/node/transport.ts
|
|
2476
2518
|
function headersFromSettings(settings) {
|
|
2477
2519
|
if (!settings || settings.headers.length === 0) return void 0;
|
|
2478
2520
|
const out = {};
|
|
@@ -2554,8 +2596,7 @@ function createTransportNode(config, options = {}) {
|
|
|
2554
2596
|
}) };
|
|
2555
2597
|
}
|
|
2556
2598
|
}
|
|
2557
|
-
|
|
2558
|
-
//#region ../../core/mcp/remote/node/cappedBody.ts
|
|
2599
|
+
// ../../core/mcp/remote/node/cappedBody.ts
|
|
2559
2600
|
async function readCappedText(body, maxBytes) {
|
|
2560
2601
|
const reader = body.getReader();
|
|
2561
2602
|
const decoder = new TextDecoder();
|
|
@@ -2624,6 +2665,24 @@ var RateLimitLedger = class {
|
|
|
2624
2665
|
function isRateLimitError(err) {
|
|
2625
2666
|
return SdkHttpError.isInstance(err) && err.status === 429;
|
|
2626
2667
|
}
|
|
2668
|
+
// ../../core/mcp/sessionTermination.ts
|
|
2669
|
+
function canTerminateSession(transport) {
|
|
2670
|
+
return typeof transport === "object" && transport !== null && "terminateSession" in transport && typeof transport.terminateSession === "function";
|
|
2671
|
+
}
|
|
2672
|
+
const SESSION_TERMINATE_TIMEOUT_MS = 3e3;
|
|
2673
|
+
async function terminateSessionBestEffort(transport, timeoutMs = SESSION_TERMINATE_TIMEOUT_MS) {
|
|
2674
|
+
if (!canTerminateSession(transport) || !transport.sessionId) return false;
|
|
2675
|
+
let timer;
|
|
2676
|
+
const timedOut = new Promise((resolve) => {
|
|
2677
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
2678
|
+
});
|
|
2679
|
+
const terminated = new Promise((resolve) => resolve(transport.terminateSession())).then(() => true, () => false);
|
|
2680
|
+
try {
|
|
2681
|
+
return await Promise.race([terminated, timedOut]);
|
|
2682
|
+
} finally {
|
|
2683
|
+
clearTimeout(timer);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2627
2686
|
function mergeOAuthSections(disk, snapshot, sections) {
|
|
2628
2687
|
const merged = {
|
|
2629
2688
|
servers: { ...disk?.servers },
|
|
@@ -2740,8 +2799,7 @@ function parseOAuthStoreWriteBody(body) {
|
|
|
2740
2799
|
if (!snapshot || !snapshotHasValidSecretPayloads(snapshot)) return null;
|
|
2741
2800
|
return { snapshot };
|
|
2742
2801
|
}
|
|
2743
|
-
|
|
2744
|
-
//#region ../../core/auth/node/secret-store.ts
|
|
2802
|
+
// ../../core/auth/node/secret-store.ts
|
|
2745
2803
|
const SERVICE_NAME = "mcp-inspector";
|
|
2746
2804
|
let keyringLoad;
|
|
2747
2805
|
var KeyringModuleShapeError = class extends Error {
|
|
@@ -2961,8 +3019,7 @@ var SessionSecretStore = class extends InMemorySecretStore {
|
|
|
2961
3019
|
return false;
|
|
2962
3020
|
}
|
|
2963
3021
|
};
|
|
2964
|
-
|
|
2965
|
-
//#region ../../core/auth/node/file-lock.ts
|
|
3022
|
+
// ../../core/auth/node/file-lock.ts
|
|
2966
3023
|
const STALE_MS = 1e4;
|
|
2967
3024
|
const RETRY = {
|
|
2968
3025
|
retries: 20,
|
|
@@ -3094,8 +3151,7 @@ async function withSecretFileLock(filePath, fn) {
|
|
|
3094
3151
|
await release();
|
|
3095
3152
|
}
|
|
3096
3153
|
}
|
|
3097
|
-
|
|
3098
|
-
//#region ../../core/auth/node/file-secret-store.ts
|
|
3154
|
+
// ../../core/auth/node/file-secret-store.ts
|
|
3099
3155
|
const scrypt = (passphrase, salt, keylen, options) => new Promise((resolve, reject) => {
|
|
3100
3156
|
crypto$1.scrypt(passphrase, salt, keylen, options, (err, key) => {
|
|
3101
3157
|
/* v8 ignore next -- @preserve: node validates the cost parameters
|
|
@@ -3503,8 +3559,7 @@ async function readSecretFilePermissions(filePath) {
|
|
|
3503
3559
|
};
|
|
3504
3560
|
}
|
|
3505
3561
|
}
|
|
3506
|
-
|
|
3507
|
-
//#region ../../core/auth/node/secret-store-selection.ts
|
|
3562
|
+
// ../../core/auth/node/secret-store-selection.ts
|
|
3508
3563
|
const SECRET_STORE_ENV = "MCP_INSPECTOR_SECRET_STORE";
|
|
3509
3564
|
const SECRET_FILE_ENV = "MCP_INSPECTOR_SECRET_FILE";
|
|
3510
3565
|
const STORAGE_DIR_ENV = "MCP_STORAGE_DIR";
|
|
@@ -3816,8 +3871,7 @@ function defaultSecretStore() {
|
|
|
3816
3871
|
async function resolveConcreteSecretStore(store) {
|
|
3817
3872
|
return store instanceof DeferredSecretStore ? (await resolveSecretStore()).store : store;
|
|
3818
3873
|
}
|
|
3819
|
-
|
|
3820
|
-
//#region ../../core/auth/node/oauth-secrets.ts
|
|
3874
|
+
// ../../core/auth/node/oauth-secrets.ts
|
|
3821
3875
|
const PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
3822
3876
|
const warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
3823
3877
|
function getPersistTokensPolicy(env = process.env) {
|
|
@@ -4087,8 +4141,7 @@ function snapshotHasPlaintextSecrets(snapshot) {
|
|
|
4087
4141
|
for (const session of Object.values(snapshot.idpSessions)) if (session.idToken !== void 0 || session.refreshToken !== void 0) return true;
|
|
4088
4142
|
return false;
|
|
4089
4143
|
}
|
|
4090
|
-
|
|
4091
|
-
//#region ../../core/auth/node/oauth-namespace-ledger.ts
|
|
4144
|
+
// ../../core/auth/node/oauth-namespace-ledger.ts
|
|
4092
4145
|
const namespaceLedgerPath = (stateFilePath) => `${stateFilePath}.namespaces.json`;
|
|
4093
4146
|
const KEYRING_LOCATION = "keyring";
|
|
4094
4147
|
const FILE_LOCATION_PREFIX = "file:";
|
|
@@ -4346,8 +4399,7 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current, ow
|
|
|
4346
4399
|
warnLedgerFailure("update", error);
|
|
4347
4400
|
}
|
|
4348
4401
|
}
|
|
4349
|
-
|
|
4350
|
-
//#region ../../core/auth/node/oauth-persist-file.ts
|
|
4402
|
+
// ../../core/auth/node/oauth-persist-file.ts
|
|
4351
4403
|
const warnedStoreFailures = /* @__PURE__ */ new Set();
|
|
4352
4404
|
function warnStoreWriteFailure(error) {
|
|
4353
4405
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -4879,8 +4931,7 @@ async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
4879
4931
|
if (locked) await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
4880
4932
|
});
|
|
4881
4933
|
}
|
|
4882
|
-
|
|
4883
|
-
//#region ../../core/mcp/import/clientConfig.ts
|
|
4934
|
+
// ../../core/mcp/import/clientConfig.ts
|
|
4884
4935
|
function parseJsonObject(raw) {
|
|
4885
4936
|
let data;
|
|
4886
4937
|
try {
|
|
@@ -4910,8 +4961,7 @@ function parseVsCodeConfig(raw) {
|
|
|
4910
4961
|
if (!servers || typeof servers !== "object" || Array.isArray(servers)) throw new Error("No 'servers' object found in VS Code config file");
|
|
4911
4962
|
return { mcpServers: normalizeServerMap(servers) };
|
|
4912
4963
|
}
|
|
4913
|
-
|
|
4914
|
-
//#region ../../core/mcp/import/strategies.ts
|
|
4964
|
+
// ../../core/mcp/import/strategies.ts
|
|
4915
4965
|
function join$1(...segments) {
|
|
4916
4966
|
return segments.map((s, i) => i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")).filter((s) => s.length > 0).join("/");
|
|
4917
4967
|
}
|
|
@@ -4952,8 +5002,7 @@ const IMPORT_STRATEGIES = Object.fromEntries([
|
|
|
4952
5002
|
function getImportStrategy(id) {
|
|
4953
5003
|
return IMPORT_STRATEGIES[id];
|
|
4954
5004
|
}
|
|
4955
|
-
|
|
4956
|
-
//#region ../../core/mcp/import/resolveSource.ts
|
|
5005
|
+
// ../../core/mcp/import/resolveSource.ts
|
|
4957
5006
|
function resolveImportSource(type, platform, homeDir, readFile) {
|
|
4958
5007
|
const strategy = getImportStrategy(type);
|
|
4959
5008
|
if (!strategy) return null;
|
|
@@ -4996,8 +5045,7 @@ function resolveImportSource(type, platform, homeDir, readFile) {
|
|
|
4996
5045
|
searched
|
|
4997
5046
|
};
|
|
4998
5047
|
}
|
|
4999
|
-
|
|
5000
|
-
//#region ../../core/mcp/remote/progressToken.ts
|
|
5048
|
+
// ../../core/mcp/remote/progressToken.ts
|
|
5001
5049
|
function progressTokenOf(message) {
|
|
5002
5050
|
if ("method" in message && message.method === "notifications/progress" && "params" in message && typeof message.params === "object" && message.params !== null) {
|
|
5003
5051
|
const parsed = ProgressTokenSchema.safeParse(message.params.progressToken);
|
|
@@ -5010,12 +5058,20 @@ function waitForProgressToken(waits, token) {
|
|
|
5010
5058
|
const numeric = Number(token);
|
|
5011
5059
|
return Number.isSafeInteger(numeric) ? waits.get(numeric) : void 0;
|
|
5012
5060
|
}
|
|
5013
|
-
|
|
5014
|
-
//#region ../../core/mcp/remote/node/remote-session.ts
|
|
5061
|
+
// ../../core/mcp/remote/node/remote-session.ts
|
|
5015
5062
|
var RemoteSession = class RemoteSession {
|
|
5063
|
+
static DEFAULT_DETACH_GRACE_MS = 6e4;
|
|
5064
|
+
static MAX_QUEUED_EVENTS = 1e3;
|
|
5016
5065
|
sessionId;
|
|
5066
|
+
serverName;
|
|
5067
|
+
transportType;
|
|
5068
|
+
createdAt;
|
|
5069
|
+
lastConsumerSeenAt = null;
|
|
5017
5070
|
transport;
|
|
5071
|
+
terminated = new AbortController();
|
|
5018
5072
|
eventQueue = [];
|
|
5073
|
+
droppedEventCount = 0;
|
|
5074
|
+
detachTimer;
|
|
5019
5075
|
eventConsumer = null;
|
|
5020
5076
|
transportDead = false;
|
|
5021
5077
|
transportError = null;
|
|
@@ -5027,8 +5083,31 @@ var RemoteSession = class RemoteSession {
|
|
|
5027
5083
|
appliedProtocolVersion;
|
|
5028
5084
|
rateLimits = new RateLimitLedger();
|
|
5029
5085
|
static PROTOCOL_VERSION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
5030
|
-
constructor(sessionId) {
|
|
5086
|
+
constructor(sessionId, descriptor = {}) {
|
|
5031
5087
|
this.sessionId = sessionId;
|
|
5088
|
+
this.serverName = descriptor.serverName;
|
|
5089
|
+
this.transportType = descriptor.transport ?? "stdio";
|
|
5090
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
5091
|
+
}
|
|
5092
|
+
get terminatedSignal() {
|
|
5093
|
+
return this.terminated.signal;
|
|
5094
|
+
}
|
|
5095
|
+
terminate() {
|
|
5096
|
+
this.terminated.abort();
|
|
5097
|
+
}
|
|
5098
|
+
describe() {
|
|
5099
|
+
const attached = this.eventConsumer !== null;
|
|
5100
|
+
const lastSeen = attached ? /* @__PURE__ */ new Date() : this.lastConsumerSeenAt;
|
|
5101
|
+
return {
|
|
5102
|
+
sessionId: this.sessionId,
|
|
5103
|
+
...this.serverName !== void 0 && { serverName: this.serverName },
|
|
5104
|
+
transport: this.transportType,
|
|
5105
|
+
createdAt: this.createdAt.toISOString(),
|
|
5106
|
+
consumerAttached: attached,
|
|
5107
|
+
lastConsumerSeenAt: lastSeen ? lastSeen.toISOString() : null,
|
|
5108
|
+
queuedEvents: this.eventQueue.length,
|
|
5109
|
+
transportDead: this.transportDead
|
|
5110
|
+
};
|
|
5032
5111
|
}
|
|
5033
5112
|
setAuthProviderHandle(handle) {
|
|
5034
5113
|
this.authProviderHandle = handle;
|
|
@@ -5046,13 +5125,50 @@ var RemoteSession = class RemoteSession {
|
|
|
5046
5125
|
this.transport.setProtocolVersion?.(version);
|
|
5047
5126
|
}
|
|
5048
5127
|
setEventConsumer(consumer) {
|
|
5128
|
+
this.cancelDetachGrace();
|
|
5049
5129
|
this.eventConsumer = consumer;
|
|
5130
|
+
this.lastConsumerSeenAt = /* @__PURE__ */ new Date();
|
|
5131
|
+
if (this.droppedEventCount > 0) {
|
|
5132
|
+
const dropped = this.droppedEventCount;
|
|
5133
|
+
this.droppedEventCount = 0;
|
|
5134
|
+
consumer({
|
|
5135
|
+
type: "stdio_log",
|
|
5136
|
+
data: {
|
|
5137
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5138
|
+
message: `[inspector] ${dropped} event(s) were dropped while no client was attached (the queue holds at most ${RemoteSession.MAX_QUEUED_EVENTS})`
|
|
5139
|
+
}
|
|
5140
|
+
});
|
|
5141
|
+
}
|
|
5050
5142
|
while (this.eventQueue.length > 0) consumer(this.eventQueue.shift());
|
|
5051
5143
|
}
|
|
5052
5144
|
clearEventConsumer() {
|
|
5145
|
+
if (this.eventConsumer !== null) this.lastConsumerSeenAt = /* @__PURE__ */ new Date();
|
|
5053
5146
|
this.eventConsumer = null;
|
|
5054
5147
|
return this.transportDead;
|
|
5055
5148
|
}
|
|
5149
|
+
detachEventConsumer(consumer) {
|
|
5150
|
+
if (this.eventConsumer !== consumer) return false;
|
|
5151
|
+
this.eventConsumer = null;
|
|
5152
|
+
this.lastConsumerSeenAt = /* @__PURE__ */ new Date();
|
|
5153
|
+
return true;
|
|
5154
|
+
}
|
|
5155
|
+
armDetachGrace(graceMs, onExpire) {
|
|
5156
|
+
this.cancelDetachGrace();
|
|
5157
|
+
this.detachTimer = setTimeout(() => {
|
|
5158
|
+
this.detachTimer = void 0;
|
|
5159
|
+
onExpire();
|
|
5160
|
+
}, graceMs);
|
|
5161
|
+
this.detachTimer.unref();
|
|
5162
|
+
}
|
|
5163
|
+
cancelDetachGrace() {
|
|
5164
|
+
if (this.detachTimer !== void 0) {
|
|
5165
|
+
clearTimeout(this.detachTimer);
|
|
5166
|
+
this.detachTimer = void 0;
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
hasPendingDetachGrace() {
|
|
5170
|
+
return this.detachTimer !== void 0;
|
|
5171
|
+
}
|
|
5056
5172
|
markTransportDead(error) {
|
|
5057
5173
|
this.transportDead = true;
|
|
5058
5174
|
this.transportError = error;
|
|
@@ -5163,7 +5279,13 @@ var RemoteSession = class RemoteSession {
|
|
|
5163
5279
|
}
|
|
5164
5280
|
pushEvent(event) {
|
|
5165
5281
|
if (this.eventConsumer) this.eventConsumer(event);
|
|
5166
|
-
else
|
|
5282
|
+
else {
|
|
5283
|
+
if (this.eventQueue.length >= RemoteSession.MAX_QUEUED_EVENTS) {
|
|
5284
|
+
this.eventQueue.shift();
|
|
5285
|
+
this.droppedEventCount++;
|
|
5286
|
+
}
|
|
5287
|
+
this.eventQueue.push(event);
|
|
5288
|
+
}
|
|
5167
5289
|
}
|
|
5168
5290
|
onMessage(message) {
|
|
5169
5291
|
if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) this.settleRequestWait(message.id);
|
|
@@ -5216,8 +5338,7 @@ var RemoteSession = class RemoteSession {
|
|
|
5216
5338
|
});
|
|
5217
5339
|
}
|
|
5218
5340
|
};
|
|
5219
|
-
|
|
5220
|
-
//#region ../../core/mcp/remote/node/tokenAuthProvider.ts
|
|
5341
|
+
// ../../core/mcp/remote/node/tokenAuthProvider.ts
|
|
5221
5342
|
const REMOTE_OAUTH_STUB_METADATA = {
|
|
5222
5343
|
redirect_uris: [],
|
|
5223
5344
|
scope: ""
|
|
@@ -5282,8 +5403,7 @@ function createRemoteAuthProvider(initialAuthState) {
|
|
|
5282
5403
|
}
|
|
5283
5404
|
};
|
|
5284
5405
|
}
|
|
5285
|
-
|
|
5286
|
-
//#region ../../core/client/config-parse.ts
|
|
5406
|
+
// ../../core/client/config-parse.ts
|
|
5287
5407
|
function isAbsoluteHttpUrl(value) {
|
|
5288
5408
|
let url;
|
|
5289
5409
|
try {
|
|
@@ -5375,8 +5495,7 @@ function formatClientConfigLoadError(error) {
|
|
|
5375
5495
|
if (error instanceof Error) return error.message;
|
|
5376
5496
|
return String(error);
|
|
5377
5497
|
}
|
|
5378
|
-
|
|
5379
|
-
//#region ../../core/client/secrets.ts
|
|
5498
|
+
// ../../core/client/secrets.ts
|
|
5380
5499
|
const CLIENT_KEYCHAIN_ID = "client";
|
|
5381
5500
|
function hasClientPlaintextSecret(config) {
|
|
5382
5501
|
const secret = config.enterpriseManagedAuth?.idp?.clientSecret;
|
|
@@ -5417,8 +5536,7 @@ function mergeSecretsIntoClientConfig(config, secrets) {
|
|
|
5417
5536
|
}
|
|
5418
5537
|
};
|
|
5419
5538
|
}
|
|
5420
|
-
|
|
5421
|
-
//#region ../../core/client/node-persistence.ts
|
|
5539
|
+
// ../../core/client/node-persistence.ts
|
|
5422
5540
|
async function withClientConfigLock(filePath, action, body) {
|
|
5423
5541
|
let entered = false;
|
|
5424
5542
|
try {
|
|
@@ -5510,25 +5628,44 @@ async function deleteClientConfigStore(filePath, secretStore) {
|
|
|
5510
5628
|
}
|
|
5511
5629
|
});
|
|
5512
5630
|
}
|
|
5513
|
-
|
|
5514
|
-
//#region ../../core/mcp/remote/node/server.ts
|
|
5631
|
+
// ../../core/mcp/remote/node/server.ts
|
|
5515
5632
|
const SSE_PRIMING_COMMENT = ":\n\n";
|
|
5516
5633
|
function closeAbortedStream(stream) {
|
|
5517
5634
|
stream.close().catch(() => {});
|
|
5518
5635
|
}
|
|
5519
|
-
async function primeAndHoldSseStream(stream, cleanup) {
|
|
5636
|
+
async function primeAndHoldSseStream(stream, cleanup, end) {
|
|
5520
5637
|
let done = false;
|
|
5521
|
-
const
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5638
|
+
const finish = () => {
|
|
5639
|
+
if (done) return false;
|
|
5640
|
+
done = true;
|
|
5641
|
+
cleanup();
|
|
5642
|
+
return true;
|
|
5643
|
+
};
|
|
5644
|
+
const released = new Promise((resolve) => {
|
|
5645
|
+
let closed = false;
|
|
5646
|
+
const release = () => {
|
|
5647
|
+
if (closed) return;
|
|
5648
|
+
closed = true;
|
|
5526
5649
|
closeAbortedStream(stream);
|
|
5527
5650
|
resolve();
|
|
5651
|
+
};
|
|
5652
|
+
let detachEnd = () => {};
|
|
5653
|
+
stream.onAbort(() => {
|
|
5654
|
+
if (finish()) detachEnd();
|
|
5655
|
+
release();
|
|
5528
5656
|
});
|
|
5657
|
+
if (!end) return;
|
|
5658
|
+
const { signal, flush } = end;
|
|
5659
|
+
const onEnd = () => {
|
|
5660
|
+
if (!finish()) return;
|
|
5661
|
+
flush().catch(() => {}).then(release);
|
|
5662
|
+
};
|
|
5663
|
+
detachEnd = () => signal.removeEventListener("abort", onEnd);
|
|
5664
|
+
if (signal.aborted) onEnd();
|
|
5665
|
+
else signal.addEventListener("abort", onEnd, { once: true });
|
|
5529
5666
|
});
|
|
5530
5667
|
await stream.write(SSE_PRIMING_COMMENT);
|
|
5531
|
-
await
|
|
5668
|
+
await released;
|
|
5532
5669
|
}
|
|
5533
5670
|
const MAX_APP_DOCUMENT_CHARS = 8388608;
|
|
5534
5671
|
const MAX_APP_DOCUMENT_BODY_BYTES = 25165824;
|
|
@@ -5635,11 +5772,27 @@ function mcpParamHeadersOnly(headers) {
|
|
|
5635
5772
|
for (const [key, value] of Object.entries(headers)) if (typeof value === "string" && key.toLowerCase().startsWith("Mcp-Param-".toLowerCase())) out[key] = value;
|
|
5636
5773
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
5637
5774
|
}
|
|
5775
|
+
const SESSION_TERMINATED_MESSAGE = "Session terminated from the Inspector's active session list";
|
|
5776
|
+
const MAX_SESSION_SERVER_NAME_CHARS = 256;
|
|
5777
|
+
function sessionServerName(value) {
|
|
5778
|
+
if (typeof value !== "string") return void 0;
|
|
5779
|
+
const name = value.trim();
|
|
5780
|
+
return name === "" ? void 0 : name.slice(0, MAX_SESSION_SERVER_NAME_CHARS);
|
|
5781
|
+
}
|
|
5638
5782
|
function createRemoteApp(options) {
|
|
5639
5783
|
const dangerouslyOmitAuth = !!options.dangerouslyOmitAuth;
|
|
5640
5784
|
const authToken = dangerouslyOmitAuth ? "" : options.authToken || process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] || randomBytes(32).toString("hex");
|
|
5641
5785
|
const app = new Hono();
|
|
5642
5786
|
const sessions = /* @__PURE__ */ new Map();
|
|
5787
|
+
const sessionDetachGraceMs = options.sessionDetachGraceMs ?? RemoteSession.DEFAULT_DETACH_GRACE_MS;
|
|
5788
|
+
const armSessionReap = (session) => {
|
|
5789
|
+
session.armDetachGrace(sessionDetachGraceMs, () => {
|
|
5790
|
+
sessions.delete(session.sessionId);
|
|
5791
|
+
session.rejectActiveRequestWaits(/* @__PURE__ */ new Error("Session closed: no client attached"));
|
|
5792
|
+
const transport = session.transport;
|
|
5793
|
+
terminateSessionBestEffort(transport).then(() => transport.close().catch(() => {}));
|
|
5794
|
+
});
|
|
5795
|
+
};
|
|
5643
5796
|
const { logger: fileLogger, allowedOrigins } = options;
|
|
5644
5797
|
const storageDir = options.storageDir ?? getDefaultStorageDir();
|
|
5645
5798
|
const mcpConfigPath = options.mcpConfigPath ?? getDefaultMcpConfigPath();
|
|
@@ -5753,7 +5906,10 @@ function createRemoteApp(options) {
|
|
|
5753
5906
|
const config = body.config;
|
|
5754
5907
|
if (!config) return c.json({ error: "Missing config" }, 400);
|
|
5755
5908
|
const sessionId = crypto.randomUUID();
|
|
5756
|
-
const session = new RemoteSession(sessionId
|
|
5909
|
+
const session = new RemoteSession(sessionId, {
|
|
5910
|
+
serverName: sessionServerName(body.serverName),
|
|
5911
|
+
transport: config.type ?? "stdio"
|
|
5912
|
+
});
|
|
5757
5913
|
let transport;
|
|
5758
5914
|
let authHandle;
|
|
5759
5915
|
try {
|
|
@@ -5826,6 +5982,7 @@ function createRemoteApp(options) {
|
|
|
5826
5982
|
return c.json({ error: `Failed to start transport: ${msg}` }, is401 ? 401 : 500);
|
|
5827
5983
|
}
|
|
5828
5984
|
sessions.set(sessionId, session);
|
|
5985
|
+
armSessionReap(session);
|
|
5829
5986
|
return c.json({ sessionId });
|
|
5830
5987
|
});
|
|
5831
5988
|
app.post("/api/mcp/send", async (c) => {
|
|
@@ -5916,13 +6073,15 @@ function createRemoteApp(options) {
|
|
|
5916
6073
|
const session = sessions.get(sessionId);
|
|
5917
6074
|
if (!session) return c.json({ error: "Session not found" }, 404);
|
|
5918
6075
|
return streamSSE(c, async (stream) => {
|
|
5919
|
-
|
|
6076
|
+
let lastWrite = Promise.resolve();
|
|
6077
|
+
const consumer = (event) => {
|
|
5920
6078
|
const data = JSON.stringify(event);
|
|
5921
|
-
stream.writeSSE({
|
|
6079
|
+
lastWrite = stream.writeSSE({
|
|
5922
6080
|
event: event.type,
|
|
5923
6081
|
data
|
|
5924
|
-
});
|
|
5925
|
-
}
|
|
6082
|
+
}).catch(() => {});
|
|
6083
|
+
};
|
|
6084
|
+
session.setEventConsumer(consumer);
|
|
5926
6085
|
if (session.isTransportDead()) {
|
|
5927
6086
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5928
6087
|
session.clearEventConsumer();
|
|
@@ -5930,7 +6089,15 @@ function createRemoteApp(options) {
|
|
|
5930
6089
|
return;
|
|
5931
6090
|
}
|
|
5932
6091
|
await primeAndHoldSseStream(stream, () => {
|
|
5933
|
-
if (session.
|
|
6092
|
+
if (!session.detachEventConsumer(consumer)) return;
|
|
6093
|
+
if (session.isTransportDead()) {
|
|
6094
|
+
sessions.delete(sessionId);
|
|
6095
|
+
return;
|
|
6096
|
+
}
|
|
6097
|
+
armSessionReap(session);
|
|
6098
|
+
}, {
|
|
6099
|
+
signal: session.terminatedSignal,
|
|
6100
|
+
flush: () => lastWrite
|
|
5934
6101
|
});
|
|
5935
6102
|
});
|
|
5936
6103
|
});
|
|
@@ -5945,12 +6112,30 @@ function createRemoteApp(options) {
|
|
|
5945
6112
|
if (!sessionId) return c.json({ error: "Missing sessionId" }, 400);
|
|
5946
6113
|
const session = sessions.get(sessionId);
|
|
5947
6114
|
if (session) {
|
|
6115
|
+
session.cancelDetachGrace();
|
|
5948
6116
|
session.clearEventConsumer();
|
|
6117
|
+
await terminateSessionBestEffort(session.transport);
|
|
5949
6118
|
await session.transport.close();
|
|
5950
6119
|
sessions.delete(sessionId);
|
|
5951
6120
|
}
|
|
5952
6121
|
return c.json({ ok: true });
|
|
5953
6122
|
});
|
|
6123
|
+
app.get("/api/mcp/sessions", (c) => {
|
|
6124
|
+
const list = { sessions: [...sessions.values()].map((session) => session.describe()).sort((a, b) => a.createdAt.localeCompare(b.createdAt)) };
|
|
6125
|
+
return c.json(list);
|
|
6126
|
+
});
|
|
6127
|
+
app.delete("/api/mcp/sessions/:sessionId", async (c) => {
|
|
6128
|
+
const sessionId = c.req.param("sessionId");
|
|
6129
|
+
const session = sessions.get(sessionId);
|
|
6130
|
+
if (!session) return c.json({ error: "Session not found" }, 404);
|
|
6131
|
+
sessions.delete(sessionId);
|
|
6132
|
+
session.cancelDetachGrace();
|
|
6133
|
+
session.markTransportDead(SESSION_TERMINATED_MESSAGE);
|
|
6134
|
+
session.terminate();
|
|
6135
|
+
session.rejectActiveRequestWaits(/* @__PURE__ */ new Error(SESSION_TERMINATED_MESSAGE));
|
|
6136
|
+
await session.transport.close().catch(() => {});
|
|
6137
|
+
return c.json({ ok: true });
|
|
6138
|
+
});
|
|
5954
6139
|
app.post("/api/mcp/auth-state", async (c) => {
|
|
5955
6140
|
let body;
|
|
5956
6141
|
try {
|
|
@@ -6042,7 +6227,14 @@ function createRemoteApp(options) {
|
|
|
6042
6227
|
timeoutMs: deadlineMs
|
|
6043
6228
|
}, 504);
|
|
6044
6229
|
const refused = findFetchTargetError(err);
|
|
6045
|
-
if (refused)
|
|
6230
|
+
if (refused) {
|
|
6231
|
+
const body = {
|
|
6232
|
+
error: refused.message,
|
|
6233
|
+
code: FETCH_TARGET_REFUSED_WIRE_CODE,
|
|
6234
|
+
url: redactUrlQuery(refused.url ?? url)
|
|
6235
|
+
};
|
|
6236
|
+
return c.json(body, 403);
|
|
6237
|
+
}
|
|
6046
6238
|
return c.json({ error: msg }, 500);
|
|
6047
6239
|
} finally {
|
|
6048
6240
|
if (timer !== void 0) clearTimeout(timer);
|
|
@@ -6608,8 +6800,9 @@ function createRemoteApp(options) {
|
|
|
6608
6800
|
const settled = await withWriteLock(async () => {
|
|
6609
6801
|
const rawInside = await readStoreFile(mcpConfigPath);
|
|
6610
6802
|
if (rawInside === null) {
|
|
6611
|
-
|
|
6612
|
-
|
|
6803
|
+
const seed = defaultSeedConfig(tmpdir());
|
|
6804
|
+
await writeMcpAndTrackMtime(serializeStore(seed));
|
|
6805
|
+
return seed;
|
|
6613
6806
|
}
|
|
6614
6807
|
const parsedInside = parseStore(rawInside);
|
|
6615
6808
|
const inside = { mcpServers: normalizeMcpServers(parsedInside?.mcpServers) };
|
|
@@ -6887,8 +7080,7 @@ function createRemoteApp(options) {
|
|
|
6887
7080
|
}
|
|
6888
7081
|
};
|
|
6889
7082
|
}
|
|
6890
|
-
|
|
6891
|
-
//#region server/inject-auth-token.ts
|
|
7083
|
+
// server/inject-auth-token.ts
|
|
6892
7084
|
function serializeTokenForScript(token) {
|
|
6893
7085
|
return JSON.stringify(token).replace(/</g, "\\u003c");
|
|
6894
7086
|
}
|
|
@@ -6901,8 +7093,7 @@ function injectAuthToken(html, token) {
|
|
|
6901
7093
|
if (bodyClose !== -1) return html.slice(0, bodyClose) + script + html.slice(bodyClose);
|
|
6902
7094
|
return script + html;
|
|
6903
7095
|
}
|
|
6904
|
-
|
|
6905
|
-
//#region server/health.ts
|
|
7096
|
+
// server/health.ts
|
|
6906
7097
|
const HEALTH_PATH = "/healthz";
|
|
6907
7098
|
const HEALTH_BODY = Object.freeze({ status: "ok" });
|
|
6908
7099
|
const HEALTH_HEADERS = Object.freeze({
|
|
@@ -6932,8 +7123,7 @@ function handleNodeHealthRequest(req, res) {
|
|
|
6932
7123
|
else res.end(body);
|
|
6933
7124
|
return true;
|
|
6934
7125
|
}
|
|
6935
|
-
|
|
6936
|
-
//#region server/node-request-bridge.ts
|
|
7126
|
+
// server/node-request-bridge.ts
|
|
6937
7127
|
function fetchUrlForNodeRequest(headers, pathname, scheme) {
|
|
6938
7128
|
return `${scheme}://${headers.host ?? headers[":authority"]}${pathname}`;
|
|
6939
7129
|
}
|
|
@@ -6955,8 +7145,7 @@ const CONNECTION_SPECIFIC_HEADERS = /* @__PURE__ */ new Set([
|
|
|
6955
7145
|
function isResponseHeaderAllowed(name, httpVersionMajor) {
|
|
6956
7146
|
return httpVersionMajor < 2 || !CONNECTION_SPECIFIC_HEADERS.has(name.toLowerCase());
|
|
6957
7147
|
}
|
|
6958
|
-
|
|
6959
|
-
//#region server/vite-hono-plugin.ts
|
|
7148
|
+
// server/vite-hono-plugin.ts
|
|
6960
7149
|
function honoMiddlewarePlugin(config) {
|
|
6961
7150
|
let resolvedAuthToken = "";
|
|
6962
7151
|
return {
|
|
@@ -6972,7 +7161,8 @@ function honoMiddlewarePlugin(config) {
|
|
|
6972
7161
|
port: config.sandboxPort,
|
|
6973
7162
|
host: config.sandboxHost,
|
|
6974
7163
|
allowedOrigins: config.allowedOrigins,
|
|
6975
|
-
publicUrl: config.sandboxPublicUrl
|
|
7164
|
+
publicUrl: config.sandboxPublicUrl,
|
|
7165
|
+
allowedHosts: config.allowedHosts
|
|
6976
7166
|
});
|
|
6977
7167
|
await sandboxController.start();
|
|
6978
7168
|
const appOriginController = createAppOriginController({
|
|
@@ -7097,8 +7287,7 @@ function honoMiddlewarePlugin(config) {
|
|
|
7097
7287
|
}
|
|
7098
7288
|
};
|
|
7099
7289
|
}
|
|
7100
|
-
|
|
7101
|
-
//#region server/vite-base-config.ts
|
|
7290
|
+
// server/vite-base-config.ts
|
|
7102
7291
|
const NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE = [
|
|
7103
7292
|
"@modelcontextprotocol/client/stdio",
|
|
7104
7293
|
"atomically",
|
|
@@ -7241,8 +7430,7 @@ function vitestSharedPaths(clientDir) {
|
|
|
7241
7430
|
}
|
|
7242
7431
|
};
|
|
7243
7432
|
}
|
|
7244
|
-
|
|
7245
|
-
//#region server/start-vite-dev-server.ts
|
|
7433
|
+
// server/start-vite-dev-server.ts
|
|
7246
7434
|
const __dirname$2 = dirname(fileURLToPath(import.meta.url));
|
|
7247
7435
|
async function startViteDevServer(config) {
|
|
7248
7436
|
const root = resolve(join(__dirname$2, ".."));
|
|
@@ -7275,8 +7463,10 @@ async function startViteDevServer(config) {
|
|
|
7275
7463
|
await server.close();
|
|
7276
7464
|
} };
|
|
7277
7465
|
}
|
|
7278
|
-
|
|
7279
|
-
|
|
7466
|
+
// server/server.ts
|
|
7467
|
+
function isApiPath(path) {
|
|
7468
|
+
return path === "/api" || path.startsWith("/api/");
|
|
7469
|
+
}
|
|
7280
7470
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
7281
7471
|
const __dirname$1 = dirname(__filename$1);
|
|
7282
7472
|
async function startHonoServer(config) {
|
|
@@ -7284,7 +7474,8 @@ async function startHonoServer(config) {
|
|
|
7284
7474
|
port: config.sandboxPort,
|
|
7285
7475
|
host: config.sandboxHost,
|
|
7286
7476
|
allowedOrigins: config.allowedOrigins,
|
|
7287
|
-
publicUrl: config.sandboxPublicUrl
|
|
7477
|
+
publicUrl: config.sandboxPublicUrl,
|
|
7478
|
+
allowedHosts: config.allowedHosts
|
|
7288
7479
|
});
|
|
7289
7480
|
await sandboxController.start();
|
|
7290
7481
|
const appOriginController = createAppOriginController({
|
|
@@ -7337,7 +7528,7 @@ async function startHonoServer(config) {
|
|
|
7337
7528
|
});
|
|
7338
7529
|
app.use("/*", serveStatic({ root: rootPath }));
|
|
7339
7530
|
app.get("*", async (c) => {
|
|
7340
|
-
if (c.req.path
|
|
7531
|
+
if (isApiPath(c.req.path)) return c.notFound();
|
|
7341
7532
|
try {
|
|
7342
7533
|
return serveIndexHtml(c);
|
|
7343
7534
|
} catch (error) {
|
|
@@ -7374,8 +7565,7 @@ async function startHonoServer(config) {
|
|
|
7374
7565
|
});
|
|
7375
7566
|
} };
|
|
7376
7567
|
}
|
|
7377
|
-
|
|
7378
|
-
//#region server/ensure-web-build.ts
|
|
7568
|
+
// server/ensure-web-build.ts
|
|
7379
7569
|
function runViteBuild(webRoot, log) {
|
|
7380
7570
|
const result = spawnSync("npm", ["run", "build:client"], {
|
|
7381
7571
|
cwd: webRoot,
|
|
@@ -7394,8 +7584,7 @@ function ensureWebBuild(webRoot, distRoot, deps = {}) {
|
|
|
7394
7584
|
log("No production web build found at clients/web/dist. Building the web UI now (first run only; this can take a minute)...");
|
|
7395
7585
|
if (build(webRoot, log) !== 0 || !exists(indexHtml)) throw new Error("Could not build the web UI automatically. Build it manually with `npm run build` (run from clients/web), or launch with `--dev` to use the Vite dev server.");
|
|
7396
7586
|
}
|
|
7397
|
-
|
|
7398
|
-
//#region server/run-web.ts
|
|
7587
|
+
// server/run-web.ts
|
|
7399
7588
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7400
7589
|
function ensureStdioCwd(config) {
|
|
7401
7590
|
if ((config.type === "stdio" || config.type === void 0) && config.command && !config.cwd) return {
|
|
@@ -7404,14 +7593,6 @@ function ensureStdioCwd(config) {
|
|
|
7404
7593
|
};
|
|
7405
7594
|
return config;
|
|
7406
7595
|
}
|
|
7407
|
-
function deriveSeedServerId(config) {
|
|
7408
|
-
if (config.type === "sse" || config.type === "streamable-http") try {
|
|
7409
|
-
return new URL(config.url).host || "server";
|
|
7410
|
-
} catch {
|
|
7411
|
-
return "server";
|
|
7412
|
-
}
|
|
7413
|
-
return config.command?.split(/[\\/]/).pop() || "server";
|
|
7414
|
-
}
|
|
7415
7596
|
async function runWeb(argv) {
|
|
7416
7597
|
const program = new Command();
|
|
7417
7598
|
const argSeparatorIndex = argv.indexOf("--");
|
|
@@ -7487,7 +7668,7 @@ async function runWeb(argv) {
|
|
|
7487
7668
|
headers: opts.header
|
|
7488
7669
|
} : { ...config };
|
|
7489
7670
|
if (opts.protocolEra !== void 0) entry.protocolEra = opts.protocolEra;
|
|
7490
|
-
initialServers = { mcpServers: { [
|
|
7671
|
+
initialServers = { mcpServers: { [adHocServerName(config)]: entry } };
|
|
7491
7672
|
writable = false;
|
|
7492
7673
|
} else if (hasHeaders) {
|
|
7493
7674
|
console.error("Error: --header requires an ad-hoc --server-url or command target.");
|
|
@@ -7532,12 +7713,10 @@ async function runWeb(argv) {
|
|
|
7532
7713
|
process.on("SIGTERM", shutdown);
|
|
7533
7714
|
return new Promise(() => {});
|
|
7534
7715
|
}
|
|
7535
|
-
|
|
7536
|
-
//#region src/index.ts
|
|
7716
|
+
// src/index.ts
|
|
7537
7717
|
const __filename = fileURLToPath(import.meta.url);
|
|
7538
7718
|
if (process.argv[1] !== void 0 && resolve(process.argv[1]) === resolve(__filename)) runWeb(process.argv).then((code) => process.exit(code ?? 0)).catch((e) => {
|
|
7539
7719
|
console.error(e);
|
|
7540
7720
|
process.exit(1);
|
|
7541
7721
|
});
|
|
7542
|
-
//#endregion
|
|
7543
7722
|
export { runWeb };
|