Development Choices

Validate Origins on MCP HTTP Servers

Author
Joseph TrasattiMember of technical staff
Published
Section
MCP
Length
6 min read3 sources cited

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:

You also need an authentication mechanism for any HTTP endpoint exposed beyond the local process. Origin validation cannot replace it.

Configure the server

An MCP HTTP request passes origin validation and authentication before the protocol handler
Origin and identity checks solve different threats and both happen first.
  1. Set the narrowest listening address

    For a local MCP server, bind the listener to loopback, normally 127.0.0.1. Do not bind to 0.0.0.0 unless access through other network interfaces is intentional. Loopback limits which machines can establish a connection; 0.0.0.0 listens 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.

  2. 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:3000

    Treat those as three different origins. Do not reduce them to a hostname comparison: changing http to https, changing the hostname, or changing a non-default port changes the origin. A path such as /settings does not belong in the allowlist because the header will not contain it.

    Handle the literal value null as an origin value, not as proof that the request is local. MDN documents browser situations that produce Origin: 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.

  3. 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 succeeds

    If the Origin header is present and invalid, the MCP transport specification requires HTTP 403 Forbidden. It permits the response body to contain a JSON-RPC error without an id, 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 Origin header 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.

  4. 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 Forbidden at 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.

  5. 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: null is 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, not 0.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.

  6. 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

  1. MCP Streamable HTTP transport specification dated 2025-11-25modelcontextprotocol.io
  2. MCP security best practices dated 2025-11-25modelcontextprotocol.io
  3. MDN’s Origin header reference, last modified August 7, 2026developer.mozilla.org

See also