From eb52de3b3811bdde7cd5cedd2bf9d2b98493c40e Mon Sep 17 00:00:00 2001 From: Jake Kaplan Date: Thu, 6 Aug 2026 09:06:53 -0400 Subject: [PATCH] Preserve deprecated initialization middleware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with OpenAI Codex --- .../fastmcp/server/providers/proxy.py | 60 +++++++++++++++++-- .../providers/proxy/test_server_metadata.py | 13 +++- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/fastmcp_slim/fastmcp/server/providers/proxy.py b/fastmcp_slim/fastmcp/server/providers/proxy.py index 1962141c4..3002d08b6 100644 --- a/fastmcp_slim/fastmcp/server/providers/proxy.py +++ b/fastmcp_slim/fastmcp/server/providers/proxy.py @@ -1155,8 +1155,8 @@ class ProxyMetadataMiddleware(Middleware): return result.model_copy(update=self._updates(result, upstream)) -class ProxyInitializeMiddleware(ProxyMetadataMiddleware): - """Deprecated compatibility name for ``ProxyMetadataMiddleware``.""" +class ProxyInitializeMiddleware(Middleware): + """Deprecated middleware for forwarding instructions during initialization.""" def __init__(self, proxy: FastMCPProxy) -> None: warnings.warn( @@ -1167,8 +1167,60 @@ class ProxyInitializeMiddleware(ProxyMetadataMiddleware): stacklevel=2, ) self.proxy = proxy - self.client_factory = proxy.client_factory - self.identity: ProxyIdentity = "proxy" + + async def on_initialize( + self, + context: MiddlewareContext[mcp_types.InitializeRequest], + call_next: CallNext[ + mcp_types.InitializeRequest, + mcp_types.InitializeResult | None, + ], + ) -> mcp_types.InitializeResult | None: + client = await self.proxy._get_client() + upstream_instructions: str | None = None + try: + if isinstance(client, ProxyClient): + ctx = context.fastmcp_context + if ctx is not None: + client._proxy_rc_ref[0] = ( + ctx.request_context, + ctx._fastmcp, + ) + async with client: + # Entering the context already ran connect-time negotiation. + # `initialize()` returns the handshake result on a legacy backend, + # but raises on a modern (server/discover) backend, which has no + # InitializeResult. That mismatch only arises when an explicit + # `mode=` pins the backend to a different era than this legacy + # front (the era-mirroring default keeps the two in lockstep, so + # a legacy front always reaches a legacy backend here). Skip the + # handshake-only call when the backend negotiated the modern era. + if client.protocol_version not in MODERN_PROTOCOL_VERSIONS: + await client.initialize() + # Capture the upstream's instructions while the session is + # live; `initialize_result` clears once the context exits. + init_result = client.initialize_result + if init_result is not None: + upstream_instructions = init_result.instructions + except MCPError: + raise + except _PROXY_TRANSPORT_ERRORS as error: + raise _proxy_upstream_error(error) from error + + 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_server_metadata.py b/tests/server/providers/proxy/test_server_metadata.py index 2ba73948c..20da63bab 100644 --- a/tests/server/providers/proxy/test_server_metadata.py +++ b/tests/server/providers/proxy/test_server_metadata.py @@ -387,9 +387,11 @@ def test_gateway_construction_does_not_create_backend_client(): assert calls == 0 -def test_proxy_initialize_middleware_is_deprecated(): +async def test_proxy_initialize_middleware_preserves_legacy_behavior(): + upstream = FastMCP("upstream", instructions="legacy instructions") + def client_factory() -> ProxyClient: - return ProxyClient(make_upstream()) + return ProxyClient(upstream) proxy = FastMCPProxy(name="compatibility-proxy", client_factory=client_factory) @@ -399,8 +401,13 @@ def test_proxy_initialize_middleware_is_deprecated(): ): middleware = ProxyInitializeMiddleware(proxy) + proxy.middleware = [middleware] + async with Client(proxy, mode="legacy") as client: + assert client.instructions == "legacy instructions" + async with Client(proxy, mode="auto") as client: + assert client.instructions is None + assert middleware.proxy is proxy - assert middleware.client_factory is client_factory async def test_fastmcp_proxy_uses_public_metadata_middleware():