diff --git a/fastmcp_slim/fastmcp/server/providers/proxy.py b/fastmcp_slim/fastmcp/server/providers/proxy.py index fd0817af1..29e6d6c19 100644 --- a/fastmcp_slim/fastmcp/server/providers/proxy.py +++ b/fastmcp_slim/fastmcp/server/providers/proxy.py @@ -106,6 +106,7 @@ class ProxyInitializeMiddleware(Middleware): ], ) -> mcp_types.InitializeResult | None: client = await self.proxy._get_client() + upstream_instructions: str | None = None try: if isinstance(client, ProxyClient): ctx = context.fastmcp_context @@ -116,6 +117,11 @@ class ProxyInitializeMiddleware(Middleware): ) async with client: await client.initialize() + # Capture the upstream's instructions while the session is live; + # `initialize_result` clears once the client context exits. + init_result = client.initialize_result + if init_result is not None: + upstream_instructions = init_result.instructions except MCPError: raise except ( @@ -128,7 +134,20 @@ class ProxyInitializeMiddleware(Middleware): ) as error: raise _proxy_upstream_error(error) from error - return await call_next(context) + result = await call_next(context) + + # Forward the upstream server's instructions unless the proxy defines its + # own. `instructions` is part of the MCP InitializeResult and is meant to + # steer the model, so a proxy that dropped it would silently degrade any + # downstream consumer relying on upstream guidance. + if ( + result is not None + and self.proxy.instructions is None + and upstream_instructions is not None + ): + result.instructions = upstream_instructions + + return result # ----------------------------------------------------------------------------- diff --git a/tests/server/providers/proxy/test_proxy_server.py b/tests/server/providers/proxy/test_proxy_server.py index 762a09abc..9f1579b45 100644 --- a/tests/server/providers/proxy/test_proxy_server.py +++ b/tests/server/providers/proxy/test_proxy_server.py @@ -189,6 +189,36 @@ async def test_create_proxy_with_transport(fastmcp_server): assert result.data == "Hello, Test!" +async def test_proxy_forwards_upstream_instructions(): + """A proxy should surface the upstream server's instructions in the handshake.""" + upstream = FastMCP(name="upstream", instructions="USE_THIS_MARKER_123") + proxy = create_proxy(upstream, name="proxy") + + async with Client(proxy) as client: + assert client.initialize_result is not None + assert client.initialize_result.instructions == "USE_THIS_MARKER_123" + + +async def test_proxy_own_instructions_take_precedence(): + """Instructions explicitly set on the proxy override the upstream's.""" + upstream = FastMCP(name="upstream", instructions="upstream instructions") + proxy = create_proxy(upstream, name="proxy", instructions="proxy instructions") + + async with Client(proxy) as client: + assert client.initialize_result is not None + assert client.initialize_result.instructions == "proxy instructions" + + +async def test_proxy_instructions_none_when_upstream_has_none(): + """A proxy over an upstream without instructions reports no instructions.""" + upstream = FastMCP(name="upstream") + proxy = create_proxy(upstream, name="proxy") + + async with Client(proxy) as client: + assert client.initialize_result is not None + assert client.initialize_result.instructions is None + + def test_create_proxy_with_url(): """create_proxy should accept a URL without connecting.""" proxy = create_proxy("http://example.com/mcp/")