mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 14:04:18 +02:00
Stop forwarding inbound HTTP headers to unrelated remote servers (#3837)
This commit is contained in:
parent
d0bcec979c
commit
95102c7d7c
3 changed files with 40 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue