@modelcontextprotocol/inspector 2.11.0 → 2.12.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 +2 -0
- package/clients/cli/build/index.js +1149 -190
- package/clients/mcpdo/build/{chunk-O6RN6WMD.js → chunk-LBQDT5VV.js} +726 -101
- package/clients/mcpdo/build/mcp-bin.js +14 -1
- package/clients/mcpdo/build/mcpdod.js +1 -1
- package/clients/tui/build/index.js +734 -106
- package/clients/web/build/index.js +738 -89
- package/clients/web/dist/assets/{index-B79AqubU.js → index-qtX15-Bz.js} +111 -111
- package/clients/web/dist/assets/{prism-light-J_CKjs7N.js → prism-light-DfUe_Xlh.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +2 -2
|
@@ -159,6 +159,12 @@ var DirectoryReadResultSchema = z.looseObject({
|
|
|
159
159
|
resources: z.array(DirectoryChildSchema),
|
|
160
160
|
nextCursor: z.string().optional()
|
|
161
161
|
});
|
|
162
|
+
var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
|
|
163
|
+
{
|
|
164
|
+
ttlMs: z.int().min(0),
|
|
165
|
+
cacheScope: z.enum(["public", "private"])
|
|
166
|
+
}
|
|
167
|
+
);
|
|
162
168
|
|
|
163
169
|
// ../../core/mcp/sha256.ts
|
|
164
170
|
var K = new Uint32Array([
|
|
@@ -1061,14 +1067,25 @@ function readInspectorVersionSafe(callerUrl) {
|
|
|
1061
1067
|
// ../../core/node/openUrl.ts
|
|
1062
1068
|
import open from "open";
|
|
1063
1069
|
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1070
|
+
var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
1071
|
+
function toOpenableHref(href) {
|
|
1072
|
+
let parsed;
|
|
1073
|
+
try {
|
|
1074
|
+
parsed = new URL(href);
|
|
1075
|
+
} catch {
|
|
1076
|
+
throw new Error("refusing to open an invalid URL");
|
|
1077
|
+
}
|
|
1078
|
+
if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
|
|
1079
|
+
throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
|
|
1080
|
+
}
|
|
1081
|
+
return parsed.href;
|
|
1082
|
+
}
|
|
1083
|
+
function absorbLateErrors(child) {
|
|
1084
|
+
child.on("error", () => {
|
|
1068
1085
|
});
|
|
1069
1086
|
}
|
|
1070
1087
|
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
1071
|
-
const href = typeof url === "string" ? url : url.href;
|
|
1088
|
+
const href = toOpenableHref(typeof url === "string" ? url : url.href);
|
|
1072
1089
|
let timer;
|
|
1073
1090
|
const timeout = new Promise((_, reject) => {
|
|
1074
1091
|
timer = setTimeout(
|
|
@@ -1077,8 +1094,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
|
1077
1094
|
);
|
|
1078
1095
|
});
|
|
1079
1096
|
try {
|
|
1080
|
-
|
|
1081
|
-
await Promise.race([opened, timeout]);
|
|
1097
|
+
await Promise.race([open(href).then(absorbLateErrors), timeout]);
|
|
1082
1098
|
} finally {
|
|
1083
1099
|
clearTimeout(timer);
|
|
1084
1100
|
}
|
|
@@ -1589,6 +1605,153 @@ function resolveAppOriginPublicOrigin(raw, allowedOrigins, sandboxUrl) {
|
|
|
1589
1605
|
return url.origin;
|
|
1590
1606
|
}
|
|
1591
1607
|
|
|
1608
|
+
// server/tls.ts
|
|
1609
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1610
|
+
import { createSecureContext } from "tls";
|
|
1611
|
+
var TLS_KEY_PATH_ENV = "INSPECTOR_TLS_KEY_PATH";
|
|
1612
|
+
var TLS_CERT_PATH_ENV = "INSPECTOR_TLS_CERT_PATH";
|
|
1613
|
+
function webScheme(tls) {
|
|
1614
|
+
return tls ? "https" : "http";
|
|
1615
|
+
}
|
|
1616
|
+
function readPem(env, path6) {
|
|
1617
|
+
try {
|
|
1618
|
+
return readFileSync4(path6, "utf-8");
|
|
1619
|
+
} catch (err) {
|
|
1620
|
+
const { code } = err;
|
|
1621
|
+
throw new Error(
|
|
1622
|
+
`Cannot read ${env}="${path6}" (${code}): check that the file exists and is readable.`,
|
|
1623
|
+
{ cause: err }
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function resolveTlsConfig(env = process.env) {
|
|
1628
|
+
const keyPath = env[TLS_KEY_PATH_ENV]?.trim();
|
|
1629
|
+
const certPath = env[TLS_CERT_PATH_ENV]?.trim();
|
|
1630
|
+
if (!keyPath && !certPath) return void 0;
|
|
1631
|
+
if (!keyPath || !certPath) {
|
|
1632
|
+
const missing = keyPath ? TLS_CERT_PATH_ENV : TLS_KEY_PATH_ENV;
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`TLS needs both ${TLS_KEY_PATH_ENV} and ${TLS_CERT_PATH_ENV}; ${missing} is not set. Set both to enable https, or neither for plain http.`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
const key = readPem(TLS_KEY_PATH_ENV, keyPath);
|
|
1638
|
+
const cert = readPem(TLS_CERT_PATH_ENV, certPath);
|
|
1639
|
+
try {
|
|
1640
|
+
if (!key.trim() || !cert.trim()) throw new Error("empty PEM file");
|
|
1641
|
+
createSecureContext({ key, cert });
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
const reason = err.message;
|
|
1644
|
+
throw new Error(
|
|
1645
|
+
`${TLS_KEY_PATH_ENV}="${keyPath}" and ${TLS_CERT_PATH_ENV}="${certPath}" are not a usable PEM key/certificate pair (${reason}).`,
|
|
1646
|
+
{ cause: err }
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
return { key, cert };
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// server/host-allow-list.ts
|
|
1653
|
+
import { isIPv4, isIPv6 as isIPv62 } from "net";
|
|
1654
|
+
import { domainToASCII } from "url";
|
|
1655
|
+
var ALLOWED_HOSTS_ENV = "MCP_INSPECTOR_ALLOWED_HOSTS";
|
|
1656
|
+
var HOSTNAME_RE = /^[a-z0-9._-]+$/;
|
|
1657
|
+
function normalizeHostname(hostname) {
|
|
1658
|
+
return hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
1659
|
+
}
|
|
1660
|
+
function parseHostHeader(value) {
|
|
1661
|
+
const raw = value.trim().toLowerCase();
|
|
1662
|
+
let hostname;
|
|
1663
|
+
let port;
|
|
1664
|
+
if (raw.startsWith("[")) {
|
|
1665
|
+
const end = raw.indexOf("]");
|
|
1666
|
+
if (end < 0) return null;
|
|
1667
|
+
hostname = raw.slice(0, end + 1);
|
|
1668
|
+
if (!isIPv62(hostname.slice(1, -1))) return null;
|
|
1669
|
+
const rest = raw.slice(end + 1);
|
|
1670
|
+
if (rest !== "") {
|
|
1671
|
+
if (!rest.startsWith(":")) return null;
|
|
1672
|
+
port = rest.slice(1);
|
|
1673
|
+
}
|
|
1674
|
+
} else {
|
|
1675
|
+
const colon = raw.indexOf(":");
|
|
1676
|
+
hostname = colon < 0 ? raw : raw.slice(0, colon);
|
|
1677
|
+
port = colon < 0 ? void 0 : raw.slice(colon + 1);
|
|
1678
|
+
}
|
|
1679
|
+
if (port !== void 0) {
|
|
1680
|
+
if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return null;
|
|
1681
|
+
}
|
|
1682
|
+
if (hostname.startsWith("[")) return hostname;
|
|
1683
|
+
const name = normalizeHostname(hostname);
|
|
1684
|
+
return HOSTNAME_RE.test(name) ? name : null;
|
|
1685
|
+
}
|
|
1686
|
+
function parseAllowedHostsEnv(value) {
|
|
1687
|
+
if (!value) return [];
|
|
1688
|
+
const out = [];
|
|
1689
|
+
for (const entry of value.split(",")) {
|
|
1690
|
+
const trimmed = entry.trim();
|
|
1691
|
+
if (trimmed === "") continue;
|
|
1692
|
+
const domain = trimmed.startsWith(".") ? "." : "";
|
|
1693
|
+
const ascii = /[\s/:*@]/.test(trimmed) ? "" : domainToASCII(normalizeHostname(trimmed.slice(domain.length)));
|
|
1694
|
+
if (!HOSTNAME_RE.test(ascii)) {
|
|
1695
|
+
console.warn(
|
|
1696
|
+
`Ignoring invalid ${ALLOWED_HOSTS_ENV} entry: ${trimmed} (list bare hostnames, e.g. inspector.example.com or .example.com)`
|
|
1697
|
+
);
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
out.push(domain + ascii);
|
|
1701
|
+
}
|
|
1702
|
+
return out;
|
|
1703
|
+
}
|
|
1704
|
+
function configuredAllowedHosts(options) {
|
|
1705
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
1706
|
+
if (!isAllInterfacesHost(options.bindHost)) {
|
|
1707
|
+
hosts.add(normalizeHostname(canonicalUrlHost(options.bindHost)));
|
|
1708
|
+
}
|
|
1709
|
+
for (const origin of options.allowedOrigins) {
|
|
1710
|
+
try {
|
|
1711
|
+
const { hostname } = new URL(origin);
|
|
1712
|
+
if (hostname !== "") hosts.add(normalizeHostname(hostname));
|
|
1713
|
+
} catch {
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
for (const extra of options.extraHosts ?? []) {
|
|
1717
|
+
hosts.add(normalizeHostname(extra));
|
|
1718
|
+
}
|
|
1719
|
+
return [...hosts];
|
|
1720
|
+
}
|
|
1721
|
+
function createHostAllowList(options) {
|
|
1722
|
+
const configured = configuredAllowedHosts(options);
|
|
1723
|
+
return (hostHeader) => {
|
|
1724
|
+
if (hostHeader === void 0) return false;
|
|
1725
|
+
const hostname = parseHostHeader(hostHeader);
|
|
1726
|
+
if (hostname === null) return false;
|
|
1727
|
+
if (hostname.startsWith("[")) return true;
|
|
1728
|
+
if (isIPv4(hostname)) return true;
|
|
1729
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) {
|
|
1730
|
+
return true;
|
|
1731
|
+
}
|
|
1732
|
+
for (const allowed of configured) {
|
|
1733
|
+
if (allowed === hostname) return true;
|
|
1734
|
+
if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) {
|
|
1735
|
+
return true;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return false;
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
function hostAllowListMiddleware(options) {
|
|
1742
|
+
const isAllowed = createHostAllowList(options);
|
|
1743
|
+
return async (c, next) => {
|
|
1744
|
+
const host = c.req.header("host");
|
|
1745
|
+
if (!isAllowed(host)) {
|
|
1746
|
+
return c.text(
|
|
1747
|
+
`Forbidden: Host "${host ?? ""}" is not allowed. To serve the Inspector under this name, add it to ${ALLOWED_HOSTS_ENV} (or to ALLOWED_ORIGINS).`,
|
|
1748
|
+
403
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
await next();
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1592
1755
|
// server/web-server-config.ts
|
|
1593
1756
|
var inspectorVersion = readInspectorVersionSafe(import.meta.url);
|
|
1594
1757
|
function defaultEnvironmentFromProcess(extra) {
|
|
@@ -1670,7 +1833,7 @@ function transportDefaults(config) {
|
|
|
1670
1833
|
function printServerBanner(config, actualPort, resolvedToken, sandboxUrl, secretStorage) {
|
|
1671
1834
|
const h = canonicalUrlHost(config.hostname);
|
|
1672
1835
|
const bannerHost = isAllInterfacesHost(h) ? "localhost" : h;
|
|
1673
|
-
const baseUrl =
|
|
1836
|
+
const baseUrl = webOrigin(webScheme(config.tls), bannerHost, actualPort);
|
|
1674
1837
|
const url = config.dangerouslyOmitAuth || !resolvedToken ? baseUrl : `${baseUrl}?${API_SERVER_ENV_VARS.AUTH_TOKEN}=${resolvedToken}`;
|
|
1675
1838
|
console.log(`
|
|
1676
1839
|
MCP Inspector Web is up and running at:
|
|
@@ -1695,6 +1858,9 @@ MCP Inspector Web is up and running at:
|
|
|
1695
1858
|
}
|
|
1696
1859
|
return url;
|
|
1697
1860
|
}
|
|
1861
|
+
function portInUseMessage(config) {
|
|
1862
|
+
return `MCP Inspector PORT IS IN USE at ${webScheme(config.tls)}://${formatHostForUrl(config.hostname)}:${config.port}`;
|
|
1863
|
+
}
|
|
1698
1864
|
function openBrowser(url) {
|
|
1699
1865
|
openUrl(url).catch((err) => {
|
|
1700
1866
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -1704,29 +1870,30 @@ function openBrowser(url) {
|
|
|
1704
1870
|
});
|
|
1705
1871
|
}
|
|
1706
1872
|
var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1707
|
-
function
|
|
1708
|
-
|
|
1873
|
+
function webOrigin(scheme, host, port) {
|
|
1874
|
+
const defaultPort = scheme === "https" ? 443 : 80;
|
|
1875
|
+
return port === defaultPort ? `${scheme}://${host}` : `${scheme}://${host}:${port}`;
|
|
1709
1876
|
}
|
|
1710
|
-
function loopbackOrigins(port) {
|
|
1877
|
+
function loopbackOrigins(scheme, port) {
|
|
1711
1878
|
return [
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1879
|
+
webOrigin(scheme, "localhost", port),
|
|
1880
|
+
webOrigin(scheme, "127.0.0.1", port),
|
|
1881
|
+
webOrigin(scheme, "[::1]", port)
|
|
1715
1882
|
];
|
|
1716
1883
|
}
|
|
1717
|
-
function defaultAllowedOrigins(hostname, port) {
|
|
1884
|
+
function defaultAllowedOrigins(hostname, port, scheme = "http") {
|
|
1718
1885
|
const h = canonicalUrlHost(hostname);
|
|
1719
1886
|
if (LOOPBACK_HOSTNAMES.has(h)) {
|
|
1720
|
-
return loopbackOrigins(port);
|
|
1887
|
+
return loopbackOrigins(scheme, port);
|
|
1721
1888
|
}
|
|
1722
1889
|
if (isAllInterfacesHost(h)) {
|
|
1723
1890
|
return [
|
|
1724
|
-
...loopbackOrigins(port),
|
|
1725
|
-
|
|
1726
|
-
|
|
1891
|
+
...loopbackOrigins(scheme, port),
|
|
1892
|
+
webOrigin(scheme, "0.0.0.0", port),
|
|
1893
|
+
webOrigin(scheme, "[::]", port)
|
|
1727
1894
|
];
|
|
1728
1895
|
}
|
|
1729
|
-
return [
|
|
1896
|
+
return [webOrigin(scheme, h, port)];
|
|
1730
1897
|
}
|
|
1731
1898
|
function buildWebServerConfig(options = {}) {
|
|
1732
1899
|
const {
|
|
@@ -1743,6 +1910,7 @@ function buildWebServerConfig(options = {}) {
|
|
|
1743
1910
|
);
|
|
1744
1911
|
}
|
|
1745
1912
|
const hostname = resolveBindHostname();
|
|
1913
|
+
const tls = resolveTlsConfig();
|
|
1746
1914
|
const dangerouslyOmitAuth = isEnvFlagEnabled(
|
|
1747
1915
|
process.env.DANGEROUSLY_OMIT_AUTH
|
|
1748
1916
|
);
|
|
@@ -1778,7 +1946,7 @@ function buildWebServerConfig(options = {}) {
|
|
|
1778
1946
|
return null;
|
|
1779
1947
|
}
|
|
1780
1948
|
}).filter((o) => o !== null);
|
|
1781
|
-
const allowedOrigins = configuredOrigins?.length ? configuredOrigins : defaultAllowedOrigins(hostname, port);
|
|
1949
|
+
const allowedOrigins = configuredOrigins?.length ? configuredOrigins : defaultAllowedOrigins(hostname, port, webScheme(tls));
|
|
1782
1950
|
const sandboxPublicUrl = resolveSandboxPublicUrl(
|
|
1783
1951
|
process.env[SANDBOX_FULL_ADDRESS_ENV],
|
|
1784
1952
|
allowedOrigins
|
|
@@ -1799,13 +1967,15 @@ function buildWebServerConfig(options = {}) {
|
|
|
1799
1967
|
initialServers,
|
|
1800
1968
|
storageDir: process.env.MCP_STORAGE_DIR,
|
|
1801
1969
|
allowedOrigins,
|
|
1970
|
+
allowedHosts: parseAllowedHostsEnv(process.env[ALLOWED_HOSTS_ENV]),
|
|
1802
1971
|
sandboxPort,
|
|
1803
1972
|
sandboxHost: hostname,
|
|
1804
1973
|
appOriginPort,
|
|
1805
1974
|
sandboxPublicUrl,
|
|
1806
1975
|
appOriginPublicOrigin,
|
|
1807
1976
|
logger,
|
|
1808
|
-
autoOpen: resolveAutoOpen()
|
|
1977
|
+
autoOpen: resolveAutoOpen(),
|
|
1978
|
+
...tls ? { tls } : {}
|
|
1809
1979
|
};
|
|
1810
1980
|
}
|
|
1811
1981
|
function resolveAutoOpen() {
|
|
@@ -1823,7 +1993,7 @@ import react from "@vitejs/plugin-react";
|
|
|
1823
1993
|
|
|
1824
1994
|
// ../../core/mcp/remote/node/server.ts
|
|
1825
1995
|
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
1826
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1996
|
+
import { existsSync as existsSync3, readFileSync as readFileSync7 } from "fs";
|
|
1827
1997
|
import { stat as fsStat } from "fs/promises";
|
|
1828
1998
|
import { homedir } from "os";
|
|
1829
1999
|
import { Hono } from "hono";
|
|
@@ -2640,11 +2810,95 @@ function createTransportNode(config, options = {}) {
|
|
|
2640
2810
|
}
|
|
2641
2811
|
}
|
|
2642
2812
|
|
|
2813
|
+
// ../../core/mcp/remote/node/cappedBody.ts
|
|
2814
|
+
async function readCappedText(body, maxBytes) {
|
|
2815
|
+
const reader = body.getReader();
|
|
2816
|
+
const decoder = new TextDecoder();
|
|
2817
|
+
let text = "";
|
|
2818
|
+
let bytes = 0;
|
|
2819
|
+
for (; ; ) {
|
|
2820
|
+
const next = await reader.read();
|
|
2821
|
+
if (next.done) return { kind: "complete", text: text + decoder.decode() };
|
|
2822
|
+
bytes += next.value.byteLength;
|
|
2823
|
+
if (bytes > maxBytes) {
|
|
2824
|
+
void reader.cancel().catch(() => {
|
|
2825
|
+
});
|
|
2826
|
+
return { kind: "overflow" };
|
|
2827
|
+
}
|
|
2828
|
+
text += decoder.decode(next.value, { stream: true });
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
function declaresOversizeBody(contentLength, maxBytes) {
|
|
2832
|
+
if (contentLength === null || !/^\s*\d+\s*$/.test(contentLength)) {
|
|
2833
|
+
return false;
|
|
2834
|
+
}
|
|
2835
|
+
return Number(contentLength) > maxBytes;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2643
2838
|
// ../../core/auth/requestTimeout.ts
|
|
2644
2839
|
var OAUTH_TIMEOUT_WIRE_CODE = "oauth_request_timeout";
|
|
2645
2840
|
|
|
2841
|
+
// ../../core/mcp/rateLimit.ts
|
|
2842
|
+
import {
|
|
2843
|
+
SdkHttpError
|
|
2844
|
+
} from "@modelcontextprotocol/client";
|
|
2845
|
+
|
|
2846
|
+
// ../../core/mcp/connectionDiagnostics.ts
|
|
2847
|
+
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
2848
|
+
|
|
2849
|
+
// ../../core/mcp/rateLimit.ts
|
|
2850
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
2851
|
+
function requestIdsOf(body) {
|
|
2852
|
+
if (body === void 0) return [];
|
|
2853
|
+
let parsed;
|
|
2854
|
+
try {
|
|
2855
|
+
parsed = JSON.parse(body);
|
|
2856
|
+
} catch {
|
|
2857
|
+
return [];
|
|
2858
|
+
}
|
|
2859
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
2860
|
+
const ids = [];
|
|
2861
|
+
for (const message of messages) {
|
|
2862
|
+
if (typeof message !== "object" || message === null) continue;
|
|
2863
|
+
if (!("method" in message) || !("id" in message)) continue;
|
|
2864
|
+
const { id } = message;
|
|
2865
|
+
if (typeof id === "string" || typeof id === "number") ids.push(id);
|
|
2866
|
+
}
|
|
2867
|
+
return ids;
|
|
2868
|
+
}
|
|
2869
|
+
var MAX_UNCLAIMED_RATE_LIMITS = 100;
|
|
2870
|
+
var RateLimitLedger = class {
|
|
2871
|
+
// `undefined` is a real value here: a 429 that carried no header.
|
|
2872
|
+
byRequestId = /* @__PURE__ */ new Map();
|
|
2873
|
+
/** File a tracked fetch, if it was a 429. Anything else is ignored. */
|
|
2874
|
+
record(entry) {
|
|
2875
|
+
if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
|
|
2876
|
+
const retryAfter = findHeader(entry.responseHeaders, "retry-after");
|
|
2877
|
+
for (const id of requestIdsOf(entry.requestBody)) {
|
|
2878
|
+
this.byRequestId.delete(id);
|
|
2879
|
+
this.byRequestId.set(id, retryAfter);
|
|
2880
|
+
if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
|
|
2881
|
+
const [oldest] = this.byRequestId.keys();
|
|
2882
|
+
if (oldest !== void 0) this.byRequestId.delete(oldest);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
/** The `Retry-After` filed for `id`, removed as it is read. */
|
|
2887
|
+
take(id) {
|
|
2888
|
+
const retryAfter = this.byRequestId.get(id);
|
|
2889
|
+
this.byRequestId.delete(id);
|
|
2890
|
+
return retryAfter;
|
|
2891
|
+
}
|
|
2892
|
+
clear() {
|
|
2893
|
+
this.byRequestId.clear();
|
|
2894
|
+
}
|
|
2895
|
+
};
|
|
2896
|
+
function isRateLimitError(err) {
|
|
2897
|
+
return SdkHttpError.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2646
2900
|
// ../../core/mcp/remote/node/server.ts
|
|
2647
|
-
import { SdkHttpError } from "@modelcontextprotocol/client";
|
|
2901
|
+
import { SdkHttpError as SdkHttpError2 } from "@modelcontextprotocol/client";
|
|
2648
2902
|
|
|
2649
2903
|
// ../../core/auth/oauth-persist.ts
|
|
2650
2904
|
import { OAuthTokensSchema } from "@modelcontextprotocol/core";
|
|
@@ -3273,7 +3527,7 @@ import * as path4 from "path";
|
|
|
3273
3527
|
|
|
3274
3528
|
// ../../core/auth/node/file-secret-store.ts
|
|
3275
3529
|
import * as crypto2 from "crypto";
|
|
3276
|
-
import { readFileSync as
|
|
3530
|
+
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3277
3531
|
import * as fs3 from "fs/promises";
|
|
3278
3532
|
import * as path3 from "path";
|
|
3279
3533
|
var scrypt2 = (passphrase, salt, keylen, options) => new Promise((resolve10, reject) => {
|
|
@@ -3326,7 +3580,7 @@ function resolveSecretPassphrase(env = process.env, secretFilePath) {
|
|
|
3326
3580
|
}
|
|
3327
3581
|
let contents;
|
|
3328
3582
|
try {
|
|
3329
|
-
contents =
|
|
3583
|
+
contents = readFileSync5(resolved2, "utf-8");
|
|
3330
3584
|
} catch (err) {
|
|
3331
3585
|
return {
|
|
3332
3586
|
// `String(err)` rather than `.message`: `readFileSync` only ever throws
|
|
@@ -4356,7 +4610,7 @@ async function resolveConcreteSecretStore(store) {
|
|
|
4356
4610
|
import { resolve as resolve6 } from "path";
|
|
4357
4611
|
|
|
4358
4612
|
// ../../core/auth/node/oauth-secrets.ts
|
|
4359
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
4613
|
+
import { createHmac, randomUUID as randomUUID2 } from "crypto";
|
|
4360
4614
|
import { OAuthTokensSchema as OAuthTokensSchema2 } from "@modelcontextprotocol/core";
|
|
4361
4615
|
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
4362
4616
|
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
@@ -4389,6 +4643,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
|
|
|
4389
4643
|
var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
|
|
4390
4644
|
var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
|
|
4391
4645
|
var IDP_SESSION_FIELD = "idp-session";
|
|
4646
|
+
var clientCredentialStampField = (field) => `${field}#owner`;
|
|
4647
|
+
var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
|
|
4648
|
+
function clientCredentialStamp(clientId, value) {
|
|
4649
|
+
return JSON.stringify({
|
|
4650
|
+
client_id: clientId,
|
|
4651
|
+
mac: credentialMac(clientId, value)
|
|
4652
|
+
});
|
|
4653
|
+
}
|
|
4654
|
+
function stampedOwner(stamp, value) {
|
|
4655
|
+
if (stamp === void 0) return void 0;
|
|
4656
|
+
let parsed;
|
|
4657
|
+
try {
|
|
4658
|
+
parsed = JSON.parse(stamp);
|
|
4659
|
+
} catch {
|
|
4660
|
+
return void 0;
|
|
4661
|
+
}
|
|
4662
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
4663
|
+
const { client_id: clientId, mac } = parsed;
|
|
4664
|
+
if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
|
|
4665
|
+
return void 0;
|
|
4666
|
+
}
|
|
4667
|
+
return clientId;
|
|
4668
|
+
}
|
|
4392
4669
|
function applyTokensPolicy(tokens, policy) {
|
|
4393
4670
|
if (policy === "none") return void 0;
|
|
4394
4671
|
if (policy === "access" && tokens.refresh_token !== void 0) {
|
|
@@ -4417,12 +4694,18 @@ function splitClientInformation(info, fields, secrets) {
|
|
|
4417
4694
|
if (client_secret === void 0 && registration_access_token === void 0) {
|
|
4418
4695
|
return info;
|
|
4419
4696
|
}
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4697
|
+
const put = (field, value) => {
|
|
4698
|
+
if (value === void 0) return;
|
|
4699
|
+
secrets[field] = value;
|
|
4700
|
+
if (fields.stamped && typeof info.client_id === "string") {
|
|
4701
|
+
secrets[clientCredentialStampField(field)] = clientCredentialStamp(
|
|
4702
|
+
info.client_id,
|
|
4703
|
+
value
|
|
4704
|
+
);
|
|
4705
|
+
}
|
|
4706
|
+
};
|
|
4707
|
+
put(fields.clientSecret, client_secret);
|
|
4708
|
+
put(fields.registrationToken, registration_access_token);
|
|
4426
4709
|
return publicInfo;
|
|
4427
4710
|
}
|
|
4428
4711
|
function joinClientInformation(info, fields, secrets) {
|
|
@@ -4441,15 +4724,18 @@ function joinClientInformation(info, fields, secrets) {
|
|
|
4441
4724
|
}
|
|
4442
4725
|
var issuerClientInfoFields = (issuer) => ({
|
|
4443
4726
|
clientSecret: issuerClientSecretField(issuer),
|
|
4444
|
-
registrationToken: issuerRegistrationTokenField(issuer)
|
|
4727
|
+
registrationToken: issuerRegistrationTokenField(issuer),
|
|
4728
|
+
stamped: true
|
|
4445
4729
|
});
|
|
4446
4730
|
var LEGACY_CLIENT_INFO_FIELDS = {
|
|
4447
4731
|
clientSecret: LEGACY_CLIENT_SECRET_FIELD,
|
|
4448
|
-
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
|
|
4732
|
+
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
4733
|
+
stamped: true
|
|
4449
4734
|
};
|
|
4450
4735
|
var PREREG_CLIENT_INFO_FIELDS = {
|
|
4451
4736
|
clientSecret: PREREG_CLIENT_SECRET_FIELD,
|
|
4452
|
-
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
|
|
4737
|
+
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
|
|
4738
|
+
stamped: false
|
|
4453
4739
|
};
|
|
4454
4740
|
function splitServerOAuthState(state, policy) {
|
|
4455
4741
|
const secrets = {};
|
|
@@ -4599,20 +4885,54 @@ function joinIdpSession(residue, secrets) {
|
|
|
4599
4885
|
return stored ? { ...residue, ...stored } : { ...residue };
|
|
4600
4886
|
}
|
|
4601
4887
|
function serverSecretFields(state) {
|
|
4888
|
+
const stamped = (field) => [
|
|
4889
|
+
field,
|
|
4890
|
+
clientCredentialStampField(field)
|
|
4891
|
+
];
|
|
4602
4892
|
const fields = /* @__PURE__ */ new Set([
|
|
4603
4893
|
LEGACY_TOKENS_FIELD,
|
|
4604
|
-
LEGACY_CLIENT_SECRET_FIELD,
|
|
4605
|
-
LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
4894
|
+
...stamped(LEGACY_CLIENT_SECRET_FIELD),
|
|
4895
|
+
...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
|
|
4606
4896
|
PREREG_CLIENT_SECRET_FIELD,
|
|
4607
4897
|
PREREG_REGISTRATION_TOKEN_FIELD
|
|
4608
4898
|
]);
|
|
4609
4899
|
for (const issuer of Object.keys(state?.byIssuer ?? {})) {
|
|
4610
4900
|
fields.add(issuerTokensField(issuer));
|
|
4611
|
-
|
|
4612
|
-
|
|
4901
|
+
for (const field of stamped(issuerClientSecretField(issuer))) {
|
|
4902
|
+
fields.add(field);
|
|
4903
|
+
}
|
|
4904
|
+
for (const field of stamped(issuerRegistrationTokenField(issuer))) {
|
|
4905
|
+
fields.add(field);
|
|
4906
|
+
}
|
|
4613
4907
|
}
|
|
4614
4908
|
return [...fields];
|
|
4615
4909
|
}
|
|
4910
|
+
function clientCredentialOwners(state) {
|
|
4911
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4912
|
+
const add = (info, fields) => {
|
|
4913
|
+
if (typeof info?.client_id !== "string") return;
|
|
4914
|
+
owners.set(fields.clientSecret, info.client_id);
|
|
4915
|
+
owners.set(fields.registrationToken, info.client_id);
|
|
4916
|
+
};
|
|
4917
|
+
add(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
|
|
4918
|
+
for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
|
|
4919
|
+
add(slot.clientInformation, issuerClientInfoFields(issuer));
|
|
4920
|
+
}
|
|
4921
|
+
return owners;
|
|
4922
|
+
}
|
|
4923
|
+
function restampClientCredentials(state, secrets) {
|
|
4924
|
+
const out = { ...secrets };
|
|
4925
|
+
for (const [field, clientId] of clientCredentialOwners(state)) {
|
|
4926
|
+
const stampField = clientCredentialStampField(field);
|
|
4927
|
+
const value = Object.hasOwn(out, field) ? out[field] : void 0;
|
|
4928
|
+
if (value === void 0) {
|
|
4929
|
+
delete out[stampField];
|
|
4930
|
+
} else if (stampedOwner(out[stampField], value) === void 0) {
|
|
4931
|
+
setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
return out;
|
|
4935
|
+
}
|
|
4616
4936
|
function clientInfoHasPlaintext(info) {
|
|
4617
4937
|
return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
|
|
4618
4938
|
}
|
|
@@ -4653,6 +4973,44 @@ function coveredLocations(location, byLocation) {
|
|
|
4653
4973
|
([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
|
|
4654
4974
|
).map(([recorded, keys]) => ({ location: recorded, keys }));
|
|
4655
4975
|
}
|
|
4976
|
+
var emptyKeys = () => ({
|
|
4977
|
+
servers: /* @__PURE__ */ new Set(),
|
|
4978
|
+
idpSessions: /* @__PURE__ */ new Set(),
|
|
4979
|
+
owners: /* @__PURE__ */ new Map()
|
|
4980
|
+
});
|
|
4981
|
+
function candidateFields(location, byLocation) {
|
|
4982
|
+
const fields = /* @__PURE__ */ new Map();
|
|
4983
|
+
for (const { keys } of coveredLocations(location, byLocation)) {
|
|
4984
|
+
for (const [url, owned] of keys.owners) {
|
|
4985
|
+
const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
|
|
4986
|
+
for (const field of owned.keys()) kept.add(field);
|
|
4987
|
+
fields.set(url, kept);
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
return fields;
|
|
4991
|
+
}
|
|
4992
|
+
function addMissingOwners(target, source) {
|
|
4993
|
+
for (const [url, fields] of source) {
|
|
4994
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
4995
|
+
for (const [field, clientId] of fields) {
|
|
4996
|
+
if (!kept.has(field)) kept.set(field, clientId);
|
|
4997
|
+
}
|
|
4998
|
+
if (kept.size > 0) target.set(url, kept);
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
function mergeOwners(target, source) {
|
|
5002
|
+
let changed = false;
|
|
5003
|
+
for (const [url, fields] of source) {
|
|
5004
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
5005
|
+
for (const [field, clientId] of fields) {
|
|
5006
|
+
if (kept.get(field) === clientId) continue;
|
|
5007
|
+
kept.set(field, clientId);
|
|
5008
|
+
changed = true;
|
|
5009
|
+
}
|
|
5010
|
+
if (kept.size > 0) target.set(url, kept);
|
|
5011
|
+
}
|
|
5012
|
+
return changed;
|
|
5013
|
+
}
|
|
4656
5014
|
var warnedLedgerFailures = /* @__PURE__ */ new Set();
|
|
4657
5015
|
function warnLedgerFailure(what, error) {
|
|
4658
5016
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -4666,6 +5024,19 @@ function warnLedgerFailure(what, error) {
|
|
|
4666
5024
|
function isRecord3(value) {
|
|
4667
5025
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4668
5026
|
}
|
|
5027
|
+
function parseOwners(value) {
|
|
5028
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5029
|
+
if (!isRecord3(value)) return owners;
|
|
5030
|
+
for (const [url, fields] of Object.entries(value)) {
|
|
5031
|
+
if (!isRecord3(fields)) continue;
|
|
5032
|
+
const byField = /* @__PURE__ */ new Map();
|
|
5033
|
+
for (const [field, clientId] of Object.entries(fields)) {
|
|
5034
|
+
if (typeof clientId === "string") byField.set(field, clientId);
|
|
5035
|
+
}
|
|
5036
|
+
if (byField.size > 0) owners.set(url, byField);
|
|
5037
|
+
}
|
|
5038
|
+
return owners;
|
|
5039
|
+
}
|
|
4669
5040
|
function stringsOf(value) {
|
|
4670
5041
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
4671
5042
|
}
|
|
@@ -4687,7 +5058,9 @@ function parseLedger(raw) {
|
|
|
4687
5058
|
if (!isRecord3(keys)) continue;
|
|
4688
5059
|
byLocation.set(location, {
|
|
4689
5060
|
servers: new Set(stringsOf(keys.servers)),
|
|
4690
|
-
idpSessions: new Set(stringsOf(keys.idpSessions))
|
|
5061
|
+
idpSessions: new Set(stringsOf(keys.idpSessions)),
|
|
5062
|
+
owners: parseOwners(keys.owners),
|
|
5063
|
+
...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
|
|
4691
5064
|
});
|
|
4692
5065
|
}
|
|
4693
5066
|
if (byLocation.size > 0) ledger.set(namespace, byLocation);
|
|
@@ -4701,7 +5074,19 @@ function serializeLedger(ledger) {
|
|
|
4701
5074
|
Object.fromEntries(
|
|
4702
5075
|
[...byLocation].map(([location, keys]) => [
|
|
4703
5076
|
location,
|
|
4704
|
-
{
|
|
5077
|
+
{
|
|
5078
|
+
servers: [...keys.servers],
|
|
5079
|
+
idpSessions: [...keys.idpSessions],
|
|
5080
|
+
...keys.owners.size > 0 && {
|
|
5081
|
+
owners: Object.fromEntries(
|
|
5082
|
+
[...keys.owners].map(([url, fields]) => [
|
|
5083
|
+
url,
|
|
5084
|
+
Object.fromEntries(fields)
|
|
5085
|
+
])
|
|
5086
|
+
)
|
|
5087
|
+
},
|
|
5088
|
+
...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
|
|
5089
|
+
}
|
|
4705
5090
|
])
|
|
4706
5091
|
)
|
|
4707
5092
|
])
|
|
@@ -4713,7 +5098,7 @@ async function writeLedger(stateFilePath, ledger) {
|
|
|
4713
5098
|
if (ledger.size === 0) await deleteStoreFile(path6);
|
|
4714
5099
|
else await writeStoreFile(path6, serializeLedger(ledger));
|
|
4715
5100
|
}
|
|
4716
|
-
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
|
|
5101
|
+
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
|
|
4717
5102
|
try {
|
|
4718
5103
|
const ledger = parseLedger(
|
|
4719
5104
|
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
@@ -4727,7 +5112,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
4727
5112
|
let keys = byLocation.get(location);
|
|
4728
5113
|
let changed = false;
|
|
4729
5114
|
if (!keys) {
|
|
4730
|
-
keys =
|
|
5115
|
+
keys = emptyKeys();
|
|
4731
5116
|
byLocation.set(location, keys);
|
|
4732
5117
|
changed = true;
|
|
4733
5118
|
}
|
|
@@ -4741,11 +5126,93 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
4741
5126
|
keys.idpSessions.add(issuer);
|
|
4742
5127
|
changed = true;
|
|
4743
5128
|
}
|
|
5129
|
+
if (mergeOwners(keys.owners, owners)) changed = true;
|
|
4744
5130
|
if (changed) await writeLedger(stateFilePath, ledger);
|
|
4745
5131
|
} catch (error) {
|
|
4746
5132
|
warnLedgerFailure("update", error);
|
|
4747
5133
|
}
|
|
4748
5134
|
}
|
|
5135
|
+
async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
|
|
5136
|
+
const ledger = parseLedger(
|
|
5137
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
5138
|
+
);
|
|
5139
|
+
const location = await secretStoreLocation(secretStore);
|
|
5140
|
+
let changed = false;
|
|
5141
|
+
for (const [namespace, byLocation] of ledger) {
|
|
5142
|
+
if (namespace === into) continue;
|
|
5143
|
+
for (const [recorded, keys] of byLocation) {
|
|
5144
|
+
if (recorded === location || keys.owners.size === 0) continue;
|
|
5145
|
+
if (keys.owedTo === into) continue;
|
|
5146
|
+
keys.owedTo = into;
|
|
5147
|
+
changed = true;
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
if (changed) await writeLedger(stateFilePath, ledger);
|
|
5151
|
+
}
|
|
5152
|
+
async function pendingCarries(stateFilePath, secretStore, current) {
|
|
5153
|
+
const ledger = parseLedger(
|
|
5154
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
5155
|
+
);
|
|
5156
|
+
const location = await secretStoreLocation(secretStore);
|
|
5157
|
+
const carries = [];
|
|
5158
|
+
for (const [namespace, byLocation] of ledger) {
|
|
5159
|
+
const owed = coveredLocations(location, byLocation).some(
|
|
5160
|
+
({ keys }) => keys.owedTo === current
|
|
5161
|
+
);
|
|
5162
|
+
if (owed) {
|
|
5163
|
+
carries.push({
|
|
5164
|
+
namespace,
|
|
5165
|
+
fields: candidateFields(location, byLocation)
|
|
5166
|
+
});
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
return carries.reverse();
|
|
5170
|
+
}
|
|
5171
|
+
async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
|
|
5172
|
+
let carried = true;
|
|
5173
|
+
for (const [url, fields] of recorded) {
|
|
5174
|
+
for (const field of fields) {
|
|
5175
|
+
const clientId = now.get(url)?.get(field);
|
|
5176
|
+
if (clientId === void 0) continue;
|
|
5177
|
+
try {
|
|
5178
|
+
const from = oauthSecretServerId(url, namespace);
|
|
5179
|
+
const to = oauthSecretServerId(url, current);
|
|
5180
|
+
const stampField = clientCredentialStampField(field);
|
|
5181
|
+
const value = await secretStoreGetStrict(secretStore, from, field);
|
|
5182
|
+
if (value === null) continue;
|
|
5183
|
+
const stamp = await secretStoreGetStrict(secretStore, from, stampField);
|
|
5184
|
+
if (stamp === null || stampedOwner(stamp, value) !== clientId) {
|
|
5185
|
+
continue;
|
|
5186
|
+
}
|
|
5187
|
+
if (await secretStoreGetStrict(secretStore, to, field) !== null) {
|
|
5188
|
+
continue;
|
|
5189
|
+
}
|
|
5190
|
+
let byLocation = ledger.get(current);
|
|
5191
|
+
if (!byLocation) {
|
|
5192
|
+
byLocation = /* @__PURE__ */ new Map();
|
|
5193
|
+
ledger.set(current, byLocation);
|
|
5194
|
+
}
|
|
5195
|
+
const target = byLocation.get(location) ?? emptyKeys();
|
|
5196
|
+
byLocation.set(location, target);
|
|
5197
|
+
const isNew = !target.servers.has(url);
|
|
5198
|
+
target.servers.add(url);
|
|
5199
|
+
const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
|
|
5200
|
+
if (mergeOwners(target.owners, owner) || isNew) {
|
|
5201
|
+
await writeLedger(stateFilePath, ledger);
|
|
5202
|
+
}
|
|
5203
|
+
await secretStore.set(to, stampField, stamp);
|
|
5204
|
+
await secretStore.set(to, field, value);
|
|
5205
|
+
} catch (error) {
|
|
5206
|
+
carried = false;
|
|
5207
|
+
warnLedgerFailure(
|
|
5208
|
+
"carry over a superseded namespace recorded in",
|
|
5209
|
+
error
|
|
5210
|
+
);
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
return carried;
|
|
5215
|
+
}
|
|
4749
5216
|
async function recordedNamespaces(stateFilePath) {
|
|
4750
5217
|
try {
|
|
4751
5218
|
const ledger = parseLedger(
|
|
@@ -4757,7 +5224,7 @@ async function recordedNamespaces(stateFilePath) {
|
|
|
4757
5224
|
return [];
|
|
4758
5225
|
}
|
|
4759
5226
|
}
|
|
4760
|
-
async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
5227
|
+
async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
|
|
4761
5228
|
let ledger;
|
|
4762
5229
|
let location;
|
|
4763
5230
|
try {
|
|
@@ -4770,8 +5237,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
4770
5237
|
return;
|
|
4771
5238
|
}
|
|
4772
5239
|
let changed = false;
|
|
4773
|
-
for (const [namespace, byLocation] of ledger) {
|
|
5240
|
+
for (const [namespace, byLocation] of [...ledger].reverse()) {
|
|
4774
5241
|
if (namespace === current) continue;
|
|
5242
|
+
if (current !== void 0 && coveredLocations(location, byLocation).some(
|
|
5243
|
+
({ keys }) => keys.owedTo === current
|
|
5244
|
+
) && !await carryOwed(
|
|
5245
|
+
stateFilePath,
|
|
5246
|
+
ledger,
|
|
5247
|
+
location,
|
|
5248
|
+
secretStore,
|
|
5249
|
+
namespace,
|
|
5250
|
+
current,
|
|
5251
|
+
candidateFields(location, byLocation),
|
|
5252
|
+
owners
|
|
5253
|
+
)) {
|
|
5254
|
+
break;
|
|
5255
|
+
}
|
|
4775
5256
|
for (const { location: recorded, keys } of coveredLocations(
|
|
4776
5257
|
location,
|
|
4777
5258
|
byLocation
|
|
@@ -4796,12 +5277,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
4796
5277
|
if (!purged || recorded !== location) continue;
|
|
4797
5278
|
byLocation.delete(recorded);
|
|
4798
5279
|
if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
|
|
4799
|
-
const keyring = byLocation.get(KEYRING_LOCATION) ??
|
|
4800
|
-
|
|
4801
|
-
idpSessions: /* @__PURE__ */ new Set()
|
|
4802
|
-
};
|
|
5280
|
+
const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
|
|
5281
|
+
addMissingOwners(keyring.owners, keys.owners);
|
|
4803
5282
|
for (const url of keys.servers) keyring.servers.add(url);
|
|
4804
5283
|
for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
|
|
5284
|
+
keyring.owedTo ??= keys.owedTo;
|
|
4805
5285
|
byLocation.set(KEYRING_LOCATION, keyring);
|
|
4806
5286
|
}
|
|
4807
5287
|
changed = true;
|
|
@@ -4912,7 +5392,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4912
5392
|
const existing = parseSecretsNamespace(raw);
|
|
4913
5393
|
if (existing !== void 0) {
|
|
4914
5394
|
if (locked) {
|
|
4915
|
-
await purgeSupersededNamespaces(
|
|
5395
|
+
await purgeSupersededNamespaces(
|
|
5396
|
+
filePath,
|
|
5397
|
+
secretStore,
|
|
5398
|
+
existing,
|
|
5399
|
+
snapshotClientOwners(parseOAuthPersistBlob(raw))
|
|
5400
|
+
);
|
|
4916
5401
|
}
|
|
4917
5402
|
return existing;
|
|
4918
5403
|
}
|
|
@@ -4932,25 +5417,33 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4932
5417
|
`Could not save OAuth state: ${filePath} predates per-state-file secret namespaces, and migrating its secret-store entries needs the file lock, which is unavailable here (see the lock warning above). Migrating without it could lose credentials if two processes migrate at once. Nothing was changed; make the lock directory writable and retry \u2014 or, if you cannot, clear this file's stored OAuth state and re-authorize (clearing does not migrate, and the fresh state file mints its namespace without the lock).`
|
|
4933
5418
|
);
|
|
4934
5419
|
}
|
|
5420
|
+
const values = await readEntrySecrets(
|
|
5421
|
+
snapshot,
|
|
5422
|
+
secretStore,
|
|
5423
|
+
void 0,
|
|
5424
|
+
await recordedNamespaces(filePath)
|
|
5425
|
+
);
|
|
5426
|
+
const owners = snapshotClientOwners(snapshot);
|
|
4935
5427
|
await recordNamespaceKeys(
|
|
4936
5428
|
filePath,
|
|
4937
5429
|
secretStore,
|
|
4938
5430
|
namespace,
|
|
4939
5431
|
Object.keys(snapshot.servers),
|
|
4940
|
-
Object.keys(snapshot.idpSessions)
|
|
5432
|
+
Object.keys(snapshot.idpSessions),
|
|
5433
|
+
writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
|
|
4941
5434
|
);
|
|
5435
|
+
await markUncarriedNamespaces(filePath, secretStore, namespace);
|
|
4942
5436
|
const touched = [];
|
|
4943
5437
|
try {
|
|
4944
|
-
const values = await readEntrySecrets(
|
|
4945
|
-
snapshot,
|
|
4946
|
-
secretStore,
|
|
4947
|
-
void 0,
|
|
4948
|
-
await recordedNamespaces(filePath)
|
|
4949
|
-
);
|
|
4950
5438
|
const copies = [
|
|
5439
|
+
// Restamped: a value read from the legacy ids predates stamps, and a
|
|
5440
|
+
// fallback's stamp may belong to a value read from elsewhere (#2734).
|
|
4951
5441
|
...[...values.servers].map(([url, secrets]) => ({
|
|
4952
5442
|
scopedId: oauthSecretServerId(url, namespace),
|
|
4953
|
-
secrets
|
|
5443
|
+
secrets: restampClientCredentials(
|
|
5444
|
+
getOwnEntry(snapshot.servers, url) ?? {},
|
|
5445
|
+
secrets
|
|
5446
|
+
)
|
|
4954
5447
|
})),
|
|
4955
5448
|
...[...values.idpSessions].map(([issuer, secrets]) => ({
|
|
4956
5449
|
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
@@ -4968,7 +5461,7 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4968
5461
|
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
4969
5462
|
throw error;
|
|
4970
5463
|
}
|
|
4971
|
-
await purgeSupersededNamespaces(filePath, secretStore, namespace);
|
|
5464
|
+
await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
|
|
4972
5465
|
const legacyIds = [
|
|
4973
5466
|
...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
|
|
4974
5467
|
...Object.keys(snapshot.idpSessions).map(
|
|
@@ -5145,7 +5638,22 @@ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secre
|
|
|
5145
5638
|
secretStore,
|
|
5146
5639
|
namespace,
|
|
5147
5640
|
Object.keys(merged.servers),
|
|
5148
|
-
Object.keys(merged.idpSessions)
|
|
5641
|
+
Object.keys(merged.idpSessions),
|
|
5642
|
+
// Only the entries this save writes: another entry's secret in
|
|
5643
|
+
// this store was written for whatever client it was written for,
|
|
5644
|
+
// and relabeling it from the file would misattribute it (#2672).
|
|
5645
|
+
writtenClientOwners(
|
|
5646
|
+
{
|
|
5647
|
+
servers: Object.fromEntries(
|
|
5648
|
+
(effective.servers ?? []).flatMap((url) => {
|
|
5649
|
+
const entry = getOwnEntry(merged.servers, url);
|
|
5650
|
+
return entry === void 0 ? [] : [[url, entry]];
|
|
5651
|
+
})
|
|
5652
|
+
),
|
|
5653
|
+
idpSessions: {}
|
|
5654
|
+
},
|
|
5655
|
+
splitSecretsOf
|
|
5656
|
+
)
|
|
5149
5657
|
);
|
|
5150
5658
|
for (const url of effective.servers ?? []) {
|
|
5151
5659
|
const serverId = oauthSecretServerId(url, namespace);
|
|
@@ -5333,7 +5841,29 @@ function fillMissingSecrets(target, source) {
|
|
|
5333
5841
|
if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
|
|
5334
5842
|
}
|
|
5335
5843
|
}
|
|
5336
|
-
|
|
5844
|
+
function writtenClientOwners(snapshot, secretsOf) {
|
|
5845
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5846
|
+
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
5847
|
+
const secrets = secretsOf(url, state);
|
|
5848
|
+
const fields = new Map(
|
|
5849
|
+
[...clientCredentialOwners(state)].filter(
|
|
5850
|
+
([field]) => Object.hasOwn(secrets, field)
|
|
5851
|
+
)
|
|
5852
|
+
);
|
|
5853
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
5854
|
+
}
|
|
5855
|
+
return owners;
|
|
5856
|
+
}
|
|
5857
|
+
var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
|
|
5858
|
+
function snapshotClientOwners(snapshot) {
|
|
5859
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5860
|
+
for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
|
|
5861
|
+
const fields = clientCredentialOwners(state);
|
|
5862
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
5863
|
+
}
|
|
5864
|
+
return owners;
|
|
5865
|
+
}
|
|
5866
|
+
async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5337
5867
|
const servers = /* @__PURE__ */ new Map();
|
|
5338
5868
|
const idpSessions = /* @__PURE__ */ new Map();
|
|
5339
5869
|
for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
|
|
@@ -5354,14 +5884,49 @@ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
|
|
|
5354
5884
|
);
|
|
5355
5885
|
}
|
|
5356
5886
|
}
|
|
5887
|
+
const owners = snapshotClientOwners(snapshot);
|
|
5888
|
+
for (const { namespace: from, fields: recorded } of carries) {
|
|
5889
|
+
const requests = [];
|
|
5890
|
+
for (const [url, fields] of recorded) {
|
|
5891
|
+
const target = servers.get(url);
|
|
5892
|
+
if (!target) continue;
|
|
5893
|
+
const wanted = [...fields].filter(
|
|
5894
|
+
(field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
|
|
5895
|
+
);
|
|
5896
|
+
if (wanted.length > 0) {
|
|
5897
|
+
requests.push({
|
|
5898
|
+
serverId: oauthSecretServerId(url, from),
|
|
5899
|
+
fields: wanted.flatMap((field) => [
|
|
5900
|
+
field,
|
|
5901
|
+
clientCredentialStampField(field)
|
|
5902
|
+
])
|
|
5903
|
+
});
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
if (requests.length === 0) continue;
|
|
5907
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
5908
|
+
for (const [url, target] of servers) {
|
|
5909
|
+
const found = values[oauthSecretServerId(url, from)] ?? {};
|
|
5910
|
+
for (const [field, clientId] of owners.get(url) ?? []) {
|
|
5911
|
+
const value = Object.hasOwn(found, field) ? found[field] : void 0;
|
|
5912
|
+
const stampField = clientCredentialStampField(field);
|
|
5913
|
+
if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
|
|
5914
|
+
continue;
|
|
5915
|
+
}
|
|
5916
|
+
setOwnEntry(target, field, value);
|
|
5917
|
+
setOwnEntry(target, stampField, found[stampField]);
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5357
5921
|
return { servers, idpSessions };
|
|
5358
5922
|
}
|
|
5359
|
-
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
|
|
5923
|
+
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5360
5924
|
const values = await readEntrySecrets(
|
|
5361
5925
|
snapshot,
|
|
5362
5926
|
secretStore,
|
|
5363
5927
|
namespace,
|
|
5364
|
-
fallbacks
|
|
5928
|
+
fallbacks,
|
|
5929
|
+
carries
|
|
5365
5930
|
);
|
|
5366
5931
|
return {
|
|
5367
5932
|
servers: Object.fromEntries(
|
|
@@ -5389,7 +5954,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
5389
5954
|
secretStore,
|
|
5390
5955
|
namespace,
|
|
5391
5956
|
Object.keys(fresh.servers),
|
|
5392
|
-
Object.keys(fresh.idpSessions)
|
|
5957
|
+
Object.keys(fresh.idpSessions),
|
|
5958
|
+
writtenClientOwners(fresh, splitSecretsOf)
|
|
5393
5959
|
);
|
|
5394
5960
|
}
|
|
5395
5961
|
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
@@ -5442,7 +6008,8 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
5442
6008
|
}
|
|
5443
6009
|
const namespace = parseSecretsNamespace(raw);
|
|
5444
6010
|
const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
|
|
5445
|
-
|
|
6011
|
+
const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
|
|
6012
|
+
return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
|
|
5446
6013
|
});
|
|
5447
6014
|
}
|
|
5448
6015
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
@@ -5682,6 +6249,8 @@ var RemoteSession = class _RemoteSession {
|
|
|
5682
6249
|
requestWaits = /* @__PURE__ */ new Map();
|
|
5683
6250
|
authProviderHandle = null;
|
|
5684
6251
|
appliedProtocolVersion;
|
|
6252
|
+
/** Each upstream `429`'s `Retry-After`, by the JSON-RPC id that drew it (#2442). */
|
|
6253
|
+
rateLimits = new RateLimitLedger();
|
|
5685
6254
|
/** A protocol version is a dated token (`2025-11-25`, `2026-07-28`, …). */
|
|
5686
6255
|
static PROTOCOL_VERSION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
5687
6256
|
constructor(sessionId) {
|
|
@@ -5893,6 +6462,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
5893
6462
|
this.pushEvent({ type: "message", data: message });
|
|
5894
6463
|
}
|
|
5895
6464
|
onFetchRequest(entry) {
|
|
6465
|
+
this.rateLimits.record(entry);
|
|
5896
6466
|
this.pushEvent({
|
|
5897
6467
|
type: "fetch_request",
|
|
5898
6468
|
data: {
|
|
@@ -5901,6 +6471,14 @@ var RemoteSession = class _RemoteSession {
|
|
|
5901
6471
|
}
|
|
5902
6472
|
});
|
|
5903
6473
|
}
|
|
6474
|
+
/**
|
|
6475
|
+
* The `Retry-After` of the upstream `429` that answered request `id` — read
|
|
6476
|
+
* by the send route, so the header rides back on the send's own error rather
|
|
6477
|
+
* than on a fetch event racing it (#2442).
|
|
6478
|
+
*/
|
|
6479
|
+
takeRetryAfter(id) {
|
|
6480
|
+
return this.rateLimits.take(id);
|
|
6481
|
+
}
|
|
5904
6482
|
onFetchResponseBody(id, responseBody) {
|
|
5905
6483
|
this.pushEvent({
|
|
5906
6484
|
type: "fetch_request_body_update",
|
|
@@ -6344,6 +6922,17 @@ var MAX_APP_DOCUMENT_CHARS = 8 * 1024 * 1024;
|
|
|
6344
6922
|
var MAX_APP_DOCUMENT_BODY_BYTES = 24 * 1024 * 1024;
|
|
6345
6923
|
var MAX_APP_CSP_CHARS = 8 * 1024;
|
|
6346
6924
|
var MAX_STORAGE_BODY_BYTES = 4 * 1024 * 1024;
|
|
6925
|
+
var MAX_PROXIED_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
6926
|
+
var BODY_FRAMING_HEADERS = /* @__PURE__ */ new Set([
|
|
6927
|
+
"content-length",
|
|
6928
|
+
"transfer-encoding",
|
|
6929
|
+
"content-encoding"
|
|
6930
|
+
]);
|
|
6931
|
+
function withoutBodyFraming(headers) {
|
|
6932
|
+
return Object.fromEntries(
|
|
6933
|
+
Object.entries(headers).filter(([k]) => !BODY_FRAMING_HEADERS.has(k))
|
|
6934
|
+
);
|
|
6935
|
+
}
|
|
6347
6936
|
function isHeaderSafeValue(value) {
|
|
6348
6937
|
return !/[^\t\x20-\x7e]/.test(value);
|
|
6349
6938
|
}
|
|
@@ -6799,8 +7388,10 @@ function createRemoteApp(options) {
|
|
|
6799
7388
|
});
|
|
6800
7389
|
}
|
|
6801
7390
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6802
|
-
if (err instanceof
|
|
7391
|
+
if (err instanceof SdkHttpError2) {
|
|
6803
7392
|
const text = err.data.text;
|
|
7393
|
+
const sentId = "id" in message ? message.id : void 0;
|
|
7394
|
+
const retryAfter = isRateLimitError(err) && (typeof sentId === "string" || typeof sentId === "number") ? session.takeRetryAfter(sentId) : void 0;
|
|
6804
7395
|
return c.json({
|
|
6805
7396
|
ok: false,
|
|
6806
7397
|
kind: "transport_error",
|
|
@@ -6811,7 +7402,8 @@ function createRemoteApp(options) {
|
|
|
6811
7402
|
...err.statusText !== void 0 && {
|
|
6812
7403
|
statusText: err.statusText
|
|
6813
7404
|
},
|
|
6814
|
-
...typeof text === "string" && { text }
|
|
7405
|
+
...typeof text === "string" && { text },
|
|
7406
|
+
...retryAfter !== void 0 && { retryAfter }
|
|
6815
7407
|
}
|
|
6816
7408
|
});
|
|
6817
7409
|
}
|
|
@@ -6944,7 +7536,28 @@ function createRemoteApp(options) {
|
|
|
6944
7536
|
const isStream = contentType?.includes("text/event-stream") || contentType?.includes("application/x-ndjson");
|
|
6945
7537
|
let resBody;
|
|
6946
7538
|
if (!isStream && res.body) {
|
|
6947
|
-
|
|
7539
|
+
const refuseOversize = () => res.ok ? c.json(
|
|
7540
|
+
{
|
|
7541
|
+
error: `response from ${safeUrl} exceeds the ${MAX_PROXIED_RESPONSE_BYTES}-byte limit for a proxied response`
|
|
7542
|
+
},
|
|
7543
|
+
502
|
|
7544
|
+
) : c.json({
|
|
7545
|
+
ok: res.ok,
|
|
7546
|
+
status: res.status,
|
|
7547
|
+
statusText: res.statusText,
|
|
7548
|
+
headers: withoutBodyFraming(resHeaders)
|
|
7549
|
+
});
|
|
7550
|
+
if (declaresOversizeBody(
|
|
7551
|
+
res.headers.get("content-length"),
|
|
7552
|
+
MAX_PROXIED_RESPONSE_BYTES
|
|
7553
|
+
)) {
|
|
7554
|
+
void res.body.cancel().catch(() => {
|
|
7555
|
+
});
|
|
7556
|
+
return refuseOversize();
|
|
7557
|
+
}
|
|
7558
|
+
const read = await readCappedText(res.body, MAX_PROXIED_RESPONSE_BYTES);
|
|
7559
|
+
if (read.kind === "overflow") return refuseOversize();
|
|
7560
|
+
resBody = read.text;
|
|
6948
7561
|
} else if (isStream) {
|
|
6949
7562
|
void res.body?.cancel().catch(() => {
|
|
6950
7563
|
});
|
|
@@ -7757,7 +8370,7 @@ function createRemoteApp(options) {
|
|
|
7757
8370
|
type,
|
|
7758
8371
|
process.platform,
|
|
7759
8372
|
homedir(),
|
|
7760
|
-
(path6) => existsSync3(path6) ?
|
|
8373
|
+
(path6) => existsSync3(path6) ? readFileSync7(path6, "utf-8") : null
|
|
7761
8374
|
);
|
|
7762
8375
|
if (!result) {
|
|
7763
8376
|
return c.json(
|
|
@@ -8173,6 +8786,30 @@ function handleNodeHealthRequest(req, res) {
|
|
|
8173
8786
|
return true;
|
|
8174
8787
|
}
|
|
8175
8788
|
|
|
8789
|
+
// server/node-request-bridge.ts
|
|
8790
|
+
function fetchUrlForNodeRequest(headers, pathname, scheme) {
|
|
8791
|
+
const authority = headers.host ?? headers[":authority"];
|
|
8792
|
+
return `${scheme}://${authority}${pathname}`;
|
|
8793
|
+
}
|
|
8794
|
+
function fetchHeadersFromNode(headers) {
|
|
8795
|
+
const out = new Headers();
|
|
8796
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
8797
|
+
if (!value || key.startsWith(":")) continue;
|
|
8798
|
+
out.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
8799
|
+
}
|
|
8800
|
+
return out;
|
|
8801
|
+
}
|
|
8802
|
+
var CONNECTION_SPECIFIC_HEADERS = /* @__PURE__ */ new Set([
|
|
8803
|
+
"connection",
|
|
8804
|
+
"keep-alive",
|
|
8805
|
+
"proxy-connection",
|
|
8806
|
+
"transfer-encoding",
|
|
8807
|
+
"upgrade"
|
|
8808
|
+
]);
|
|
8809
|
+
function isResponseHeaderAllowed(name, httpVersionMajor) {
|
|
8810
|
+
return httpVersionMajor < 2 || !CONNECTION_SPECIFIC_HEADERS.has(name.toLowerCase());
|
|
8811
|
+
}
|
|
8812
|
+
|
|
8176
8813
|
// server/vite-hono-plugin.ts
|
|
8177
8814
|
function honoMiddlewarePlugin(config) {
|
|
8178
8815
|
let resolvedAuthToken = "";
|
|
@@ -8280,18 +8917,12 @@ function honoMiddlewarePlugin(config) {
|
|
|
8280
8917
|
if (!pathname.startsWith("/api")) {
|
|
8281
8918
|
return next();
|
|
8282
8919
|
}
|
|
8283
|
-
const url =
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
(
|
|
8287
|
-
if (value) {
|
|
8288
|
-
headers.set(
|
|
8289
|
-
key,
|
|
8290
|
-
Array.isArray(value) ? value.join(", ") : value
|
|
8291
|
-
);
|
|
8292
|
-
}
|
|
8293
|
-
}
|
|
8920
|
+
const url = fetchUrlForNodeRequest(
|
|
8921
|
+
req.headers,
|
|
8922
|
+
pathname,
|
|
8923
|
+
webScheme(config.tls)
|
|
8294
8924
|
);
|
|
8925
|
+
const headers = fetchHeadersFromNode(req.headers);
|
|
8295
8926
|
const init = { method: req.method, headers };
|
|
8296
8927
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
8297
8928
|
const chunks = [];
|
|
@@ -8308,13 +8939,17 @@ function honoMiddlewarePlugin(config) {
|
|
|
8308
8939
|
const response = await honoApp.fetch(new Request(url, init));
|
|
8309
8940
|
res.statusCode = response.status;
|
|
8310
8941
|
response.headers.forEach((value, key) => {
|
|
8311
|
-
|
|
8942
|
+
if (isResponseHeaderAllowed(key, req.httpVersionMajor)) {
|
|
8943
|
+
res.setHeader(key, value);
|
|
8944
|
+
}
|
|
8312
8945
|
});
|
|
8313
8946
|
const isSSE = response.headers.get("content-type")?.includes("text/event-stream");
|
|
8314
8947
|
if (isSSE) {
|
|
8315
8948
|
res.setHeader("X-Accel-Buffering", "no");
|
|
8316
8949
|
res.setHeader("Cache-Control", "no-cache");
|
|
8317
|
-
|
|
8950
|
+
if (isResponseHeaderAllowed("connection", req.httpVersionMajor)) {
|
|
8951
|
+
res.setHeader("Connection", "keep-alive");
|
|
8952
|
+
}
|
|
8318
8953
|
}
|
|
8319
8954
|
if (response.body) {
|
|
8320
8955
|
res.flushHeaders?.();
|
|
@@ -8667,6 +9302,10 @@ async function startViteDevServer(config) {
|
|
|
8667
9302
|
// `config.port`, so a drifted bind would 403 every connect and CSP-block
|
|
8668
9303
|
// the MCP Apps iframe while the banner advertises the unusable port.
|
|
8669
9304
|
strictPort: true,
|
|
9305
|
+
// The same TLS pair the prod listener uses (#2446), matching
|
|
9306
|
+
// `vite.config.ts`. Vite serves it over HTTP/2 with an HTTP/1.1
|
|
9307
|
+
// fallback; the Hono plugin's /api bridge handles both.
|
|
9308
|
+
https: config.tls,
|
|
8670
9309
|
// Allow Vite to serve source files from the repo root (core/ lives
|
|
8671
9310
|
// outside clients/web), matching `vite.config.ts`'s `server.fs.allow`.
|
|
8672
9311
|
fs: {
|
|
@@ -8685,10 +9324,11 @@ async function startViteDevServer(config) {
|
|
|
8685
9324
|
}
|
|
8686
9325
|
|
|
8687
9326
|
// server/server.ts
|
|
8688
|
-
import { readFileSync as
|
|
9327
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
8689
9328
|
import { join as join8, dirname as dirname8 } from "path";
|
|
8690
9329
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
8691
9330
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
9331
|
+
import { createServer as createHttpsServer } from "https";
|
|
8692
9332
|
import { serve } from "@hono/node-server";
|
|
8693
9333
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
8694
9334
|
import { Hono as Hono2 } from "hono";
|
|
@@ -8735,12 +9375,20 @@ async function startHonoServer(config) {
|
|
|
8735
9375
|
});
|
|
8736
9376
|
const app = new Hono2();
|
|
8737
9377
|
app.get(HEALTH_PATH, (c) => healthResponse(c.req.method));
|
|
9378
|
+
app.use(
|
|
9379
|
+
"*",
|
|
9380
|
+
hostAllowListMiddleware({
|
|
9381
|
+
bindHost: config.hostname,
|
|
9382
|
+
allowedOrigins: config.allowedOrigins,
|
|
9383
|
+
extraHosts: config.allowedHosts
|
|
9384
|
+
})
|
|
9385
|
+
);
|
|
8738
9386
|
app.use("/api/*", async (c) => {
|
|
8739
9387
|
return apiApp.fetch(c.req.raw);
|
|
8740
9388
|
});
|
|
8741
9389
|
const serveIndexHtml = (c) => {
|
|
8742
9390
|
const indexPath = join8(rootPath, "index.html");
|
|
8743
|
-
const html =
|
|
9391
|
+
const html = readFileSync8(indexPath, "utf-8");
|
|
8744
9392
|
c.header("Cache-Control", "no-store");
|
|
8745
9393
|
return c.html(injectAuthToken(html, resolvedAuthToken));
|
|
8746
9394
|
};
|
|
@@ -8768,7 +9416,10 @@ async function startHonoServer(config) {
|
|
|
8768
9416
|
{
|
|
8769
9417
|
fetch: app.fetch,
|
|
8770
9418
|
port: config.port,
|
|
8771
|
-
hostname: config.hostname
|
|
9419
|
+
hostname: config.hostname,
|
|
9420
|
+
// https only when both TLS paths were set (#2446); otherwise
|
|
9421
|
+
// @hono/node-server's default plain `http.createServer`.
|
|
9422
|
+
...config.tls ? { createServer: createHttpsServer, serverOptions: config.tls } : {}
|
|
8772
9423
|
},
|
|
8773
9424
|
(info) => {
|
|
8774
9425
|
const sandboxUrl = sandboxController.getUrl();
|
|
@@ -8786,9 +9437,7 @@ async function startHonoServer(config) {
|
|
|
8786
9437
|
);
|
|
8787
9438
|
httpServer.on("error", (err) => {
|
|
8788
9439
|
if (err.message.includes("EADDRINUSE")) {
|
|
8789
|
-
console.error(
|
|
8790
|
-
`MCP Inspector PORT IS IN USE at http://${formatHostForUrl(config.hostname)}:${config.port}`
|
|
8791
|
-
);
|
|
9440
|
+
console.error(portInUseMessage(config));
|
|
8792
9441
|
process.exit(1);
|
|
8793
9442
|
} else {
|
|
8794
9443
|
throw err;
|