@modelcontextprotocol/inspector 2.13.0 → 2.14.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 +1013 -80
- package/clients/mcpdo/build/mcp-bin.js +50 -22
- package/clients/mcpdo/build/mcpdod.js +1 -1
- package/clients/mcpdo/build/{server-BHuKGkMj.js → server-Dv339Ejx.js} +886 -48
- package/clients/tui/build/index.js +781 -46
- package/clients/web/build/index.js +223 -124
- 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-BCud2uMC.js +1594 -0
- package/clients/web/dist/assets/{index-dqp8Sd8W.css → index-Dval2a7I.css} +1 -1
- package/clients/web/dist/assets/{prism-light-BIhJtJkv.js → prism-light-CoVRM9R2.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/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 +5 -4
- package/skills/mcpdo/SKILL.md +14 -0
- package/clients/web/dist/assets/index-BqEbhCDX.js +0 -1587
|
@@ -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";
|
|
@@ -556,26 +556,37 @@ function expectedSecretFields(stored) {
|
|
|
556
556
|
if (isStdioStored(stored) && stored.env) for (const k of Object.keys(stored.env)) fields.push(envSecretField(k));
|
|
557
557
|
return fields;
|
|
558
558
|
}
|
|
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"
|
|
559
|
+
const EXAMPLE_SERVER_URL = "https://example-server.modelcontextprotocol.io/mcp";
|
|
560
|
+
function adHocServerName(config) {
|
|
561
|
+
if (config.type === "sse" || config.type === "streamable-http") try {
|
|
562
|
+
return new URL(config.url).host || "server";
|
|
563
|
+
} catch {
|
|
564
|
+
return "server";
|
|
577
565
|
}
|
|
578
|
-
|
|
566
|
+
return config.command?.split(/[\\/]/).pop() || "server";
|
|
567
|
+
}
|
|
568
|
+
function defaultSeedConfig(tempDir) {
|
|
569
|
+
return { mcpServers: {
|
|
570
|
+
"filesystem-server-default": {
|
|
571
|
+
type: "stdio",
|
|
572
|
+
command: "npx",
|
|
573
|
+
args: [
|
|
574
|
+
"-y",
|
|
575
|
+
"@modelcontextprotocol/server-filesystem",
|
|
576
|
+
tempDir
|
|
577
|
+
]
|
|
578
|
+
},
|
|
579
|
+
"everything-server-default": {
|
|
580
|
+
type: "stdio",
|
|
581
|
+
command: "npx",
|
|
582
|
+
args: ["-y", "@modelcontextprotocol/server-everything"]
|
|
583
|
+
},
|
|
584
|
+
"example-server-default": {
|
|
585
|
+
type: "streamable-http",
|
|
586
|
+
url: EXAMPLE_SERVER_URL
|
|
587
|
+
}
|
|
588
|
+
} };
|
|
589
|
+
}
|
|
579
590
|
//#endregion
|
|
580
591
|
//#region ../../core/storage/own-entry.ts
|
|
581
592
|
function setOwnEntry(map, key, value) {
|
|
@@ -913,8 +924,103 @@ function resolveBindHostname(env = process.env) {
|
|
|
913
924
|
return stripBrackets(host);
|
|
914
925
|
}
|
|
915
926
|
//#endregion
|
|
927
|
+
//#region server/host-allow-list.ts
|
|
928
|
+
const ALLOWED_HOSTS_ENV = "MCP_INSPECTOR_ALLOWED_HOSTS";
|
|
929
|
+
const HOSTNAME_RE = /^[a-z0-9._-]+$/;
|
|
930
|
+
function normalizeHostname(hostname) {
|
|
931
|
+
return hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
932
|
+
}
|
|
933
|
+
function parseHostHeader(value) {
|
|
934
|
+
const raw = value.trim().toLowerCase();
|
|
935
|
+
let hostname;
|
|
936
|
+
let port;
|
|
937
|
+
if (raw.startsWith("[")) {
|
|
938
|
+
const end = raw.indexOf("]");
|
|
939
|
+
if (end < 0) return null;
|
|
940
|
+
hostname = raw.slice(0, end + 1);
|
|
941
|
+
if (!isIPv6(hostname.slice(1, -1))) return null;
|
|
942
|
+
const rest = raw.slice(end + 1);
|
|
943
|
+
if (rest !== "") {
|
|
944
|
+
if (!rest.startsWith(":")) return null;
|
|
945
|
+
port = rest.slice(1);
|
|
946
|
+
}
|
|
947
|
+
} else {
|
|
948
|
+
const colon = raw.indexOf(":");
|
|
949
|
+
hostname = colon < 0 ? raw : raw.slice(0, colon);
|
|
950
|
+
port = colon < 0 ? void 0 : raw.slice(colon + 1);
|
|
951
|
+
}
|
|
952
|
+
if (port !== void 0) {
|
|
953
|
+
if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return null;
|
|
954
|
+
}
|
|
955
|
+
if (hostname.startsWith("[")) return hostname;
|
|
956
|
+
const name = normalizeHostname(hostname);
|
|
957
|
+
return HOSTNAME_RE.test(name) ? name : null;
|
|
958
|
+
}
|
|
959
|
+
function parseAllowedHostsEnv(value) {
|
|
960
|
+
if (!value) return [];
|
|
961
|
+
const out = [];
|
|
962
|
+
for (const entry of value.split(",")) {
|
|
963
|
+
const trimmed = entry.trim();
|
|
964
|
+
if (trimmed === "") continue;
|
|
965
|
+
const domain = trimmed.startsWith(".") ? "." : "";
|
|
966
|
+
const ascii = /[\s/:*@]/.test(trimmed) ? "" : domainToASCII(normalizeHostname(trimmed.slice(domain.length)));
|
|
967
|
+
if (!HOSTNAME_RE.test(ascii)) {
|
|
968
|
+
console.warn(`Ignoring invalid ${ALLOWED_HOSTS_ENV} entry: ${trimmed} (list bare hostnames, e.g. inspector.example.com or .example.com)`);
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
out.push(domain + ascii);
|
|
972
|
+
}
|
|
973
|
+
return out;
|
|
974
|
+
}
|
|
975
|
+
function configuredAllowedHosts(options) {
|
|
976
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
977
|
+
if (!isAllInterfacesHost(options.bindHost)) hosts.add(normalizeHostname(canonicalUrlHost(options.bindHost)));
|
|
978
|
+
for (const origin of options.allowedOrigins) try {
|
|
979
|
+
const { hostname } = new URL(origin);
|
|
980
|
+
if (hostname !== "") hosts.add(normalizeHostname(hostname));
|
|
981
|
+
} catch {}
|
|
982
|
+
for (const extra of options.extraHosts ?? []) hosts.add(normalizeHostname(extra));
|
|
983
|
+
return [...hosts];
|
|
984
|
+
}
|
|
985
|
+
function createHostAllowList(options) {
|
|
986
|
+
const configured = configuredAllowedHosts(options);
|
|
987
|
+
return (hostHeader) => {
|
|
988
|
+
if (hostHeader === void 0) return false;
|
|
989
|
+
const hostname = parseHostHeader(hostHeader);
|
|
990
|
+
if (hostname === null) return false;
|
|
991
|
+
if (hostname.startsWith("[")) return true;
|
|
992
|
+
if (isIPv4(hostname)) return true;
|
|
993
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
|
994
|
+
for (const allowed of configured) {
|
|
995
|
+
if (allowed === hostname) return true;
|
|
996
|
+
if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) return true;
|
|
997
|
+
}
|
|
998
|
+
return false;
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function hostAllowListMiddleware(options) {
|
|
1002
|
+
const isAllowed = createHostAllowList(options);
|
|
1003
|
+
return async (c, next) => {
|
|
1004
|
+
const host = c.req.header("host");
|
|
1005
|
+
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);
|
|
1006
|
+
await next();
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
//#endregion
|
|
916
1010
|
//#region server/sandbox-controller.ts
|
|
917
1011
|
const __dirname$3 = dirname(fileURLToPath(import.meta.url));
|
|
1012
|
+
function isSandboxPath(url) {
|
|
1013
|
+
const path = (url ?? "").split("?", 1)[0];
|
|
1014
|
+
return path === "/sandbox" || path === "/sandbox/";
|
|
1015
|
+
}
|
|
1016
|
+
function publicUrlHosts(publicUrl) {
|
|
1017
|
+
if (!publicUrl) return [];
|
|
1018
|
+
try {
|
|
1019
|
+
return [new URL(publicUrl).hostname];
|
|
1020
|
+
} catch {
|
|
1021
|
+
return [];
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
918
1024
|
const LOOPBACK_FRAME_ANCESTORS = ["http://127.0.0.1:*", "http://localhost:*"];
|
|
919
1025
|
const CSP_HOST_SOURCE = /^[a-z][a-z0-9+.-]*:\/\/[^\s;,'"[\]*]+$/i;
|
|
920
1026
|
function sandboxFrameAncestors(allowedOrigins) {
|
|
@@ -948,7 +1054,12 @@ function resolveSandboxPort() {
|
|
|
948
1054
|
return fromSandbox ?? parseListenPort$1(process.env.SERVER_PORT) ?? 6275;
|
|
949
1055
|
}
|
|
950
1056
|
function createSandboxController(options) {
|
|
951
|
-
const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl } = options;
|
|
1057
|
+
const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl, allowedHosts } = options;
|
|
1058
|
+
const isAllowedHost = createHostAllowList({
|
|
1059
|
+
bindHost: host,
|
|
1060
|
+
allowedOrigins: allowedOrigins ?? [],
|
|
1061
|
+
extraHosts: [...allowedHosts ?? [], ...publicUrlHosts(publicUrl)]
|
|
1062
|
+
});
|
|
952
1063
|
let server = null;
|
|
953
1064
|
let sandboxUrl = null;
|
|
954
1065
|
let boundPort = 0;
|
|
@@ -977,18 +1088,25 @@ function createSandboxController(options) {
|
|
|
977
1088
|
resolve(value);
|
|
978
1089
|
};
|
|
979
1090
|
server = createServer((req, res) => {
|
|
980
|
-
if (req.
|
|
1091
|
+
if (!isAllowedHost(req.headers.host)) {
|
|
1092
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
1093
|
+
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).`);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const isHead = req.method === "HEAD";
|
|
1097
|
+
if (req.method !== "GET" && !isHead || !isSandboxPath(req.url)) {
|
|
981
1098
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
982
1099
|
res.end("Not Found");
|
|
983
1100
|
return;
|
|
984
1101
|
}
|
|
985
1102
|
res.writeHead(200, {
|
|
986
1103
|
"Content-Type": "text/html; charset=utf-8",
|
|
1104
|
+
"Content-Length": Buffer.byteLength(sandboxHtml),
|
|
987
1105
|
"Cache-Control": "no-store, no-cache, must-revalidate",
|
|
988
1106
|
Pragma: "no-cache",
|
|
989
1107
|
"Content-Security-Policy": SANDBOX_PROXY_CSP
|
|
990
1108
|
});
|
|
991
|
-
res.end(sandboxHtml);
|
|
1109
|
+
res.end(isHead ? void 0 : sandboxHtml);
|
|
992
1110
|
});
|
|
993
1111
|
let retriedDynamic = false;
|
|
994
1112
|
server.on("error", (err) => {
|
|
@@ -1318,89 +1436,6 @@ function resolveTlsConfig(env = process.env) {
|
|
|
1318
1436
|
};
|
|
1319
1437
|
}
|
|
1320
1438
|
//#endregion
|
|
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
1439
|
//#region server/web-server-config.ts
|
|
1405
1440
|
const inspectorVersion = readInspectorVersionSafe(import.meta.url);
|
|
1406
1441
|
function defaultEnvironmentFromProcess(extra) {
|
|
@@ -1522,6 +1557,10 @@ function defaultAllowedOrigins(hostname, port, scheme = "http") {
|
|
|
1522
1557
|
];
|
|
1523
1558
|
return [webOrigin(scheme, h, port)];
|
|
1524
1559
|
}
|
|
1560
|
+
function weakAuthTokenWarning(token) {
|
|
1561
|
+
if (token === "" || token.length >= 32) return null;
|
|
1562
|
+
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.`;
|
|
1563
|
+
}
|
|
1525
1564
|
function buildWebServerConfig(options = {}) {
|
|
1526
1565
|
const { initialMcpConfig = null, mcpConfigPath, writable = true, initialServers = null } = options;
|
|
1527
1566
|
const rawPort = process.env.CLIENT_PORT?.trim() || "6274";
|
|
@@ -1531,6 +1570,8 @@ function buildWebServerConfig(options = {}) {
|
|
|
1531
1570
|
const tls = resolveTlsConfig();
|
|
1532
1571
|
const dangerouslyOmitAuth = isEnvFlagEnabled(process.env.DANGEROUSLY_OMIT_AUTH);
|
|
1533
1572
|
const authToken = dangerouslyOmitAuth ? "" : process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] ?? process.env["MCP_PROXY_AUTH_TOKEN"] ?? "";
|
|
1573
|
+
const weakToken = weakAuthTokenWarning(authToken);
|
|
1574
|
+
if (weakToken) console.warn(weakToken);
|
|
1534
1575
|
const sandboxPort = resolveSandboxPort();
|
|
1535
1576
|
const requestedAppOriginPort = resolveAppOriginPort();
|
|
1536
1577
|
const appOriginCollidesWith = requestedAppOriginPort !== 0 && (requestedAppOriginPort === port ? "the web server (CLIENT_PORT)" : requestedAppOriginPort === sandboxPort ? "the MCP Apps sandbox (MCP_SANDBOX_PORT)" : null);
|
|
@@ -5013,9 +5054,13 @@ function waitForProgressToken(waits, token) {
|
|
|
5013
5054
|
//#endregion
|
|
5014
5055
|
//#region ../../core/mcp/remote/node/remote-session.ts
|
|
5015
5056
|
var RemoteSession = class RemoteSession {
|
|
5057
|
+
static DEFAULT_DETACH_GRACE_MS = 6e4;
|
|
5058
|
+
static MAX_QUEUED_EVENTS = 1e3;
|
|
5016
5059
|
sessionId;
|
|
5017
5060
|
transport;
|
|
5018
5061
|
eventQueue = [];
|
|
5062
|
+
droppedEventCount = 0;
|
|
5063
|
+
detachTimer;
|
|
5019
5064
|
eventConsumer = null;
|
|
5020
5065
|
transportDead = false;
|
|
5021
5066
|
transportError = null;
|
|
@@ -5046,13 +5091,47 @@ var RemoteSession = class RemoteSession {
|
|
|
5046
5091
|
this.transport.setProtocolVersion?.(version);
|
|
5047
5092
|
}
|
|
5048
5093
|
setEventConsumer(consumer) {
|
|
5094
|
+
this.cancelDetachGrace();
|
|
5049
5095
|
this.eventConsumer = consumer;
|
|
5096
|
+
if (this.droppedEventCount > 0) {
|
|
5097
|
+
const dropped = this.droppedEventCount;
|
|
5098
|
+
this.droppedEventCount = 0;
|
|
5099
|
+
consumer({
|
|
5100
|
+
type: "stdio_log",
|
|
5101
|
+
data: {
|
|
5102
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5103
|
+
message: `[inspector] ${dropped} event(s) were dropped while no client was attached (the queue holds at most ${RemoteSession.MAX_QUEUED_EVENTS})`
|
|
5104
|
+
}
|
|
5105
|
+
});
|
|
5106
|
+
}
|
|
5050
5107
|
while (this.eventQueue.length > 0) consumer(this.eventQueue.shift());
|
|
5051
5108
|
}
|
|
5052
5109
|
clearEventConsumer() {
|
|
5053
5110
|
this.eventConsumer = null;
|
|
5054
5111
|
return this.transportDead;
|
|
5055
5112
|
}
|
|
5113
|
+
detachEventConsumer(consumer) {
|
|
5114
|
+
if (this.eventConsumer !== consumer) return false;
|
|
5115
|
+
this.eventConsumer = null;
|
|
5116
|
+
return true;
|
|
5117
|
+
}
|
|
5118
|
+
armDetachGrace(graceMs, onExpire) {
|
|
5119
|
+
this.cancelDetachGrace();
|
|
5120
|
+
this.detachTimer = setTimeout(() => {
|
|
5121
|
+
this.detachTimer = void 0;
|
|
5122
|
+
onExpire();
|
|
5123
|
+
}, graceMs);
|
|
5124
|
+
this.detachTimer.unref();
|
|
5125
|
+
}
|
|
5126
|
+
cancelDetachGrace() {
|
|
5127
|
+
if (this.detachTimer !== void 0) {
|
|
5128
|
+
clearTimeout(this.detachTimer);
|
|
5129
|
+
this.detachTimer = void 0;
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
hasPendingDetachGrace() {
|
|
5133
|
+
return this.detachTimer !== void 0;
|
|
5134
|
+
}
|
|
5056
5135
|
markTransportDead(error) {
|
|
5057
5136
|
this.transportDead = true;
|
|
5058
5137
|
this.transportError = error;
|
|
@@ -5163,7 +5242,13 @@ var RemoteSession = class RemoteSession {
|
|
|
5163
5242
|
}
|
|
5164
5243
|
pushEvent(event) {
|
|
5165
5244
|
if (this.eventConsumer) this.eventConsumer(event);
|
|
5166
|
-
else
|
|
5245
|
+
else {
|
|
5246
|
+
if (this.eventQueue.length >= RemoteSession.MAX_QUEUED_EVENTS) {
|
|
5247
|
+
this.eventQueue.shift();
|
|
5248
|
+
this.droppedEventCount++;
|
|
5249
|
+
}
|
|
5250
|
+
this.eventQueue.push(event);
|
|
5251
|
+
}
|
|
5167
5252
|
}
|
|
5168
5253
|
onMessage(message) {
|
|
5169
5254
|
if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) this.settleRequestWait(message.id);
|
|
@@ -5640,6 +5725,14 @@ function createRemoteApp(options) {
|
|
|
5640
5725
|
const authToken = dangerouslyOmitAuth ? "" : options.authToken || process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] || randomBytes(32).toString("hex");
|
|
5641
5726
|
const app = new Hono();
|
|
5642
5727
|
const sessions = /* @__PURE__ */ new Map();
|
|
5728
|
+
const sessionDetachGraceMs = options.sessionDetachGraceMs ?? RemoteSession.DEFAULT_DETACH_GRACE_MS;
|
|
5729
|
+
const armSessionReap = (session) => {
|
|
5730
|
+
session.armDetachGrace(sessionDetachGraceMs, () => {
|
|
5731
|
+
sessions.delete(session.sessionId);
|
|
5732
|
+
session.rejectActiveRequestWaits(/* @__PURE__ */ new Error("Session closed: no client attached"));
|
|
5733
|
+
session.transport.close().catch(() => {});
|
|
5734
|
+
});
|
|
5735
|
+
};
|
|
5643
5736
|
const { logger: fileLogger, allowedOrigins } = options;
|
|
5644
5737
|
const storageDir = options.storageDir ?? getDefaultStorageDir();
|
|
5645
5738
|
const mcpConfigPath = options.mcpConfigPath ?? getDefaultMcpConfigPath();
|
|
@@ -5826,6 +5919,7 @@ function createRemoteApp(options) {
|
|
|
5826
5919
|
return c.json({ error: `Failed to start transport: ${msg}` }, is401 ? 401 : 500);
|
|
5827
5920
|
}
|
|
5828
5921
|
sessions.set(sessionId, session);
|
|
5922
|
+
armSessionReap(session);
|
|
5829
5923
|
return c.json({ sessionId });
|
|
5830
5924
|
});
|
|
5831
5925
|
app.post("/api/mcp/send", async (c) => {
|
|
@@ -5916,13 +6010,14 @@ function createRemoteApp(options) {
|
|
|
5916
6010
|
const session = sessions.get(sessionId);
|
|
5917
6011
|
if (!session) return c.json({ error: "Session not found" }, 404);
|
|
5918
6012
|
return streamSSE(c, async (stream) => {
|
|
5919
|
-
|
|
6013
|
+
const consumer = (event) => {
|
|
5920
6014
|
const data = JSON.stringify(event);
|
|
5921
6015
|
stream.writeSSE({
|
|
5922
6016
|
event: event.type,
|
|
5923
6017
|
data
|
|
5924
6018
|
});
|
|
5925
|
-
}
|
|
6019
|
+
};
|
|
6020
|
+
session.setEventConsumer(consumer);
|
|
5926
6021
|
if (session.isTransportDead()) {
|
|
5927
6022
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5928
6023
|
session.clearEventConsumer();
|
|
@@ -5930,7 +6025,12 @@ function createRemoteApp(options) {
|
|
|
5930
6025
|
return;
|
|
5931
6026
|
}
|
|
5932
6027
|
await primeAndHoldSseStream(stream, () => {
|
|
5933
|
-
if (session.
|
|
6028
|
+
if (!session.detachEventConsumer(consumer)) return;
|
|
6029
|
+
if (session.isTransportDead()) {
|
|
6030
|
+
sessions.delete(sessionId);
|
|
6031
|
+
return;
|
|
6032
|
+
}
|
|
6033
|
+
armSessionReap(session);
|
|
5934
6034
|
});
|
|
5935
6035
|
});
|
|
5936
6036
|
});
|
|
@@ -5945,6 +6045,7 @@ function createRemoteApp(options) {
|
|
|
5945
6045
|
if (!sessionId) return c.json({ error: "Missing sessionId" }, 400);
|
|
5946
6046
|
const session = sessions.get(sessionId);
|
|
5947
6047
|
if (session) {
|
|
6048
|
+
session.cancelDetachGrace();
|
|
5948
6049
|
session.clearEventConsumer();
|
|
5949
6050
|
await session.transport.close();
|
|
5950
6051
|
sessions.delete(sessionId);
|
|
@@ -6608,8 +6709,9 @@ function createRemoteApp(options) {
|
|
|
6608
6709
|
const settled = await withWriteLock(async () => {
|
|
6609
6710
|
const rawInside = await readStoreFile(mcpConfigPath);
|
|
6610
6711
|
if (rawInside === null) {
|
|
6611
|
-
|
|
6612
|
-
|
|
6712
|
+
const seed = defaultSeedConfig(tmpdir());
|
|
6713
|
+
await writeMcpAndTrackMtime(serializeStore(seed));
|
|
6714
|
+
return seed;
|
|
6613
6715
|
}
|
|
6614
6716
|
const parsedInside = parseStore(rawInside);
|
|
6615
6717
|
const inside = { mcpServers: normalizeMcpServers(parsedInside?.mcpServers) };
|
|
@@ -6972,7 +7074,8 @@ function honoMiddlewarePlugin(config) {
|
|
|
6972
7074
|
port: config.sandboxPort,
|
|
6973
7075
|
host: config.sandboxHost,
|
|
6974
7076
|
allowedOrigins: config.allowedOrigins,
|
|
6975
|
-
publicUrl: config.sandboxPublicUrl
|
|
7077
|
+
publicUrl: config.sandboxPublicUrl,
|
|
7078
|
+
allowedHosts: config.allowedHosts
|
|
6976
7079
|
});
|
|
6977
7080
|
await sandboxController.start();
|
|
6978
7081
|
const appOriginController = createAppOriginController({
|
|
@@ -7277,6 +7380,9 @@ async function startViteDevServer(config) {
|
|
|
7277
7380
|
}
|
|
7278
7381
|
//#endregion
|
|
7279
7382
|
//#region server/server.ts
|
|
7383
|
+
function isApiPath(path) {
|
|
7384
|
+
return path === "/api" || path.startsWith("/api/");
|
|
7385
|
+
}
|
|
7280
7386
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
7281
7387
|
const __dirname$1 = dirname(__filename$1);
|
|
7282
7388
|
async function startHonoServer(config) {
|
|
@@ -7284,7 +7390,8 @@ async function startHonoServer(config) {
|
|
|
7284
7390
|
port: config.sandboxPort,
|
|
7285
7391
|
host: config.sandboxHost,
|
|
7286
7392
|
allowedOrigins: config.allowedOrigins,
|
|
7287
|
-
publicUrl: config.sandboxPublicUrl
|
|
7393
|
+
publicUrl: config.sandboxPublicUrl,
|
|
7394
|
+
allowedHosts: config.allowedHosts
|
|
7288
7395
|
});
|
|
7289
7396
|
await sandboxController.start();
|
|
7290
7397
|
const appOriginController = createAppOriginController({
|
|
@@ -7337,7 +7444,7 @@ async function startHonoServer(config) {
|
|
|
7337
7444
|
});
|
|
7338
7445
|
app.use("/*", serveStatic({ root: rootPath }));
|
|
7339
7446
|
app.get("*", async (c) => {
|
|
7340
|
-
if (c.req.path
|
|
7447
|
+
if (isApiPath(c.req.path)) return c.notFound();
|
|
7341
7448
|
try {
|
|
7342
7449
|
return serveIndexHtml(c);
|
|
7343
7450
|
} catch (error) {
|
|
@@ -7404,14 +7511,6 @@ function ensureStdioCwd(config) {
|
|
|
7404
7511
|
};
|
|
7405
7512
|
return config;
|
|
7406
7513
|
}
|
|
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
7514
|
async function runWeb(argv) {
|
|
7416
7515
|
const program = new Command();
|
|
7417
7516
|
const argSeparatorIndex = argv.indexOf("--");
|
|
@@ -7487,7 +7586,7 @@ async function runWeb(argv) {
|
|
|
7487
7586
|
headers: opts.header
|
|
7488
7587
|
} : { ...config };
|
|
7489
7588
|
if (opts.protocolEra !== void 0) entry.protocolEra = opts.protocolEra;
|
|
7490
|
-
initialServers = { mcpServers: { [
|
|
7589
|
+
initialServers = { mcpServers: { [adHocServerName(config)]: entry } };
|
|
7491
7590
|
writable = false;
|
|
7492
7591
|
} else if (hasHeaders) {
|
|
7493
7592
|
console.error("Error: --header requires an ad-hoc --server-url or command target.");
|
|
Binary file
|
|
Binary file
|
|
Binary file
|