diff --git a/src/fastmcp/server/providers/proxy.py b/src/fastmcp/server/providers/proxy.py index 5d2ea562f..8fc1da93e 100644 --- a/src/fastmcp/server/providers/proxy.py +++ b/src/fastmcp/server/providers/proxy.py @@ -642,6 +642,17 @@ def _create_client_factory( """ if isinstance(target, Client): client = target + if client.is_connected() and type(client) is ProxyClient: + logger.info( + "Proxy detected connected ProxyClient - creating fresh sessions for each " + "request to avoid request context leakage." + ) + + def fresh_client_factory() -> Client: + return client.new() + + return fresh_client_factory + if client.is_connected(): logger.info( "Proxy detected connected client - reusing existing session for all requests. " @@ -652,12 +663,11 @@ def reuse_client_factory() -> Client: return client return reuse_client_factory - else: - def fresh_client_factory() -> Client: - return client.new() + def fresh_client_factory() -> Client: + return client.new() - return fresh_client_factory + return fresh_client_factory else: # target is not a Client, so it's compatible with ProxyClient.__init__ base_client = ProxyClient(cast(Any, target)) diff --git a/tests/server/providers/proxy/test_proxy_client.py b/tests/server/providers/proxy/test_proxy_client.py index 7b3bb19b9..52db25cc6 100644 --- a/tests/server/providers/proxy/test_proxy_client.py +++ b/tests/server/providers/proxy/test_proxy_client.py @@ -17,7 +17,7 @@ from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingParams from fastmcp.exceptions import ToolError from fastmcp.server.elicitation import AcceptedElicitation -from fastmcp.server.providers.proxy import ProxyClient +from fastmcp.server.providers.proxy import ProxyClient, _create_client_factory @pytest.fixture @@ -419,12 +419,20 @@ async def test_client_factory_creates_fresh_sessions(self, fastmcp_server: FastM assert hasattr(proxy_via_as_proxy, "_local_provider") assert hasattr(proxy_via_factory, "_local_provider") - async def test_connected_client_reuses_sessions(self, fastmcp_server: FastMCP): - """Test that connected clients passed to as_proxy reuse sessions (preserves #959 behavior).""" - # Create a connected client (should reuse sessions) - async with Client(fastmcp_server) as connected_client: - proxy = FastMCP.as_proxy(connected_client) - - # Verify the proxy is created successfully and uses session reuse - assert proxy is not None - assert hasattr(proxy, "_local_provider") + async def test_connected_proxy_client_uses_fresh_sessions( + self, fastmcp_server: FastMCP + ): + """Connected ProxyClient targets should create fresh sessions to avoid stale context.""" + async with ProxyClient(fastmcp_server) as connected_client: + factory = _create_client_factory(connected_client) + + client_a = factory() + client_b = factory() + + assert isinstance(client_a, Client) + assert isinstance(client_b, Client) + assert client_a is not connected_client + assert client_b is not connected_client + assert client_a is not client_b + assert not client_a.is_connected() + assert not client_b.is_connected()