Tighten proxy metadata docs

🤖 Generated with OpenAI Codex
This commit is contained in:
Jake Kaplan 2026-08-05 20:33:07 -04:00
commit d08c9d07ee
2 changed files with 10 additions and 36 deletions

View file

@ -312,28 +312,15 @@ Rejection works only **before** `call_next()`. Raising `McpError` afterward logs
#### on_discover
Called when a modern client negotiates through `server/discover`. Like `on_initialize`, this hook receives the typed request and can safely inspect or transform the typed `DiscoverResult` returned by `call_next()`.
Called when a modern client negotiates through `server/discover`. The returned `DiscoverResult` can be modified before it is sent to the client.
```python
import mcp_types
from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext
class DiscoveryMiddleware(Middleware):
async def on_discover(
self,
context: MiddlewareContext[mcp_types.DiscoverRequest],
call_next: CallNext[
mcp_types.DiscoverRequest,
mcp_types.DiscoverResult,
],
) -> mcp_types.DiscoverResult:
result = await call_next(context)
return result.model_copy(update={"instructions": "Custom instructions"})
async def on_discover(self, context, call_next):
result = await call_next(context)
return result.model_copy(update={"instructions": "Custom instructions"})
```
**Returns:** `DiscoverResult` — the transformed value is serialized to the client. Fields such as `supported_versions`, `capabilities`, `ttl_ms`, and `cache_scope` describe the server's public behavior, so middleware should only change them when it also changes that behavior.
Fields such as `supported_versions`, `capabilities`, and cache policy should only be changed when the server's public behavior also changes.
### Raw Handler

View file

@ -388,37 +388,24 @@ Only reuse sessions when you know the backend is stateless (e.g. stateless HTTP)
## Advanced Usage
### Controlled Gateways with Negotiation Metadata
### Forwarding Negotiation Metadata
`ProxyProvider` deliberately owns only remote components. To also forward optional server negotiation metadata, add `ProxyNegotiationMetadataMiddleware` explicitly. This is useful for gateways that need transforms, local components, or other middleware without adopting all of `FastMCPProxy`:
Add `ProxyNegotiationMetadataMiddleware` when a gateway built with `ProxyProvider` should also expose backend instructions and namespaced `_meta`:
```python
from fastmcp import FastMCP
from fastmcp.server.middleware import ProxyNegotiationMetadataMiddleware
from fastmcp.server.providers.proxy import ProxyClient, ProxyProvider
def create_backend_client() -> ProxyClient:
return ProxyClient("http://backend:8000/mcp", mode="auto")
backend = ProxyProvider(create_backend_client)
metadata = ProxyNegotiationMetadataMiddleware(
backend,
identity="proxy",
)
backend = ProxyProvider(lambda: ProxyClient("http://backend:8000/mcp", mode="auto"))
gateway = FastMCP(
"Controlled Gateway",
instructions="Use only approved gateway operations.",
providers=[backend],
middleware=[metadata],
middleware=[ProxyNegotiationMetadataMiddleware(backend)],
)
```
The middleware works across both the legacy `initialize` handshake and modern `server/discover`. It forwards upstream instructions, namespaced `_meta`, and optionally the full upstream `serverInfo`. The frontend remains authoritative for protocol versions, capabilities, cache policy, and `resultType`, since those fields must describe the gateway after its filtering and transforms. Unknown top-level fields are not forwarded because the gateway cannot safely interpret their claims.
`identity="proxy"` (the default) retains the gateway's `serverInfo`; use `identity="upstream"` to expose the backend's complete implementation identity. Explicit frontend instructions and frontend metadata win on collision. Metadata is fetched lazily during negotiation, and an unavailable backend does not fail negotiation solely because optional metadata could not be read—the first proxied operation reports that failure.
By default the gateway keeps its own `serverInfo`; pass `identity="upstream"` to expose the backend identity. Frontend values win on collisions, and protocol versions, capabilities, cache policy, `resultType`, and unknown top-level fields always remain frontend-owned. If the backend is unavailable, negotiation succeeds without its optional metadata.
### FastMCPProxy Class