MCP Python SDK WebSocket Servers Need an Explicit Origin Gate Even After CVE-2026-59950 Patch

A missing origin check in the official Model Context Protocol Python SDK allowed a hostile webpage to connect to a reachable MCP WebSocket server, complete the protocol handshake, enumerate tools, invoke them, and read exposed resources. The browser supplied an Origin header, but the deprecated server transport accepted the connection without inspecting it.

The issue is tracked as CVE-2026-59950 and GHSA-vj7q-gjh5-988w. It affects the mcp package before 1.28.1. NVD currently scores it 8.1 High under CVSS 3.1, with no privileges required and user interaction represented by the victim visiting a malicious page. The direct impact depends on the tools and resources attached to the server.

The operational trap is unusually important: upgrading to 1.28.1 adds the missing validation hook, but does not turn validation on. Applications must pass a configured TransportSecuritySettings, or migrate away from the deprecated WebSocket transport. Treat 1.28.1 as the version that makes a secure configuration possible—not as an automatic boundary repair.

A webpage could speak MCP to localhost

Browsers do not apply the HTTP same-origin response policy to WebSocket connections in the way developers often expect. A page from an attacker-controlled origin can attempt a WebSocket upgrade to a service on localhost or a reachable LAN address. The server must decide whether the requesting origin and destination host are acceptable before it accepts the handshake.

In vulnerable SDK versions, mcp.server.websocket.websocket_server constructed a Starlette WebSocket and immediately called accept(subprotocol="mcp"). It had no SDK-level option to apply the Host and Origin validation already available to the SSE and Streamable HTTP transports. Once connected, browser JavaScript could send the ordinary MCP initialization and JSON-RPC requests.

This is a cross-site WebSocket hijacking path rather than a bypass of MCP authorization. The affected transport itself required no token or existing session. If an application placed authentication, a reverse proxy, or its own origin enforcement in front of the endpoint, that separate control could still stop the attack. Some browsers also prompt before a public webpage reaches a private-network address, but the maintainer advisory correctly warns that client-side prompting is not a substitute for server policy.

The affected population is narrower than the package range

The package version range alone can overstate exposure. The vulnerable WebSocket transport was never part of the MCP specification, was already marked deprecated, and is not reachable through FastMCP. A developer had to import websocket_server and wire it into an ASGI application. Servers using stdio, SSE, or Streamable HTTP are not affected by this specific advisory.

That makes code-level inventory more valuable than a package scanner result by itself. A dependency report can identify mcp<1.28.1, but responders still need to search application code, deployment manifests, ingress routes, and runtime listeners for the deprecated transport. Conversely, a scanner that marks 1.28.1 as simply “fixed” can miss an application that upgraded while leaving the new security settings unset.

The vulnerability also illustrates why a loopback bind is not authentication. Localhost blocks direct connections from remote machines, but a browser running on that host can become the network bridge. The security decision has to consider who caused the browser to initiate the connection, which is what the WebSocket Origin header communicates.

Version 1.28.1 adds an opt-in pre-handshake check

The June 26 patch gives websocket_server() an optional security_settings argument and runs the existing TransportSecurityMiddleware before accepting the WebSocket. With DNS-rebinding protection enabled and suitable allowed_hosts and allowed_origins, a mismatch is closed before acceptance and presented by the ASGI server as HTTP 403.

Tests added with the patch verify both sides of that contract: disallowed origins and hosts are rejected, allowed origins connect, and the default configuration still accepts any origin. The pull request explicitly describes that default as preserving existing behavior and avoiding a breaking change. This backwards-compatibility choice is why version-only remediation is incomplete.

The maintainer-recommended path is to migrate to Streamable HTTP, where FastMCP enables DNS-rebinding protection automatically for localhost binds. The deprecated WebSocket transport has been removed entirely in version 2. Applications that cannot migrate immediately should enable the 1.28.1 security settings and keep an independent authentication and network policy in front of consequential tools.

Defensive engineering guidance

  • Find actual transport use. Search for imports and calls to mcp.server.websocket.websocket_server, Starlette WebSocket routes that wrap it, and exposed ws:// or wss:// MCP endpoints. Do not infer exposure from the dependency version alone.
  • Prefer migration over preservation. Move affected applications to the supported Streamable HTTP transport or to MCP Python SDK v2, where the deprecated WebSocket implementation no longer exists.
  • If temporarily staying on v1, upgrade and configure. Install mcp>=1.28.1, pass TransportSecuritySettings, enable DNS-rebinding protection, and restrict both allowed_hosts and allowed_origins to the exact deployment.
  • Put authentication in front of tools. Origin validation answers which website initiated a browser connection; it does not establish user identity or authorization. Require credentials and enforce per-tool permissions independently.
  • Constrain the blast radius. Run MCP servers with least-privilege service accounts, narrow filesystem access, bounded network egress, and approval gates for destructive or high-value tool calls.
  • Test from a browser context. Include a page hosted on an unapproved origin in regression testing. Confirm the WebSocket upgrade is rejected before MCP initialization and confirm approved clients still work.
  • Review telemetry for unexpected origins. Where proxies or application logs retain WebSocket upgrade headers, look for public-site origins connecting to loopback, workstation, development, or internal MCP endpoints.

CVE-2026-59950 is a small transport-layer omission with an agent-sized consequence: once a browser reaches an MCP session, the available actions are whatever authority the application placed behind that session. The lasting lesson is not merely to compare a package version. Verify that the origin gate is active, authenticate the caller, and keep the tool boundary narrow enough that one accepted connection cannot become ambient authority over the host.

Sources: