@modelcontextprotocol/inspector 2.0.0 → 2.2.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 +67 -10
- package/clients/cli/build/index.js +436 -72
- package/clients/tui/build/index.js +2846 -669
- package/clients/web/build/index.js +135 -13
- package/clients/web/dist/assets/index-BrCIxo3Q.js +242 -0
- package/clients/web/dist/assets/prism-light-CWdjx3zH.js +6 -0
- package/clients/web/dist/index.html +1 -1
- package/clients/web/static/sandbox_proxy.html +237 -0
- package/package.json +15 -16
- package/clients/web/dist/assets/index-wIMJi5MN.js +0 -300
- package/clients/web/dist/assets/prism-light-CVa8c8_p.js +0 -6
|
@@ -453,9 +453,13 @@ function loadMcpServersConfig(configPath, writable) {
|
|
|
453
453
|
return { ...config, mcpServers: normalizedServers };
|
|
454
454
|
} catch (error) {
|
|
455
455
|
if (error instanceof Error) {
|
|
456
|
-
throw new Error(`Error loading configuration: ${error.message}
|
|
456
|
+
throw new Error(`Error loading configuration: ${error.message}`, {
|
|
457
|
+
cause: error
|
|
458
|
+
});
|
|
457
459
|
}
|
|
458
|
-
throw new Error("Error loading configuration: Unknown error"
|
|
460
|
+
throw new Error("Error loading configuration: Unknown error", {
|
|
461
|
+
cause: error
|
|
462
|
+
});
|
|
459
463
|
}
|
|
460
464
|
}
|
|
461
465
|
function loadServerFromConfig(configPath, serverName, writable) {
|
|
@@ -1507,6 +1511,9 @@ function createTransportNode(config, options = {}) {
|
|
|
1507
1511
|
}
|
|
1508
1512
|
}
|
|
1509
1513
|
|
|
1514
|
+
// ../../core/json/xMcpHeader.ts
|
|
1515
|
+
var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
|
|
1516
|
+
|
|
1510
1517
|
// ../../core/mcp/import/clientConfig.ts
|
|
1511
1518
|
function parseJsonObject(raw) {
|
|
1512
1519
|
let data;
|
|
@@ -1514,7 +1521,7 @@ function parseJsonObject(raw) {
|
|
|
1514
1521
|
data = JSON.parse(raw);
|
|
1515
1522
|
} catch (err) {
|
|
1516
1523
|
const detail = err instanceof Error ? err.message : String(err);
|
|
1517
|
-
throw new Error(`Invalid JSON: ${detail}
|
|
1524
|
+
throw new Error(`Invalid JSON: ${detail}`, { cause: err });
|
|
1518
1525
|
}
|
|
1519
1526
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
1520
1527
|
throw new Error("Expected a JSON object at the top level");
|
|
@@ -1673,6 +1680,9 @@ var RemoteSession = class _RemoteSession {
|
|
|
1673
1680
|
static AUTH_HTTP_ECHO_SUPPRESS_MS = 3e4;
|
|
1674
1681
|
requestWaits = /* @__PURE__ */ new Map();
|
|
1675
1682
|
authProviderHandle = null;
|
|
1683
|
+
appliedProtocolVersion;
|
|
1684
|
+
/** A protocol version is a dated token (`2025-11-25`, `2026-07-28`, …). */
|
|
1685
|
+
static PROTOCOL_VERSION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
1676
1686
|
constructor(sessionId) {
|
|
1677
1687
|
this.sessionId = sessionId;
|
|
1678
1688
|
}
|
|
@@ -1688,6 +1698,27 @@ var RemoteSession = class _RemoteSession {
|
|
|
1688
1698
|
setTransport(transport) {
|
|
1689
1699
|
this.transport = transport;
|
|
1690
1700
|
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Apply the client's negotiated protocol version to the upstream transport
|
|
1703
|
+
* (#1935). The browser's SDK Client owns the `initialize` handshake, so the
|
|
1704
|
+
* `setProtocolVersion` call it makes lands on the *remote* transport; without
|
|
1705
|
+
* this the backend's real HTTP transport never learns the version and drops
|
|
1706
|
+
* `Mcp-Protocol-Version` from every subsequent request.
|
|
1707
|
+
*
|
|
1708
|
+
* Ignores anything that isn't a plausible version token so a client can't
|
|
1709
|
+
* push arbitrary bytes into an upstream header, and re-applies only on
|
|
1710
|
+
* change (the value rides every `/api/mcp/send`). The value arrives from an
|
|
1711
|
+
* unvalidated JSON body, so the type is checked rather than assumed —
|
|
1712
|
+
* `RegExp.test` would otherwise coerce a `123` or `true` into a "matching"
|
|
1713
|
+
* string and hand the wrong type to the transport.
|
|
1714
|
+
*/
|
|
1715
|
+
applyProtocolVersion(version) {
|
|
1716
|
+
if (typeof version !== "string" || version === this.appliedProtocolVersion || !_RemoteSession.PROTOCOL_VERSION_PATTERN.test(version)) {
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
this.appliedProtocolVersion = version;
|
|
1720
|
+
this.transport.setProtocolVersion?.(version);
|
|
1721
|
+
}
|
|
1691
1722
|
setEventConsumer(consumer) {
|
|
1692
1723
|
this.eventConsumer = consumer;
|
|
1693
1724
|
while (this.eventQueue.length > 0) {
|
|
@@ -1946,8 +1977,42 @@ function createRemoteAuthProvider(initialAuthState) {
|
|
|
1946
1977
|
}
|
|
1947
1978
|
|
|
1948
1979
|
// ../../core/auth/node/secret-store.ts
|
|
1949
|
-
import { AsyncEntry, findCredentialsAsync } from "@napi-rs/keyring";
|
|
1950
1980
|
var SERVICE_NAME = "mcp-inspector";
|
|
1981
|
+
var keyringLoad;
|
|
1982
|
+
var KeyringModuleShapeError = class extends Error {
|
|
1983
|
+
constructor(detail, options) {
|
|
1984
|
+
super(`@napi-rs/keyring loaded but ${detail}`, options);
|
|
1985
|
+
this.name = "KeyringModuleShapeError";
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
var checkKeyringShape = (mod) => {
|
|
1989
|
+
try {
|
|
1990
|
+
if (typeof mod.AsyncEntry === "function" && typeof mod.findCredentialsAsync === "function") {
|
|
1991
|
+
return { ok: true, mod };
|
|
1992
|
+
}
|
|
1993
|
+
return {
|
|
1994
|
+
ok: false,
|
|
1995
|
+
err: new KeyringModuleShapeError(
|
|
1996
|
+
"did not expose AsyncEntry / findCredentialsAsync"
|
|
1997
|
+
)
|
|
1998
|
+
};
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
return {
|
|
2001
|
+
ok: false,
|
|
2002
|
+
err: new KeyringModuleShapeError(
|
|
2003
|
+
`its exports could not be read: ${err instanceof Error ? err.message : String(err)}`,
|
|
2004
|
+
{ cause: err }
|
|
2005
|
+
)
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
var loadKeyring = () => {
|
|
2010
|
+
keyringLoad ??= import("@napi-rs/keyring").then(
|
|
2011
|
+
checkKeyringShape,
|
|
2012
|
+
(err) => ({ ok: false, err })
|
|
2013
|
+
);
|
|
2014
|
+
return keyringLoad;
|
|
2015
|
+
};
|
|
1951
2016
|
function parseAccount(account) {
|
|
1952
2017
|
const idx = account.indexOf(":");
|
|
1953
2018
|
if (idx <= 0 || idx === account.length - 1) return null;
|
|
@@ -1959,16 +2024,31 @@ function parseAccount(account) {
|
|
|
1959
2024
|
var buildAccount = (serverId, field) => `${serverId}:${field}`;
|
|
1960
2025
|
var KeychainUnavailableError = class extends Error {
|
|
1961
2026
|
constructor(cause) {
|
|
2027
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1962
2028
|
super(
|
|
1963
|
-
`OS keychain is not available.
|
|
2029
|
+
`OS keychain is not available. ${hintFor(cause, message)} Underlying error: ${message}`
|
|
1964
2030
|
);
|
|
1965
2031
|
this.name = "KeychainUnavailableError";
|
|
1966
2032
|
}
|
|
1967
2033
|
};
|
|
2034
|
+
var hintFor = (cause, message) => {
|
|
2035
|
+
if (cause instanceof KeyringModuleShapeError) {
|
|
2036
|
+
return `The @napi-rs/keyring package loaded but does not expose the API this build expects \u2014 most likely a version or packaging mismatch; reinstall the Inspector, and report this if it persists.`;
|
|
2037
|
+
}
|
|
2038
|
+
if (message.includes("Cannot find native binding")) {
|
|
2039
|
+
return `The @napi-rs/keyring platform package for this OS is missing or unavailable \u2014 reinstall the Inspector (for npx, clear the npx cache under your npm cache directory first).`;
|
|
2040
|
+
}
|
|
2041
|
+
return `On Linux, install libsecret / gnome-keyring.`;
|
|
2042
|
+
};
|
|
1968
2043
|
var KeyringSecretStore = class {
|
|
1969
2044
|
async get(serverId, field) {
|
|
1970
|
-
const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
|
|
1971
2045
|
try {
|
|
2046
|
+
const keyring = await loadKeyring();
|
|
2047
|
+
if (!keyring.ok) return null;
|
|
2048
|
+
const entry = new keyring.mod.AsyncEntry(
|
|
2049
|
+
SERVICE_NAME,
|
|
2050
|
+
buildAccount(serverId, field)
|
|
2051
|
+
);
|
|
1972
2052
|
const v = await entry.getPassword();
|
|
1973
2053
|
return v ?? null;
|
|
1974
2054
|
} catch {
|
|
@@ -1976,16 +2056,27 @@ var KeyringSecretStore = class {
|
|
|
1976
2056
|
}
|
|
1977
2057
|
}
|
|
1978
2058
|
async set(serverId, field, value) {
|
|
1979
|
-
const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
|
|
1980
2059
|
try {
|
|
2060
|
+
const keyring = await loadKeyring();
|
|
2061
|
+
if (!keyring.ok) throw new KeychainUnavailableError(keyring.err);
|
|
2062
|
+
const entry = new keyring.mod.AsyncEntry(
|
|
2063
|
+
SERVICE_NAME,
|
|
2064
|
+
buildAccount(serverId, field)
|
|
2065
|
+
);
|
|
1981
2066
|
await entry.setPassword(value);
|
|
1982
2067
|
} catch (err) {
|
|
2068
|
+
if (err instanceof KeychainUnavailableError) throw err;
|
|
1983
2069
|
throw new KeychainUnavailableError(err);
|
|
1984
2070
|
}
|
|
1985
2071
|
}
|
|
1986
2072
|
async delete(serverId, field) {
|
|
1987
|
-
const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
|
|
1988
2073
|
try {
|
|
2074
|
+
const keyring = await loadKeyring();
|
|
2075
|
+
if (!keyring.ok) return;
|
|
2076
|
+
const entry = new keyring.mod.AsyncEntry(
|
|
2077
|
+
SERVICE_NAME,
|
|
2078
|
+
buildAccount(serverId, field)
|
|
2079
|
+
);
|
|
1989
2080
|
await entry.deleteCredential();
|
|
1990
2081
|
} catch {
|
|
1991
2082
|
}
|
|
@@ -1993,7 +2084,9 @@ var KeyringSecretStore = class {
|
|
|
1993
2084
|
async deleteAllForServer(serverId) {
|
|
1994
2085
|
let creds;
|
|
1995
2086
|
try {
|
|
1996
|
-
|
|
2087
|
+
const keyring = await loadKeyring();
|
|
2088
|
+
if (!keyring.ok) return;
|
|
2089
|
+
creds = await keyring.mod.findCredentialsAsync(SERVICE_NAME);
|
|
1997
2090
|
} catch {
|
|
1998
2091
|
return;
|
|
1999
2092
|
}
|
|
@@ -2225,6 +2318,7 @@ async function deleteClientConfigStore(filePath, secretStore) {
|
|
|
2225
2318
|
|
|
2226
2319
|
// ../../core/mcp/remote/node/server.ts
|
|
2227
2320
|
import { ZodError as ZodError2 } from "zod";
|
|
2321
|
+
var SSE_PRIMING_COMMENT = ":\n\n";
|
|
2228
2322
|
function createOriginMiddleware(allowedOrigins) {
|
|
2229
2323
|
return async (c, next) => {
|
|
2230
2324
|
if (!allowedOrigins || allowedOrigins.length === 0) {
|
|
@@ -2347,6 +2441,16 @@ function requestIdForSendWait(message) {
|
|
|
2347
2441
|
}
|
|
2348
2442
|
return void 0;
|
|
2349
2443
|
}
|
|
2444
|
+
function mcpParamHeadersOnly(headers) {
|
|
2445
|
+
if (headers === null || typeof headers !== "object") return void 0;
|
|
2446
|
+
const out = {};
|
|
2447
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2448
|
+
if (typeof value === "string" && key.toLowerCase().startsWith(MCP_PARAM_HEADER_PREFIX.toLowerCase())) {
|
|
2449
|
+
out[key] = value;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2453
|
+
}
|
|
2350
2454
|
function createRemoteApp(options) {
|
|
2351
2455
|
const dangerouslyOmitAuth = !!options.dangerouslyOmitAuth;
|
|
2352
2456
|
const authToken = dangerouslyOmitAuth ? "" : options.authToken || process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] || randomBytes(32).toString("hex");
|
|
@@ -2554,7 +2658,7 @@ function createRemoteApp(options) {
|
|
|
2554
2658
|
} catch {
|
|
2555
2659
|
return c.json({ error: "Invalid JSON body" }, 400);
|
|
2556
2660
|
}
|
|
2557
|
-
const { sessionId, message, relatedRequestId } = body;
|
|
2661
|
+
const { sessionId, message, relatedRequestId, headers, protocolVersion } = body;
|
|
2558
2662
|
if (!sessionId || !message) {
|
|
2559
2663
|
return c.json({ error: "Missing sessionId or message" }, 400);
|
|
2560
2664
|
}
|
|
@@ -2566,6 +2670,7 @@ function createRemoteApp(options) {
|
|
|
2566
2670
|
const errorMsg = session.getTransportError() || "Transport closed";
|
|
2567
2671
|
return c.json({ ok: false, kind: "transport_error", error: errorMsg });
|
|
2568
2672
|
}
|
|
2673
|
+
session.applyProtocolVersion(protocolVersion);
|
|
2569
2674
|
session.beginSend();
|
|
2570
2675
|
const requestId = requestIdForSendWait(message);
|
|
2571
2676
|
let responseWait;
|
|
@@ -2575,8 +2680,10 @@ function createRemoteApp(options) {
|
|
|
2575
2680
|
});
|
|
2576
2681
|
}
|
|
2577
2682
|
try {
|
|
2683
|
+
const paramHeaders = mcpParamHeadersOnly(headers);
|
|
2578
2684
|
await session.transport.send(message, {
|
|
2579
|
-
relatedRequestId
|
|
2685
|
+
relatedRequestId,
|
|
2686
|
+
...paramHeaders && { headers: paramHeaders }
|
|
2580
2687
|
});
|
|
2581
2688
|
if (responseWait) {
|
|
2582
2689
|
await responseWait;
|
|
@@ -2623,6 +2730,7 @@ function createRemoteApp(options) {
|
|
|
2623
2730
|
sessions.delete(sessionId);
|
|
2624
2731
|
return;
|
|
2625
2732
|
}
|
|
2733
|
+
await stream.write(SSE_PRIMING_COMMENT);
|
|
2626
2734
|
stream.onAbort(() => {
|
|
2627
2735
|
const shouldCleanup = session.clearEventConsumer();
|
|
2628
2736
|
stream.close();
|
|
@@ -3593,6 +3701,7 @@ function createRemoteApp(options) {
|
|
|
3593
3701
|
serverEventSubscribers.add(send);
|
|
3594
3702
|
ensureWatcher();
|
|
3595
3703
|
}
|
|
3704
|
+
await stream.write(SSE_PRIMING_COMMENT);
|
|
3596
3705
|
stream.onAbort(() => {
|
|
3597
3706
|
serverEventSubscribers.delete(send);
|
|
3598
3707
|
void maybeStopWatcher();
|
|
@@ -3833,7 +3942,9 @@ var NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE = [
|
|
|
3833
3942
|
"which",
|
|
3834
3943
|
// `@napi-rs/keyring` is loaded only inside
|
|
3835
3944
|
// `core/auth/node/secret-store.ts` from the Hono `/api/servers`
|
|
3836
|
-
// handlers
|
|
3945
|
+
// handlers — and lazily even there, via a cached dynamic import, so
|
|
3946
|
+
// an unsupported platform degrades instead of crashing at startup
|
|
3947
|
+
// (#1905). It's a native-binding package (no browser code path) so
|
|
3837
3948
|
// excluding it keeps Vite's dep scanner from chasing into the
|
|
3838
3949
|
// platform-specific binaries during dev startup.
|
|
3839
3950
|
"@napi-rs/keyring"
|
|
@@ -3928,9 +4039,20 @@ function vitestSharedPaths(clientDir) {
|
|
|
3928
4039
|
find: /^@napi-rs\/keyring$/,
|
|
3929
4040
|
replacement: path2.resolve(dirname8, "node_modules/@napi-rs/keyring")
|
|
3930
4041
|
},
|
|
4042
|
+
// `express` and `yaml` resolve from the **repo root**, unlike every pin
|
|
4043
|
+
// above. Both are reached only through `test-servers/src` — express by the
|
|
4044
|
+
// http/oauth servers, yaml by `load-config.ts` — which is root-owned code
|
|
4045
|
+
// with no manifest of its own, so the root is where they are declared and
|
|
4046
|
+
// the only place a client's resolution chain is guaranteed to find them.
|
|
4047
|
+
// Pointing these at `<client>/node_modules` is what broke when the MCP
|
|
4048
|
+
// packages moved to the root (#1970): express was never declared by a client
|
|
4049
|
+
// at all, it arrived in `clients/cli` as a peer of `express-rate-limit`
|
|
4050
|
+
// under `@modelcontextprotocol/server-legacy`, so removing that manifest
|
|
4051
|
+
// entry took express with it and every cli test that spawns a test server
|
|
4052
|
+
// failed to resolve it.
|
|
3931
4053
|
{
|
|
3932
4054
|
find: /^express$/,
|
|
3933
|
-
replacement: path2.resolve(
|
|
4055
|
+
replacement: path2.resolve(repoRoot, "node_modules/express")
|
|
3934
4056
|
},
|
|
3935
4057
|
{
|
|
3936
4058
|
find: /^yaml$/,
|