Stop forwarding inbound HTTP headers to unrelated remote servers (#3837)

This commit is contained in:
Jeremiah Lowin 2026-04-11 12:14:38 -04:00 committed by GitHub
commit 95102c7d7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 40 additions and 10 deletions

View file

@ -94,6 +94,8 @@ class StreamableHttpTransport(ClientTransport):
)
self.sse_read_timeout = normalize_timeout_to_timedelta(sse_read_timeout)
self.forward_incoming_headers: bool = False
self._get_session_id_cb: Callable[[], str | None] | None = None
def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
@ -148,10 +150,14 @@ class StreamableHttpTransport(ClientTransport):
async def connect_session(
self, **session_kwargs: Unpack[SessionKwargs]
) -> AsyncIterator[ClientSession]:
# Load headers from an active HTTP request, if available. This will only be true
# if the client is used in a FastMCP Proxy, in which case the MCP client headers
# need to be forwarded to the remote server.
headers = get_http_headers(include={"authorization"}) | self.headers
# When used in a proxy, forward the inbound request's authorization
# header to the upstream server. This is off by default so that a
# plain Client used inside a server tool handler doesn't accidentally
# leak the caller's credentials to an unrelated remote server.
if self.forward_incoming_headers:
headers = get_http_headers(include={"authorization"}) | self.headers
else:
headers = dict(self.headers)
# Configure timeout if provided, preserving MCP's 30s connect default
timeout: httpx.Timeout | None = None

View file

@ -61,6 +61,8 @@ class SSETransport(ClientTransport):
self._set_auth(auth)
self.forward_incoming_headers: bool = False
self.sse_read_timeout = normalize_timeout_to_timedelta(sse_read_timeout)
def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
@ -117,12 +119,16 @@ class SSETransport(ClientTransport):
) -> AsyncIterator[ClientSession]:
client_kwargs: dict[str, Any] = {}
# load headers from an active HTTP request, if available. This will only be true
# if the client is used in a FastMCP Proxy, in which case the MCP client headers
# need to be forwarded to the remote server.
client_kwargs["headers"] = (
get_http_headers(include={"authorization"}) | self.headers
)
# When used in a proxy, forward the inbound request's authorization
# header to the upstream server. This is off by default so that a
# plain Client used inside a server tool handler doesn't accidentally
# leak the caller's credentials to an unrelated remote server.
if self.forward_incoming_headers:
client_kwargs["headers"] = (
get_http_headers(include={"authorization"}) | self.headers
)
else:
client_kwargs["headers"] = dict(self.headers)
# sse_read_timeout has a default value set, so we can't pass None without overriding it
# instead we simply leave the kwarg out if it's not provided

View file

@ -752,6 +752,15 @@ def _create_client_factory(
"""
if isinstance(target, Client):
client = target
# Plain Clients used as proxy backends also need header forwarding,
# same as ProxyClient (which sets this in __init__).
from fastmcp.client.transports.http import StreamableHttpTransport
from fastmcp.client.transports.sse import SSETransport
if isinstance(client.transport, StreamableHttpTransport | SSETransport):
client.transport.forward_incoming_headers = True
if client.is_connected() and type(client) is ProxyClient:
logger.info(
"Proxy detected connected ProxyClient - creating fresh sessions for each "
@ -997,6 +1006,15 @@ class ProxyClient(Client[ClientTransportT]):
kwargs["progress_handler"] = default_proxy_progress_handler
super().__init__(**kwargs | {"transport": transport})
# Enable forwarding of inbound HTTP headers (e.g. authorization) to
# the upstream server. This is only appropriate for proxy clients,
# where the caller's credentials should be propagated.
from fastmcp.client.transports.http import StreamableHttpTransport
from fastmcp.client.transports.sse import SSETransport
if isinstance(self.transport, StreamableHttpTransport | SSETransport):
self.transport.forward_incoming_headers = True
class StatefulProxyClient(ProxyClient[ClientTransportT]):
"""A proxy client that provides a stateful client factory for the proxy server.