Menu

AI

MCP CORS Errors on a Remote Server: How to Fix It (Browser Clients, Inspector, Web Apps)

Fix CORS errors when a browser-based MCP client calls your remote server: allow the Mcp-* request headers, expose Mcp-Session-Id, and match your Origin allowlist.

By: Irina ShvayaNovember 2, 2026

MCP CORS Errors on a Remote Server: How to Fix It (Browser Clients, Inspector, Web Apps)

This fix is part of MCP troubleshooting, within The MCP Guide. Still stuck? Request a free MCP readiness review.

A CORS error on an MCP server means a browser blocked the request, not the server. It only happens when the client runs in a web page, such as a web-based agent or the MCP Inspector web UI. The fastest fix is to add CORS middleware to your server that allows your client's origin, allows the Mcp-Method, Mcp-Name and MCP-Protocol-Version request headers, and exposes Mcp-Session-Id if you still serve 2025-era clients. Then make sure the server's own Origin allowlist agrees with the CORS list.

Checked against: the Python SDK "Add to an existing app" guide, the Streamable HTTP page of the 2026-07-28 spec and MDN's CORS guide on 2 November 2026 (Python SDK mcp 2.3.0, TypeScript @modelcontextprotocol/server 2.4.0). eSEOspace did not run these steps.

Key Takeaways

  • CORS is enforced by browsers. Desktop and terminal clients such as Claude Code never trigger it.
  • Every MCP request is preflighted, because Content-Type: application/json and the Mcp-* headers are not on the CORS safelist.
  • The 2026-07-28 spec requires MCP-Protocol-Version, Mcp-Method and, for some methods, Mcp-Name headers, so older allow-lists often miss them.
  • A server's Origin check is separate from CORS: an origin it does not trust gets HTTP 403 even after a clean preflight.
  • Never answer with a * wildcard on credentialed requests. MDN says browsers reject that.

What this error means

Browsers block a script on one origin from reading a response from another origin unless the server opts in. That opt-in is CORS. MDN explains that for anything beyond a "simple" request, the browser first sends an OPTIONS preflight and only sends the real request if the server approves it. Only three content types count as simple: application/x-www-form-urlencoded, multipart/form-data and text/plain. MCP uses JSON, so every request is preflighted.

The Python SDK guide says a browser client needs two permissions: to send its MCP request headers, and to read the one MCP sends back. It calls allow_headers "the half everyone forgets."

The 2026-07-28 spec made this more likely. The Streamable HTTP page requires an MCP-Protocol-Version header on every POST and Mcp-Method on all requests, with Mcp-Name on tools/call, resources/read and prompts/get. The TypeScript SDK's protocol versions page notes that deployed 2025 servers "commonly have allow-lists that predate the 2026 headers."

Typical browser console messages include "CORS header 'Access-Control-Allow-Origin' missing" or a preflight that fails. A modern client may simply report a TypeError from fetch. Use our MCP error code lookup when you also see a JSON-RPC code.

Quick fix

  1. Confirm the client really runs in a browser. If the same server works in Claude Code or the Inspector CLI, the problem is CORS, not the server.
  2. Add CORS middleware in front of your MCP endpoint, with your exact client origin (no wildcard if you send cookies or an Authorization header).
  3. Allow these request headers: Authorization, Content-Type, Mcp-Method, Mcp-Name, MCP-Protocol-Version. Add Mcp-Session-Id and Last-Event-ID only if you still serve 2025-era clients.
  4. Expose Mcp-Session-Id in Access-Control-Expose-Headers if you run a sessionful 2025-era endpoint. MDN notes that only safelisted response headers are visible to scripts by default.
  5. Allow POST (and GET and DELETE for 2025-era sessions).
  6. Add the same origin to your MCP server's own Origin allowlist.
  7. Reload the page and watch the OPTIONS request in the browser's Network tab.

Fix by client / OS

Python server (Starlette or FastAPI)

The Python SDK guide gives this pattern, which we copied from its example file (checked against mcp 2.3.0 docs):

from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from mcp.server.transport_security import TransportSecuritySettings

security = TransportSecuritySettings(
    allowed_hosts=["mcp.example.com", "mcp.example.com:*"],
    allowed_origins=["https://app.example.com"],
)

middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=["https://app.example.com"],
        allow_methods=["GET", "POST", "DELETE"],
        allow_headers=[
            "Authorization", "Content-Type", "Last-Event-ID",
            "Mcp-Method", "Mcp-Name", "Mcp-Protocol-Version", "Mcp-Session-Id",
        ],
        expose_headers=["Mcp-Session-Id"],
    )
]

Pass transport_security=security to mcp.streamable_http_app(). The guide warns that "an origin the transport doesn't trust gets a 403 even after a clean preflight."

TypeScript server

The TypeScript v2 Express guide says createMcpExpressApp validates Host and Origin by default on localhost binds. Passing allowedOrigins replaces the default list, and http://* and https://* are not honored. Add your CORS middleware (for example the cors package) with the same headers listed above. A browser extension client sends its extension ID as Origin, so add that ID to the list.

MCP Inspector web UI

The Inspector web app runs in your browser, so it can hit CORS where the CLI does not. The Inspector README points to its TLS page for custom hostnames and ALLOWED_ORIGINS. To confirm the server itself works, run the CLI version, which has no browser in the path:

npx @modelcontextprotocol/inspector --cli https://mcp.example.com/mcp --transport http --method tools/list

Claude Code, Claude Desktop, Cursor and VS Code

These clients do not run your requests inside a web page, so they do not apply CORS. If one of them fails against your server, the cause is something else. See Claude Code MCP failed to connect.

Where to find the logs

  • Browser: the Console shows the CORS reason; the Network tab shows the OPTIONS preflight and which Access-Control-* headers came back.
  • Python server: a rejected Origin returns 403 with the body Invalid Origin header, per the Python SDK troubleshooting page. A rejected Host returns 421 Misdirected Request and logs Invalid Host header at WARNING.
  • Reverse proxy or CDN: check its access log for OPTIONS requests that never reached the app.

If it still fails

  • Preflight returns 404 or 405. Your proxy or framework does not route OPTIONS to the CORS middleware. Put CORS before the MCP mount.
  • Preflight passes, then 403. That is the server's Origin check, not CORS. Add the origin to allowed_origins or allowedOrigins.
  • 421 on every request after deploy. That is the Host allowlist. Add your public hostname with and without :*.
  • Works locally, fails in production. A CDN may strip or cache CORS headers. Test the origin server directly.
  • Security check. Do not "fix" CORS with * everywhere. The spec requires Origin validation to prevent DNS rebinding. Our Streamable HTTP reference covers the header rules.

Next step

If you are putting an MCP server in front of browser clients for the first time, eSEOspace's MCP development team can review your transport, CORS and auth setup. For the full list of fix guides, start at the MCP Guide.

More in the MCP Guide

Free tools: MCP Error Code Lookup · MCP Config Generator

Latest research: State of MCP: October 2026

Frequently asked questions

Why does my MCP server work in Claude Code but not in my web app?

Claude Code is not a browser, so it does not enforce CORS. Your web app runs in a browser, which preflights every MCP request and blocks it unless the server allows your origin and headers.

Do I still need to expose Mcp-Session-Id?

Only for 2025-era clients on a sessionful endpoint. The 2026-07-28 spec removed protocol sessions, so a modern-only server does not send that header. The Python SDK guide still exposes it because its example serves both eras.

Can I use Access-Control-Allow-Origin: * for an MCP server?

Not for requests with credentials. MDN states that the server must not use the * wildcard for Access-Control-Allow-Origin or Access-Control-Allow-Headers on a credentialed request. Most MCP servers use an Authorization header, so list origins explicitly.

Is a 403 Invalid Origin header the same as a CORS error?

No. CORS is the browser blocking a response. A 403 Invalid Origin header is your MCP server refusing the request because its own allowlist does not include that origin. You need both lists to match.

You Might Also like to Read

Get a FREE Website Audit

Enter your website and get a 20–30 page SEO, GEO, AEO & CRO audit within 24 hours. Completely free.

Don't have a site yet? Get in touch →

Ready to speak with a marketing expert?
Give us a call! (916) 866-7893

Websites Launched
1407
Websites Launched
Marketing Experts
29
Marketing Experts
Hours of Dedicated Work
204533
Hours of Dedicated Work
Irina S.Benjamin G.Jeanette M.David G.