@modelcontextprotocol/inspector 2.4.0 → 2.5.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.
@@ -348,6 +348,9 @@ function storedFieldsToInspectorSettings(stored) {
348
348
  if (stored.oauth?.requestRefreshToken === false) {
349
349
  settings.oauthRequestRefreshToken = false;
350
350
  }
351
+ if (stored.oauth?.revokeOnClear === false) {
352
+ settings.oauthRevokeOnClear = false;
353
+ }
351
354
  if (stored.cwd) settings.cwd = stored.cwd;
352
355
  return settings;
353
356
  }
@@ -417,6 +420,9 @@ function inspectorSettingsToStoredFields(settings) {
417
420
  if (settings.oauthRequestRefreshToken === false) {
418
421
  oauthFields.requestRefreshToken = false;
419
422
  }
423
+ if (settings.oauthRevokeOnClear === false) {
424
+ oauthFields.revokeOnClear = false;
425
+ }
420
426
  if (Object.keys(oauthFields).length > 0) {
421
427
  out.oauth = oauthFields;
422
428
  }
@@ -4489,11 +4495,27 @@ function createRemoteApp(options) {
4489
4495
  void responseWait.catch(() => {
4490
4496
  });
4491
4497
  }
4498
+ const upstreamAbort = new AbortController();
4499
+ const clientSignal = c.req.raw.signal;
4500
+ const onClientDisconnect = () => {
4501
+ upstreamAbort.abort(clientSignal.reason);
4502
+ if (requestId !== void 0) {
4503
+ session.cancelRequestWait(requestId);
4504
+ }
4505
+ };
4506
+ if (clientSignal.aborted) {
4507
+ onClientDisconnect();
4508
+ } else {
4509
+ clientSignal.addEventListener("abort", onClientDisconnect, {
4510
+ once: true
4511
+ });
4512
+ }
4492
4513
  try {
4493
4514
  const paramHeaders = mcpParamHeadersOnly(headers);
4494
4515
  await session.transport.send(message, {
4495
4516
  relatedRequestId,
4496
- ...paramHeaders && { headers: paramHeaders }
4517
+ ...paramHeaders && { headers: paramHeaders },
4518
+ requestSignal: upstreamAbort.signal
4497
4519
  });
4498
4520
  if (responseWait) {
4499
4521
  await responseWait;
@@ -4514,6 +4536,7 @@ function createRemoteApp(options) {
4514
4536
  const msg = err instanceof Error ? err.message : String(err);
4515
4537
  return c.json({ ok: false, kind: "transport_error", error: msg });
4516
4538
  } finally {
4539
+ clientSignal.removeEventListener("abort", onClientDisconnect);
4517
4540
  session.endSend();
4518
4541
  }
4519
4542
  });
@@ -4755,6 +4778,9 @@ function createRemoteApp(options) {
4755
4778
  if (o.requestRefreshToken !== void 0 && typeof o.requestRefreshToken !== "boolean") {
4756
4779
  return false;
4757
4780
  }
4781
+ if (o.revokeOnClear !== void 0 && typeof o.revokeOnClear !== "boolean") {
4782
+ return false;
4783
+ }
4758
4784
  return true;
4759
4785
  };
4760
4786
  const isRootArray = (v) => {
@@ -4826,7 +4852,7 @@ function createRemoteApp(options) {
4826
4852
  if ("oauth" in valObj && !isOauthObject(valObj.oauth)) {
4827
4853
  logWarn(
4828
4854
  { route: "/api/servers", id, droppedKey: "oauth" },
4829
- "Dropping malformed `oauth` field \u2014 expected `{ clientId?, clientSecret?, scopes?, authorizationParams?, authorizationUrl?, tokenUrl?, enterpriseManaged?, onInsufficientScope?, requestRefreshToken? }`."
4855
+ "Dropping malformed `oauth` field \u2014 expected `{ clientId?, clientSecret?, scopes?, authorizationParams?, authorizationUrl?, tokenUrl?, enterpriseManaged?, onInsufficientScope?, requestRefreshToken?, revokeOnClear? }`."
4830
4856
  );
4831
4857
  delete valObj.oauth;
4832
4858
  }
@@ -5010,6 +5036,12 @@ function createRemoteApp(options) {
5010
5036
  error: "settings.oauthRequestRefreshToken must be a boolean"
5011
5037
  };
5012
5038
  }
5039
+ if (obj.oauthRevokeOnClear !== void 0 && typeof obj.oauthRevokeOnClear !== "boolean") {
5040
+ return {
5041
+ ok: false,
5042
+ error: "settings.oauthRevokeOnClear must be a boolean"
5043
+ };
5044
+ }
5013
5045
  if (obj.oauthOnInsufficientScope !== void 0 && obj.oauthOnInsufficientScope !== "reauthorize" && obj.oauthOnInsufficientScope !== "throw") {
5014
5046
  return {
5015
5047
  ok: false,
@@ -5092,6 +5124,9 @@ function createRemoteApp(options) {
5092
5124
  if (obj.oauthRequestRefreshToken === false) {
5093
5125
  value.oauthRequestRefreshToken = false;
5094
5126
  }
5127
+ if (obj.oauthRevokeOnClear === false) {
5128
+ value.oauthRevokeOnClear = false;
5129
+ }
5095
5130
  if (obj.oauthOnInsufficientScope === "reauthorize" || obj.oauthOnInsufficientScope === "throw") {
5096
5131
  value.oauthOnInsufficientScope = obj.oauthOnInsufficientScope;
5097
5132
  }
@@ -5864,7 +5899,11 @@ function vitestSharedPaths(clientDir) {
5864
5899
  "react",
5865
5900
  "react-dom",
5866
5901
  "@modelcontextprotocol/client",
5867
- "@modelcontextprotocol/core"
5902
+ "@modelcontextprotocol/core",
5903
+ // Every SDK schema is a zod type, so a second copy breaks `instanceof`
5904
+ // across the whole surface. The alias below picks the install; this keeps
5905
+ // it to one copy within it.
5906
+ "zod"
5868
5907
  ];
5869
5908
  const nodeModulesAliases = [
5870
5909
  {
@@ -5890,67 +5929,117 @@ function vitestSharedPaths(clientDir) {
5890
5929
  find: /^react-dom\/client$/,
5891
5930
  replacement: path5.resolve(dirname10, "node_modules/react-dom/client.js")
5892
5931
  },
5893
- { find: /^pino$/, replacement: path5.resolve(dirname10, "node_modules/pino") },
5932
+ // Everything below is **root-owned** and resolves from the repo root,
5933
+ // unlike the `react` / `react-dom` pins above. What they have in common is
5934
+ // that no client declares them, so the root install is the only place a
5935
+ // client's resolution chain is guaranteed to find one — not that they are
5936
+ // all root `dependencies`: `express` is test-only and sits in the root
5937
+ // `devDependencies`. Nor is this the complete root runtime set; a package
5938
+ // no client install carries a copy of (`ajv`, `commander`, `undici`)
5939
+ // resolves to the root on its own and needs no pin. Absence from this list
5940
+ // is a statement about the installed tree, not about the manifests — check
5941
+ // the tree before adding or removing an entry.
5942
+ //
5943
+ // `express` and `yaml` are reached only through `test-servers/src` —
5944
+ // express by the http/oauth servers, yaml by `load-config.ts` — which is
5945
+ // root-owned code with no manifest of its own.
5946
+ //
5947
+ // Pointing these at `<client>/node_modules` is what broke when the MCP
5948
+ // packages moved to the root (#1970): express was never declared by a client
5949
+ // at all, it arrived in `clients/cli` as a peer of `express-rate-limit`
5950
+ // under `@modelcontextprotocol/server-legacy`, so removing that manifest
5951
+ // entry took express with it and every cli test that spawns a test server
5952
+ // failed to resolve it.
5953
+ {
5954
+ find: /^express$/,
5955
+ replacement: path5.resolve(repoRoot, "node_modules/express")
5956
+ },
5957
+ {
5958
+ find: /^yaml$/,
5959
+ replacement: path5.resolve(repoRoot, "node_modules/yaml")
5960
+ },
5961
+ // Same reasoning, one layer in: `proper-lockfile` is reached only through
5962
+ // `core/` (the secrets file's cross-process lock, #2082), which is the
5963
+ // other root-owned tree with no manifest of its own. Resolution finds the
5964
+ // root copy on its own today — nothing declares it in a client — and this
5965
+ // pin is what keeps that from depending on nothing ever arriving as some
5966
+ // client's transitive dependency, which would otherwise give a test two
5967
+ // copies of a module whose whole job is a single registry of held locks.
5968
+ {
5969
+ find: /^proper-lockfile$/,
5970
+ replacement: path5.resolve(repoRoot, "node_modules/proper-lockfile")
5971
+ },
5972
+ // The rest of the root-owned aliases, consolidated into the root manifest
5973
+ // by #2195. Mostly `core/`'s runtime dependencies, but not only —
5974
+ // `@hono/node-server` is reached from web client code alone and is here for
5975
+ // the same resolution reason rather than because `core/` imports it. Each
5976
+ // used to be declared by the clients that reached it and was pinned to
5977
+ // `<client>/node_modules` accordingly; once the declarations went away
5978
+ // those paths stopped existing, so the pin has to follow the package to
5979
+ // the root. Left un-repointed they would resolve
5980
+ // to a directory that is not there — or, worse, to a transitive copy some
5981
+ // unrelated dependency happened to drag in, which is the duplicate this
5982
+ // whole pin list exists to prevent.
5983
+ {
5984
+ find: /^pino$/,
5985
+ replacement: path5.resolve(repoRoot, "node_modules/pino")
5986
+ },
5894
5987
  {
5895
5988
  find: /^pino\/browser\.js$/,
5896
- replacement: path5.resolve(dirname10, "node_modules/pino/browser.js")
5989
+ replacement: path5.resolve(repoRoot, "node_modules/pino/browser.js")
5897
5990
  },
5898
5991
  {
5899
5992
  find: /^hono$/,
5900
- replacement: path5.resolve(dirname10, "node_modules/hono/dist/index.js")
5993
+ replacement: path5.resolve(repoRoot, "node_modules/hono/dist/index.js")
5901
5994
  },
5902
5995
  {
5903
5996
  find: /^hono\/streaming$/,
5904
5997
  replacement: path5.resolve(
5905
- dirname10,
5998
+ repoRoot,
5906
5999
  "node_modules/hono/dist/helper/streaming/index.js"
5907
6000
  )
5908
6001
  },
5909
6002
  {
5910
6003
  find: /^@hono\/node-server$/,
5911
- replacement: path5.resolve(dirname10, "node_modules/@hono/node-server")
6004
+ replacement: path5.resolve(repoRoot, "node_modules/@hono/node-server")
5912
6005
  },
5913
6006
  {
5914
6007
  find: /^atomically$/,
5915
- replacement: path5.resolve(dirname10, "node_modules/atomically")
6008
+ replacement: path5.resolve(repoRoot, "node_modules/atomically")
5916
6009
  },
5917
6010
  {
5918
6011
  find: /^chokidar$/,
5919
- replacement: path5.resolve(dirname10, "node_modules/chokidar")
6012
+ replacement: path5.resolve(repoRoot, "node_modules/chokidar")
5920
6013
  },
5921
6014
  {
5922
6015
  find: /^@napi-rs\/keyring$/,
5923
- replacement: path5.resolve(dirname10, "node_modules/@napi-rs/keyring")
5924
- },
5925
- // `express` and `yaml` resolve from the **repo root**, unlike every pin
5926
- // above. Both are reached only through `test-servers/src` — express by the
5927
- // http/oauth servers, yaml by `load-config.ts` — which is root-owned code
5928
- // with no manifest of its own, so the root is where they are declared and
5929
- // the only place a client's resolution chain is guaranteed to find them.
5930
- // Pointing these at `<client>/node_modules` is what broke when the MCP
5931
- // packages moved to the root (#1970): express was never declared by a client
5932
- // at all, it arrived in `clients/cli` as a peer of `express-rate-limit`
5933
- // under `@modelcontextprotocol/server-legacy`, so removing that manifest
5934
- // entry took express with it and every cli test that spawns a test server
5935
- // failed to resolve it.
5936
- {
5937
- find: /^express$/,
5938
- replacement: path5.resolve(repoRoot, "node_modules/express")
6016
+ replacement: path5.resolve(repoRoot, "node_modules/@napi-rs/keyring")
5939
6017
  },
6018
+ // `zod` and `open` are pinned for a different reason from everything above:
6019
+ // they resolve *somewhere* without help, and the somewhere is wrong. Both
6020
+ // still sit at the top level of `clients/web/node_modules` as transitive
6021
+ // copies — zod under `eslint-plugin-react-hooks`, open under Storybook —
6022
+ // so an unpinned bare import from a web test resolves the client copy while
6023
+ // `core/` and the SDK packages resolve the root's (Copilot).
6024
+ //
6025
+ // For `open` that is merely wasteful. For `zod` it is the hazard this file
6026
+ // exists for: two copies in one process means a schema built by one and an
6027
+ // `instanceof` check made by the other, across the entire
6028
+ // `@modelcontextprotocol/*` surface. The versions are identical today and
6029
+ // `verify:dep-lockstep` is what keeps them that way, but identical is not
6030
+ // the same as single, and only a pin makes it single.
6031
+ //
6032
+ // `zod/v4` needs its own entry — first-party code imports both specifiers,
6033
+ // and pinning only the bare one would split the package across two installs
6034
+ // rather than collapse it.
6035
+ { find: /^zod$/, replacement: path5.resolve(repoRoot, "node_modules/zod") },
5940
6036
  {
5941
- find: /^yaml$/,
5942
- replacement: path5.resolve(repoRoot, "node_modules/yaml")
6037
+ find: /^zod\/v4$/,
6038
+ replacement: path5.resolve(repoRoot, "node_modules/zod/v4/index.js")
5943
6039
  },
5944
- // Same reasoning, one layer in: `proper-lockfile` is reached only through
5945
- // `core/` (the secrets file's cross-process lock, #2082), which is the
5946
- // other root-owned tree with no manifest of its own. Resolution finds the
5947
- // root copy on its own today — nothing declares it in a client — and this
5948
- // pin is what keeps that from depending on nothing ever arriving as some
5949
- // client's transitive dependency, which would otherwise give a test two
5950
- // copies of a module whose whole job is a single registry of held locks.
5951
6040
  {
5952
- find: /^proper-lockfile$/,
5953
- replacement: path5.resolve(repoRoot, "node_modules/proper-lockfile")
6041
+ find: /^open$/,
6042
+ replacement: path5.resolve(repoRoot, "node_modules/open")
5954
6043
  }
5955
6044
  ];
5956
6045
  const projectResolve = {