Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/local-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ running and where a package goes.

## Install

Requires Node `>=22.19.0`.
Requires Node `^22.23.4 || ^24.22.1 || >=26.12.1`.

```sh
npm install # at the REPO ROOT
Expand Down
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/1-bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ body:
attributes:
label: Node version
description: >
Output of `node --version`. The current v2 line requires Node >=
22.19.0; the deprecated v1 line has its own, older floor.
placeholder: "v22.19.0"
Output of `node --version`. The current v2 line requires Node ^22.23.4,
^24.22.1 or >=26.12.1; the deprecated v1 line has its own, older floor.
placeholder: "v22.23.4"
validations:
required: true

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ silently. For the fuller map (what each `core/` area owns, what each
v2 is **not** an npm workspace — each client under `clients/*` keeps its own
`package.json` and `node_modules`. A single `npm install` at the **repo root**
is still all you need: the root `postinstall` cascades into every client. Node
`>=22.19.0`.
`^22.23.4 || ^24.22.1 || >=26.12.1`.

```sh
npm install # repo root
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ npx -p @modelcontextprotocol/inspector mcpdo --help # mcpdo

## Quick start (development)

Requires Node `>=22.19.0`.
Requires Node `^22.23.4 || ^24.22.1 || >=26.12.1`: the patched release on each supported line.

```bash
npm install # at the repo root; postinstall cascades into every client
Expand Down
4 changes: 2 additions & 2 deletions clients/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ Connections to remote HTTP/SSE servers honor the conventional proxy environment

OAuth requests go through the destination guard rather than the plain proxied fetch: it reads the same variables, refuses link-local and cloud-metadata targets, and pins each direct connection to the addresses it checked ([#2749](https://github.com/modelcontextprotocol/inspector/issues/2749)). What it can and cannot promise behind a proxy is in [Outbound fetch destination guard](../web/README.md#outbound-fetch-destination-guard).

Proxy routing is powered by the [`undici`](https://www.npmjs.com/package/undici) package (declared in the **root** manifest only; `^8.x` requires Node `>= 22.19.0`, the Inspector's supported floor). It is imported lazily on the first request and, for MCP traffic, only when a proxy variable is set, so MCP connections without a proxy configured pay no cost. OAuth requests load it either way, for the destination guard.
Proxy routing is powered by the [`undici`](https://www.npmjs.com/package/undici) package (declared in the **root** manifest only; `^8.x` requires Node `>= 22.19.0`, below the Inspector's supported floor of `^22.23.4 || ^24.22.1 || >=26.12.1`). It is imported lazily on the first request and, for MCP traffic, only when a proxy variable is set, so MCP connections without a proxy configured pay no cost. OAuth requests load it either way, for the destination guard.

**Both halves of the pair come from userland undici — its `fetch` _and_ its `EnvHttpProxyAgent` ([#2067](https://github.com/modelcontextprotocol/inspector/issues/2067)).** The obvious-looking alternative is to hand the agent to Node's built-in `fetch` as a `dispatcher`, which is what v2.0.0–2.3.0 did. That couples two _different copies_ of undici at the dispatcher handler interface, and that interface is not stable across majors: Node 22 embeds undici 6, Node 24 embeds 7, Node 26 embeds 8. A userland undici 8 agent handed a built-in undici 7 handler is rejected outright — `fetch failed: invalid onRequestStart method` — and the request never leaves the process. Keeping both sides inside one copy is what makes proxying work unchanged from the Node 22.19 floor through Node 26. (Node's own `NODE_USE_ENV_PROXY` is not a substitute: it is unsupported at our 22.19 engine floor.)
**Both halves of the pair come from userland undici — its `fetch` _and_ its `EnvHttpProxyAgent` ([#2067](https://github.com/modelcontextprotocol/inspector/issues/2067)).** The obvious-looking alternative is to hand the agent to Node's built-in `fetch` as a `dispatcher`, which is what v2.0.0–2.3.0 did. That couples two _different copies_ of undici at the dispatcher handler interface, and that interface is not stable across majors: Node 22 embeds undici 6, Node 24 embeds 7, Node 26 embeds 8. A userland undici 8 agent handed a built-in undici 7 handler is rejected outright — `fetch failed: invalid onRequestStart method` — and the request never leaves the process. Keeping both sides inside one copy is what makes proxying work unchanged on every line the floor admits, Node 22 through Node 26. (Node's own `NODE_USE_ENV_PROXY` is not a substitute: it exists at our floor, added in 22.21.0, but it is opt-in at process start and still experimental, so a user who only exports `HTTPS_PROXY` would not get it.)

Because undici's `Response` is a different class from `globalThis.Response`, the proxied fetch re-wraps each response as a genuine global `Response`, streaming preserved. Without that, `res instanceof Response` is `false` for callers that test it — including the MCP SDK, whose OAuth error formatter would degrade every message to `Raw body: [object Response]`.

Expand Down
140 changes: 70 additions & 70 deletions clients/web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions clients/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@
"@types/react-dom": "^19.2.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@vitejs/plugin-react": "^6.0.0",
"@vitest/browser-playwright": "4.1.11",
"@vitest/browser-playwright": "4.1.12",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"eslint-plugin-storybook": "^10.5.5",
"happy-dom": "^20.11.2",
"happy-dom": "^21.0.4",
"playwright": "^1.58.2",
"storybook": "^10.5.5",
"tsdown": "^0.23.1",
Expand All @@ -86,7 +86,7 @@
"@types/node": "^24.12.4",
"esbuild": "^0.28.2",
"open": "^11.0.4",
"vitest": "4.1.11",
"vitest": "4.1.12",
"rolldown": "1.2.13"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,8 @@ describe("FileSecretStore failure handling", () => {
});

it("refuses an authentic tag truncated to 4 bytes (#2485)", async () => {
// A genuine tag, cut short. Node 22 (the engines floor) authenticates a
// 4-byte GCM tag unless the length is pinned, which makes a forgery a
// A genuine tag, cut short. Node 22 and 24 (both within the engines floor)
// authenticate a 4-byte GCM tag unless the length is pinned, which makes a forgery a
// ~2^-32 guess — so no read may return the secret, whatever the runtime.
await writeEncryptedFixture();
const parsed = JSON.parse(await fs.readFile(filePath(), "utf-8"));
Expand All @@ -398,9 +398,9 @@ describe("FileSecretStore failure handling", () => {

it("pins the tag length at the cipher, not only in the envelope check (#2485)", async () => {
// The test above cannot see this: `encryptedEnvelopeProblem` rejects a
// short tag before `createDecipheriv` runs, and current Node rejects one
// natively. So observe the options themselves — without them, Node 22
// authenticates a 4-byte tag should the envelope check ever regress.
// short tag before `createDecipheriv` runs, and Node 26 rejects one
// natively. So observe the options themselves — without them, Node 22 and
// 24 authenticate a 4-byte tag should the envelope check ever regress.
const seen: { cipher: unknown[]; decipher: unknown[] } = {
cipher: [],
decipher: [],
Expand Down
Loading
Loading