Preserve deprecated initialization middleware

🤖 Generated with OpenAI Codex
This commit is contained in:
Jake Kaplan 2026-08-06 09:06:53 -04:00
commit eb52de3b38
2 changed files with 66 additions and 7 deletions

View file

@ -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
# -----------------------------------------------------------------------------

View file

@ -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():