Next.js Dev Server Exposes an MCP Endpoint: What CVE-2026-94486 Really Does (2026)

Next.js 16 added a feature most people building with it never turned on: the development server now runs a Model Context Protocol (MCP) endpoint at /_next/mcp, so an AI coding agent can ask your running app about its errors, routes, logs and Server Action source. It is enabled by default. On 2026-09-30, Vercel disclosed CVE-2026-94486: that endpoint did not properly check which website a request came from.

We installed the vulnerable version and the patched one side by side and reproduced the bypass. The short version is below, and then the part the aggregator headlines skip: how much a real browser can actually do with it.

TL;DR

Next.js 16 runs an MCP endpoint on next dev by default. In versions below 16.3.8 a cross-origin request could bypass the dev server's same-site guard by appending ?x=/_next/image to the URL, reaching an endpoint that leaks your project's path on disk, route list, and dev logs. It is dev-only (production is not affected), rated Low (CVSS 2.3), and fixed in 16.3.8. Upgrade, or set experimental.mcpServer: false. In our testing a plain browser fetch was still blocked by CORS preflight, so the practical risk is narrower than "any site reads your code."

What the endpoint does

When you run next dev on Next.js 16, the server exposes /_next/mcp. Per Next's own docs, an agent connected through it can call tools including:

  • get_project_metadata: the project's absolute path on disk and the dev server URL
  • get_routes: every route, grouped by appRouter and pagesRouter, dynamic segments and all
  • get_logs: the path to the dev log file, which holds browser console logs and server output
  • get_errors: current errors with source-mapped stack traces, including snippets of your code
  • get_server_action_by_id: the source file and export name behind a Server Action

That is a useful control plane for a coding agent on your machine. It is also a description of exactly what you would not want a web page you happen to be visiting to read.

The docs state the default plainly: "The endpoint is enabled by default. To disable it, set experimental.mcpServer to false in next.config."

The guard, and the bypass we reproduced

Next's dev server already ships a cross-site guard. It is supposed to block a request to an internal /_next/* resource when that request comes from another origin, which is the standard defense for a server bound to localhost. We confirmed it works: a cross-origin POST to /_next/mcp with Origin: https://evil.example returns 403 Unauthorized on both the vulnerable and the patched build.

So where is the hole? The guard carves out a few static-asset paths that browsers load through CSS and cannot send as CORS requests: /_next/image, /_next/static/media. In the vulnerable version it decided whether a request was one of those by checking the entire URL string, query parameters included.

That is the bug. Append a harmless-looking query to the MCP path and the guard waves it through.

Reproduced against next@16.3.7 (vulnerable)
# Plain cross-origin request: blocked, as it should be
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  'http://127.0.0.1:3000/_next/mcp' \
  -H 'Origin: https://evil.example' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"get_routes","arguments":{}}}'
# -> 403

# Same request, one query param appended: allowed
curl -s -X POST \
  'http://127.0.0.1:3000/_next/mcp?x=/_next/image' \
  -H 'Origin: https://evil.example' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"get_routes","arguments":{}}}'
# -> 200, and the body contains:
#   { "appRouter": ["/", "/admin/users"] }

The ?x=/_next/image string never points at a real image. It only has to make the guard's substring check think the URL is a static asset. The same trick worked with a path suffix, /_next/mcp/_next/static/media, because the MCP handler matched any path that started with /_next/mcp. On the patched 16.3.8 build both requests come back 403 and 404.

We ran get_project_metadata the same way and got the project's full absolute path on disk back. On a shared or named machine, that path alone can leak a client name or an unreleased product's codename.

What the fix actually changed

Upgrading to 16.3.8 is the fix, and it is worth knowing it closed the hole in two places, because it tells you what the real defect was:

1

The cross-site guard stopped checking the whole URL and now checks only the pathname. Appending ?x=/_next/image no longer fools it, because the query string is not part of the path.

2

The MCP handler stopped matching any path that starts with /_next/mcp and now matches that path exactly. So /_next/mcp/_next/static/media no longer reaches it.

Either change alone would have closed the version we tested. Shipping both is the belt-and-suspenders you want in a security patch.

The part the headlines skip

