@modelcontextprotocol/inspector 2.1.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.
@@ -1680,6 +1680,9 @@ var RemoteSession = class _RemoteSession {
1680
1680
  static AUTH_HTTP_ECHO_SUPPRESS_MS = 3e4;
1681
1681
  requestWaits = /* @__PURE__ */ new Map();
1682
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}$/;
1683
1686
  constructor(sessionId) {
1684
1687
  this.sessionId = sessionId;
1685
1688
  }
@@ -1695,6 +1698,27 @@ var RemoteSession = class _RemoteSession {
1695
1698
  setTransport(transport) {
1696
1699
  this.transport = transport;
1697
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
+ }
1698
1722
  setEventConsumer(consumer) {
1699
1723
  this.eventConsumer = consumer;
1700
1724
  while (this.eventQueue.length > 0) {
@@ -1953,8 +1977,42 @@ function createRemoteAuthProvider(initialAuthState) {
1953
1977
  }
1954
1978
 
1955
1979
  // ../../core/auth/node/secret-store.ts
1956
- import { AsyncEntry, findCredentialsAsync } from "@napi-rs/keyring";
1957
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
+ };
1958
2016
  function parseAccount(account) {
1959
2017
  const idx = account.indexOf(":");
1960
2018
  if (idx <= 0 || idx === account.length - 1) return null;
@@ -1966,16 +2024,31 @@ function parseAccount(account) {
1966
2024
  var buildAccount = (serverId, field) => `${serverId}:${field}`;
1967
2025
  var KeychainUnavailableError = class extends Error {
1968
2026
  constructor(cause) {
2027
+ const message = cause instanceof Error ? cause.message : String(cause);
1969
2028
  super(
1970
- `OS keychain is not available. On Linux, install libsecret / gnome-keyring. Underlying error: ${cause instanceof Error ? cause.message : String(cause)}`
2029
+ `OS keychain is not available. ${hintFor(cause, message)} Underlying error: ${message}`
1971
2030
  );
1972
2031
  this.name = "KeychainUnavailableError";
1973
2032
  }
1974
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
+ };
1975
2043
  var KeyringSecretStore = class {
1976
2044
  async get(serverId, field) {
1977
- const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
1978
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
+ );
1979
2052
  const v = await entry.getPassword();
1980
2053
  return v ?? null;
1981
2054
  } catch {
@@ -1983,16 +2056,27 @@ var KeyringSecretStore = class {
1983
2056
  }
1984
2057
  }
1985
2058
  async set(serverId, field, value) {
1986
- const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
1987
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
+ );
1988
2066
  await entry.setPassword(value);
1989
2067
  } catch (err) {
2068
+ if (err instanceof KeychainUnavailableError) throw err;
1990
2069
  throw new KeychainUnavailableError(err);
1991
2070
  }
1992
2071
  }
1993
2072
  async delete(serverId, field) {
1994
- const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
1995
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
+ );
1996
2080
  await entry.deleteCredential();
1997
2081
  } catch {
1998
2082
  }
@@ -2000,7 +2084,9 @@ var KeyringSecretStore = class {
2000
2084
  async deleteAllForServer(serverId) {
2001
2085
  let creds;
2002
2086
  try {
2003
- creds = await findCredentialsAsync(SERVICE_NAME);
2087
+ const keyring = await loadKeyring();
2088
+ if (!keyring.ok) return;
2089
+ creds = await keyring.mod.findCredentialsAsync(SERVICE_NAME);
2004
2090
  } catch {
2005
2091
  return;
2006
2092
  }
@@ -2232,6 +2318,7 @@ async function deleteClientConfigStore(filePath, secretStore) {
2232
2318
 
2233
2319
  // ../../core/mcp/remote/node/server.ts
2234
2320
  import { ZodError as ZodError2 } from "zod";
2321
+ var SSE_PRIMING_COMMENT = ":\n\n";
2235
2322
  function createOriginMiddleware(allowedOrigins) {
2236
2323
  return async (c, next) => {
2237
2324
  if (!allowedOrigins || allowedOrigins.length === 0) {
@@ -2571,7 +2658,7 @@ function createRemoteApp(options) {
2571
2658
  } catch {
2572
2659
  return c.json({ error: "Invalid JSON body" }, 400);
2573
2660
  }
2574
- const { sessionId, message, relatedRequestId, headers } = body;
2661
+ const { sessionId, message, relatedRequestId, headers, protocolVersion } = body;
2575
2662
  if (!sessionId || !message) {
2576
2663
  return c.json({ error: "Missing sessionId or message" }, 400);
2577
2664
  }
@@ -2583,6 +2670,7 @@ function createRemoteApp(options) {
2583
2670
  const errorMsg = session.getTransportError() || "Transport closed";
2584
2671
  return c.json({ ok: false, kind: "transport_error", error: errorMsg });
2585
2672
  }
2673
+ session.applyProtocolVersion(protocolVersion);
2586
2674
  session.beginSend();
2587
2675
  const requestId = requestIdForSendWait(message);
2588
2676
  let responseWait;
@@ -2642,6 +2730,7 @@ function createRemoteApp(options) {
2642
2730
  sessions.delete(sessionId);
2643
2731
  return;
2644
2732
  }
2733
+ await stream.write(SSE_PRIMING_COMMENT);
2645
2734
  stream.onAbort(() => {
2646
2735
  const shouldCleanup = session.clearEventConsumer();
2647
2736
  stream.close();
@@ -3612,6 +3701,7 @@ function createRemoteApp(options) {
3612
3701
  serverEventSubscribers.add(send);
3613
3702
  ensureWatcher();
3614
3703
  }
3704
+ await stream.write(SSE_PRIMING_COMMENT);
3615
3705
  stream.onAbort(() => {
3616
3706
  serverEventSubscribers.delete(send);
3617
3707
  void maybeStopWatcher();
@@ -3852,7 +3942,9 @@ var NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE = [
3852
3942
  "which",
3853
3943
  // `@napi-rs/keyring` is loaded only inside
3854
3944
  // `core/auth/node/secret-store.ts` from the Hono `/api/servers`
3855
- // handlers. It's a native-binding package (no browser code path) so
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
3856
3948
  // excluding it keeps Vite's dep scanner from chasing into the
3857
3949
  // platform-specific binaries during dev startup.
3858
3950
  "@napi-rs/keyring"
@@ -3947,9 +4039,20 @@ function vitestSharedPaths(clientDir) {
3947
4039
  find: /^@napi-rs\/keyring$/,
3948
4040
  replacement: path2.resolve(dirname8, "node_modules/@napi-rs/keyring")
3949
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.
3950
4053
  {
3951
4054
  find: /^express$/,
3952
- replacement: path2.resolve(dirname8, "node_modules/express")
4055
+ replacement: path2.resolve(repoRoot, "node_modules/express")
3953
4056
  },
3954
4057
  {
3955
4058
  find: /^yaml$/,