mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
* Add guard-mode MRTR server support (SEP-2322) * Add server-side MRTR guard tests * Add MRTR guard docs, exports, and output-schema handling * Apply formatting to MRTR guard changes * Fix MRTR review round 1: middleware-safe suspend, Annotated strip, stable audience - ToolInputRequired subclasses BaseException (CancelledError precedent) so error middleware's broad except Exception cannot swallow a suspension - Strip InputRequiredResult arms inside Annotated return types - Reject a custom RequestStateSecurity without a stable audience (random per-replica server names would break shared-key verification) * Fix static analysis: rewrite tuple([...]) as tuple literal (C409) * Recognize InputRequiredResult inside Annotated union arms _is_input_required_type now peels Annotated first, so a metadata-carrying guard arm (str | Annotated[InputRequiredResult, Field(...)]) is stripped and the data arm's output schema survives. * docs: frame multi-round tools as elicitation on the modern protocol Fold multi-round-tools.mdx into elicitation.mdx as two eras of one capability; drop pause/suspend framing for the stateless per-round model. * Transport MRTR asks as InputRequiredToolResult, not a raised signal An input-required result is the full result of a stateless MRTR leg, so it flows through the middleware chain as an ordinary ToolResult subclass instead of a raised ToolInputRequired(BaseException). Middleware observes it, caching skips it, and response-limiting leaves it untouched. * Document MRTR middleware interaction and the isinstance pattern * Update MRTR change-register verify note to InputRequiredToolResult * Align test module docstring with result-cycle framing * Fix MRTR review: bypass cache on continuation legs; soften audience guard - ResponseCachingMiddleware skips read AND write on continuation legs: the cache key is name+arguments only, so a continuation's final result would be served to later fresh calls, which would never be asked - The stable-audience check is a warning, not an error: a policy object cannot reveal whether its keys are shared, and single-process customization (ephemeral ttl, custom codec) is legitimate unnamed * Treat state-only rounds as continuations in the response cache A round carrying request_state but no questions retries with input_responses=None; request_state alone must bypass the cache or its terminal result is stored under the fresh-call key. * Fix MRTR review round: preserve asks through transforms, empty-name audience, docs predicate - TransformedTool.run returns an InputRequiredToolResult intact instead of reshaping it into an empty ToolResult for non-object output schemas - audience warning uses a falsy-name check (empty string also autogenerates a per-replica name) - the elicitation docs continuation predicate checks request_state too * Add create_proxy(mode=) opt-in for guard round-tripping through proxies An auto-created proxy client stays handshake-era by default (a dual-era backend serves both, and one proxy session is one era; handshake preserves server-initiated push forwarding). Pass create_proxy(target, mode="auto") to negotiate modern so an upstream guard's InputRequiredResult round-trips — the two are mutually exclusive per session. * Wrap raw InputRequiredResult returned by a transform_fn A custom transform function may return the raw ask directly, like any tool body — wrap it into InputRequiredToolResult so it survives output normalization and reaches the wire, not only pre-wrapped forwarded guards. * Reject input-required results from background tasks * Unwrap type aliases before stripping guard arms * Apply ruff format * Recursively strip guard arms through nested and composed aliases * Reflect MRTR continuation fields on the middleware message * Suppress output schema for InputRequiredResult subclasses * Forward progress on modern proxy tool calls * Suppress output schema for bare aliased guard returns * Suppress output schema for any surviving guard return wrapping
53 lines
6.5 KiB
Text
53 lines
6.5 KiB
Text
---
|
|
title: 2026-07-28 Protocol Support
|
|
---
|
|
|
|
FastMCP v4 serves the sessionless `2026-07-28` protocol era and the session-based handshake eras from a single server, with per-connection auto-detection. This page catalogs what FastMCP provides for the modern era — both the protocol machinery it inherits from the MCP Python SDK and the capabilities FastMCP implements itself on top of that layer. It is the reference for what a v4 deployment can actually do on the modern protocol today.
|
|
|
|
## Identity assertion (SEP-990): a complete server-side implementation
|
|
|
|
SEP-990 defines enterprise "on-behalf-of" access: a corporate identity provider (Okta, Microsoft Entra, etc.) issues a signed *ID-JAG* asserting an employee's identity, the employee's agent presents it at the MCP authorization server's token endpoint via the RFC 7523 `jwt-bearer` grant, and receives a short-lived access token — no browser login, no per-user consent screen, and revocation lives at the IdP.
|
|
|
|
The protocol layer for this flow — grant parsing, the `exchange_identity_assertion` provider hook, and metadata advertisement — comes from the SDK. The validation and issuance logic that makes the flow actually work is FastMCP's implementation, and enabling it is one parameter on the existing auth providers:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.auth import OAuthProxy, IdentityAssertion
|
|
|
|
auth = OAuthProxy(
|
|
..., # existing upstream configuration unchanged
|
|
identity_assertion=IdentityAssertion(
|
|
trusted_issuers=["https://login.acme-corp.com"],
|
|
),
|
|
)
|
|
mcp = FastMCP("Internal API", auth=auth)
|
|
```
|
|
|
|
Behind that one parameter, FastMCP performs the full SEP-990 §5.1 / RFC 7523 §3 processing: JWKS-based signature verification with automatic OIDC discovery of issuer keys, `typ`/`iss`/`aud`/`sub` validation, temporal checks (`exp`, `iat`, `nbf`, maximum assertion lifetime), enforcement of the assertion's signed `client_id` and `resource` bindings, `jti` replay rejection, scope derivation from the signed assertion (client requests can narrow but never widen), short-lived token issuance with no refresh token, and revocation tracking for the issued tokens. The asserted subject flows into the normal FastMCP auth context, so tools read it through `get_access_token()` like any other identity. See [Identity Assertion](/servers/auth/oauth-proxy#identity-assertion-sep-990) for the full documentation.
|
|
|
|
This slots into FastMCP's existing authorization-server stack — the OAuth proxy's dynamic client registration, the consent flow, and self-issued JWTs — which is what makes a one-parameter enterprise deployment possible.
|
|
|
|
## Modern-era capability inventory
|
|
|
|
The complete picture of what a FastMCP v4 server and client provide on the `2026-07-28` era:
|
|
|
|
| Capability | What FastMCP provides |
|
|
| --- | --- |
|
|
| **Dual-era serving** | One server answers both `server/discover` (modern, sessionless) and `initialize` (handshake) connections, auto-detected per connection. Any replica behind a plain load balancer can answer a modern request. |
|
|
| **Identity assertion (SEP-990)** | Complete server-side implementation, one parameter to enable (above). |
|
|
| **Authorization server** | Full AS stack: `OAuthProxy` bridges DCR-expecting MCP clients to non-DCR enterprise IdPs, ~18 built-in providers, consent UI, self-issued JWTs, protected-resource metadata (RFC 9728). |
|
|
| **Cache hints (SEP-2549)** | Server-level authoring (`FastMCP(cache_ttl=..., cache_scope=...)`) stamps every cacheable result; the FastMCP client honors hints with an opt-in response cache. |
|
|
| **Distributed response caching** | `KeyValueResponseCacheStore` backs the client cache with any key-value store (Redis, memory, filetree), so a fleet of clients or proxy replicas shares cache fills across processes. |
|
|
| **Resource path security** | Templated resource parameters are screened for traversal, absolute paths, and null bytes before handlers run — on by default, including provider-sourced and mounted templates. |
|
|
| **Client protocol negotiation** | `Client(mode="auto")` probes `server/discover` and falls back to the classic handshake; the client answers multi-round-trip `input_required` requests through its existing handlers. |
|
|
| **Elicitation on the modern protocol (SEP-2322)** | Tools request user input via multi-round trips: a tool returns an `InputRequiredResult` and re-runs per round, reading the client's answers off `ctx.input_responses` / `ctx.request_state` (the [guard pattern](/servers/elicitation#elicitation-on-the-modern-protocol)). Each round is a complete request→response cycle; the framework seals `request_state` on the wire and unseals it before the tool runs, and a shared-key `request_state_security` policy carries state across replicas. On handshake-era connections returning this result produces a clear era error. |
|
|
| **Spec-standard errors (SEP-2164)** | Missing-resource reads return `-32602`; push-feature calls on modern connections fail with clear era-specific errors rather than generic method-not-found. |
|
|
| **Background tasks** | `@mcp.tool(task=True)` runs on a Redis-backed distributed runtime (Docket) with cross-replica notifications — execution infrastructure that is FastMCP's own, independent of the protocol-era task surface. |
|
|
| **Middleware** | Typed per-method hooks (`on_call_tool`, `on_list_tools`, …) and a suite of built-ins (auth, rate limiting, caching, error handling, logging, timing, and more). |
|
|
| **Composition** | `mount()`, providers, proxying, and tool transforms compose servers dynamically at runtime, with lifespans and middleware driven through the SDK session manager. |
|
|
| **Pagination** | Declarative `FastMCP(list_page_size=...)` paginates all list operations in the high-level server; the client auto-paginates with cycle detection. |
|
|
| **Telemetry** | OpenTelemetry spans on by default (no-op without an exporter), SDK-aligned attributes (`mcp.method.name`, `mcp.protocol.version`, `gen_ai.*`), plus auth and provider-delegation spans; `FASTMCP_ENABLE_TELEMETRY=false` disables cleanly. |
|
|
|
|
## Still in the program
|
|
|
|
Elicitation on the modern protocol is now shipped in its **guard form** — a tool returns an `InputRequiredResult` and re-runs per round to gather user input via multi-round trips (see [Elicitation on the modern protocol](/servers/elicitation#elicitation-on-the-modern-protocol)). The declarative `Resolve(...)` layer over that primitive remains staged, tracked in the [Feature Program](/development/v4-notes/feature-program), along with the unified `subscriptions/listen` stream. The [Known Gaps](/development/v4-notes/known-gaps) page tracks the upstream dependencies that gate them.
|