Some coverage of this CVE reads like "any website you visit can steal your Next.js source." That overshoots, and a founder deciding whether to panic deserves the measured version.

The bypass above defeats the server's origin check. But a malicious web page runs in a browser, and the browser has its own rules. To POST JSON to your dev server, a page has to send Content-Type: application/json, which is not a CORS "simple" request, so the browser first sends a preflight OPTIONS. We tested that preflight against the endpoint:

The browser's preflight, tested
curl -s -i -X OPTIONS \
  'http://127.0.0.1:3000/_next/mcp?x=/_next/image' \
  -H 'Origin: https://evil.example' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type'
# -> HTTP/1.1 405 Method Not Allowed
#    (no Access-Control-Allow-Origin header)

No Access-Control-Allow-Origin comes back, so the preflight fails and the browser never sends the real request. A cross-origin GET is a simple request and does reach the endpoint, but its response also carries no Access-Control-Allow-Origin, so the attacking page's JavaScript cannot read what comes back.

This is not a reason to skip the upgrade. The server-side guard really was bypassable, Vercel rated it a genuine (if Low) vulnerability and patched it, and we only tested the obvious request shapes rather than every trick a determined attacker might use, such as pairing it with DNS rebinding. Defense in depth means not leaning on the browser to save you.

So why is it Low (CVSS 2.3) rather than the critical-sounding thing the word "source code" implies? Because it is dev-only, the data is development metadata rather than your production database, and the browser's own cross-origin rules are a second wall behind the one that broke. That is the honest severity.

The 30-second check

1

Find your Next.js version: npm ls next or look at next in package.json. If it is 16.0.0 through 16.3.7, you are in range. 15.x and below never had this endpoint.

2

Upgrade: npm install next@latest (16.3.8 or newer). Then restart next dev.

3

If you do not use an agent that talks to the running dev server, turn the endpoint off regardless of version. Add experimental: { mcpServer: false } to next.config.js and restart.

4

Want to see it for yourself? With the dev server running, curl http://127.0.0.1:3000/_next/mcp from your own machine. A response means the endpoint is live. On a patched build the cross-origin bypass above returns 403.

The bigger lesson for vibe-coded apps

The specific CVE is small. The shape of it is not, and it is going to keep happening: tools you build with are quietly adding agent control planes, and those planes run with your full local access.

Your dev server now has an endpoint that can read your routes, your logs and your Server Action source, and it turned on without you asking. Cursor added remote control on by default. An AI agent will cheerfully put your whole dev server on a public tunnel to show you a preview. The pattern is the same every time: a convenience feature opens a door on localhost, the default is "on," and the security note is three clicks deep in a changelog.

The habit worth building is boring. When a tool you use ships a new version, skim what it turned on, not just what it fixed. The dangerous default is rarely the one in the headline.

Is CVE-2026-94486 exploitable in production?

No. The MCP endpoint only exists when you run next dev. Production builds (next build then next start) do not serve /_next/mcp at all, so a deployed app is not affected. This is purely a local development issue.

What versions of Next.js are affected?

Next.js 16.0.0 up to but not including 16.3.8. The fix shipped in 16.3.8 on 2026-09-30, and 15.x was never affected because the MCP endpoint is a Next 16 feature. If you are on any 16.x below 16.3.8, upgrade.

How do I turn the MCP endpoint off entirely?

Set experimental.mcpServer to false in next.config.js and restart the dev server. That removes the endpoint whether or not you have upgraded. If you do not use an AI agent that talks to the running dev server, there is no reason to leave it on.

Can a random website really read my source code through this?

The advisory allows for it, which is why Vercel assigned a CVE and patched it. In our own testing a plain cross-origin fetch was stopped by the browser's CORS preflight before the bypass mattered, so a basic drive-by page could not read the output. We did not test every possible request shape, and the server-side guard really was bypassable, so upgrade rather than relying on that.

Shipping a Next.js app built with an AI agent?

CheckYourVibe scans your deployed app for the issues AI codegen leaves behind. Find them before someone else does.

Vulnerability Guides

Next.js Dev Server Exposes an MCP Endpoint: What CVE-2026-94486 Really Does (2026)