Validate Origins on MCP HTTP Servers
Validate the Origin header on every Streamable HTTP connection, reject invalid origins with HTTP 403, and bind local servers to 127.0.0.1 unless remote access is deliberate. Keep authentication on every exposed endpoint: an allowed browser origin limits where browser requests start, but it does not establish the caller’s identity.
Prerequisites
Before changing the server, identify three things:
- The exact MCP endpoint and transport. The MCP Streamable HTTP transport specification dated 2025-11-25 defines one endpoint that accepts HTTP POST and GET requests, with optional Server-Sent Events. If you are still choosing between that transport and the older HTTP+SSE path, settle the Streamable HTTP or deprecated SSE transport first.
- Whether the endpoint is meant to serve only a client on the same machine or clients elsewhere. That decision controls the listening address; it must not be left to a framework default.
- Every browser origin that genuinely needs access, written as its complete scheme, hostname, and port. Development and production origins are separate entries when any part differs.
You also need an authentication mechanism for any HTTP endpoint exposed beyond the local process. Origin validation cannot replace it.
Configure the server
-
Set the narrowest listening address
For a local MCP server, bind the listener to loopback, normally
127.0.0.1. Do not bind to0.0.0.0unless access through other network interfaces is intentional. Loopback limits which machines can establish a connection;0.0.0.0listens through every available interface and therefore changes a local service into a network-reachable one wherever routing and other controls permit it.This choice reduces exposure but does not finish the job. The MCP security best practices dated 2025-11-25 describe an insecure local server left on localhost as reachable through DNS rebinding from compromised JavaScript. A browser context is the reason the Origin check is still required even when the listener uses loopback.
Bind beyond loopback only when remote access is part of the design. Record that choice as an exposed deployment and apply authentication in step 4. The practical boundary between those cases is covered in remote versus local MCP servers.
-
Build an exact Origin allowlist
List the browser applications allowed to call the MCP endpoint. According to MDN’s Origin header reference, last modified August 7, 2026, an origin contains the scheme, hostname, and port that caused a request; it does not contain a URL path.
Store complete values such as:
https://console.example.com https://console.example.com:8443 http://127.0.0.1:3000Treat those as three different origins. Do not reduce them to a hostname comparison: changing
httptohttps, changing the hostname, or changing a non-default port changes the origin. A path such as/settingsdoes not belong in the allowlist because the header will not contain it.Handle the literal value
nullas an origin value, not as proof that the request is local. MDN documents browser situations that produceOrigin: null. Unless such a context is an intentional client of this server, it should not be in the allowlist.Keep development entries separate from production entries so that deploying the server does not automatically trust a development web page.
-
Validate Origin before processing MCP messages
Put the check at the HTTP endpoint boundary, before JSON-RPC parsing, session lookup, tool execution, or opening an event stream. The Streamable HTTP requirement applies to all incoming connections, so do not protect only POST while leaving the endpoint’s GET path unchecked.
The decision can remain small and explicit:
read Origin if Origin is present and is not in ALLOWED_ORIGINS: return HTTP 403 Forbidden continue to authentication continue to the MCP handler only after authentication succeedsIf the
Originheader is present and invalid, the MCP transport specification requires HTTP403 Forbidden. It permits the response body to contain a JSON-RPC error without anid, but a body is not required. Returning before the MCP handler matters because rejecting a response later would still allow unwanted work to begin.Do not silently convert an unknown origin into an allowed one. Log enough to distinguish an absent header from a rejected value, but do not make the log itself part of the authorization decision.
A request without an
Originheader has not passed an identity check. Non-browser MCP clients may not supply browser context, so missing-origin handling must remain distinct from authentication. It must never become a route around the authentication layer configured next. -
Authenticate every exposed HTTP connection
Require the endpoint’s intended authentication after the Origin gate and before MCP processing. Apply it to POST requests and GET connections, including an event stream when the server supports one.
Origin validation answers a narrow question: did a browser identify the request as starting from an allowed scheme, hostname, and port? It does not identify a person, service, or MCP client, and it does not grant permission to invoke a tool. Authentication answers the identity and access question. An exposed endpoint therefore needs both controls.
Keep the failure paths separate. An invalid supplied origin gets
403 Forbiddenat the Origin gate. A request from an allowed origin can still fail authentication. That distinction prevents an allowlisted web application from being treated as a credential and ensures direct HTTP callers remain subject to authentication. -
Test the three controls independently
Exercise the endpoint at the HTTP boundary rather than relying only on a successful tool call. At minimum, verify these cases:
- A request carrying an exact allowlisted origin reaches the authentication layer.
- The same hostname with the wrong scheme or port is rejected with HTTP 403.
- An unlisted hostname is rejected with HTTP 403.
Origin: nullis rejected unless it was deliberately allowlisted.- GET and POST receive the same Origin validation.
- A request with an allowed origin but no valid authentication does not reach the MCP handler.
- A local deployment listens on
127.0.0.1, not0.0.0.0.
For an intentionally remote deployment, replace the last assertion with one that confirms the chosen listening interface while retaining the Origin and authentication tests. Remote reachability changes the binding requirement; it does not remove either application-layer control.
-
Check the deployed path, not only the server process
Run the same rejection tests against the address clients actually use. Confirm that an invalid supplied Origin still produces HTTP 403 and that allowed-origin requests still encounter authentication before MCP handling.
Then make one authenticated initialization request from an approved client and verify that the request reaches the MCP endpoint. If the security checks pass but initialization or a later tool request fails, follow the MCP tool-call debugging sequence rather than weakening the Origin allowlist. A transport or protocol error is not evidence that the security boundary should accept more origins.
Repeat the deployed checks whenever the public hostname, browser application origin, listening address, or endpoint path changes. Those changes can alter which requests should be accepted even when the MCP implementation itself is unchanged.
Expected result
The local configuration listens only on 127.0.0.1; a deliberately remote configuration listens only where intended. Every Streamable HTTP GET and POST with a supplied, unapproved Origin receives HTTP 403 before MCP processing. An approved or absent Origin still passes through authentication, and only an authenticated request reaches the MCP handler.
Sources
- MCP Streamable HTTP transport specification dated 2025-11-25modelcontextprotocol.io
- MCP security best practices dated 2025-11-25modelcontextprotocol.io
- MDN’s Origin header reference, last modified August 7, 2026developer.mozilla.org
See also
Add progress tokens to MCP requests, emit correlated notifications, keep values monotonic, and handle totals, timeouts, and completion correctly.
Define MCP prompt arguments, validate every prompts/get request, and use completion without treating suggestions as enforcement.
How MCP clients discover server-provided prompts, supply arguments, and receive role-tagged messages without invoking tools or selecting context.
Trace initialization, verify the HTTP version header, and stop cleanly when client and server share no supported MCP protocol version.