The initialize handshake is negotiated by the SDK with no knowledge of the
server's protocol_versions allowlist, and FastMCP can only veto the handshake,
not steer the negotiated revision. So a server pinned to an older handshake
revision (e.g. ["2024-11-05"]) wrongly refused an ordinary client that
requested a newer handshake revision. Enforce era membership for handshake
versions (exact membership stays for modern per-request versions), refusing
only a genuine cross-era mismatch.
Three defects hidden by tests pinned to the handshake era, where a raw
exception reaches the wire as str(exc). At 2026-07-28 the runner masks
anything that is not an MCPError/ValidationError as "Internal server error".
- _on_read_resource / _on_get_prompt now translate FastMCPError through
to_mcp_error, mirroring _on_call_tool. Masking is unchanged.
- FastMCPProxy registers a server/discover handler so upstream instructions
reach modern clients; on_initialize only fires for the handshake.
- ProxyProvider's list methods normalize transport failures into MCPError.
* Mirror front protocol era onto proxy backend connection
A proxy created from a non-Client target now negotiates, on its backend,
whatever era its front client negotiated, instead of pinning one era.
Explicit create_proxy(mode=...) still overrides. Guards the eager backend
initialize() so an explicit modern pin behind a handshake front no longer
crashes.
* Carry the mirrored proxy era into multi-server config backends
A multi-server MCPConfig target mounts one proxy per configured server on a
composite router, so setting the era on the outer client stopped at the router
and every real backend stayed on its default era. TransportOptions.backend_mode
carries it down, resolved per request alongside the outer mirroring.
The router is also sealed under a policy held on the transport rather than a
fresh per-router ephemeral key, so a guard tool's request_state survives the
router being rebuilt between rounds.
Replace `min_protocol_version` with `protocol_versions`, an allowlist of MCP
protocol versions taking the SDK's own era tuples. Versions are an enumerated
set, not an ordered scalar, so enforcement is set membership rather than a
bound -- which is what makes handshake-only expressible.
Generalize the startup coherence check into a capability -> required-versions
map, and make it silent unless a version set was declared.
* Make the SDK seam the root of FastMCP middleware dispatch (D3)
Notifications, cancellations, and malformed/unroutable messages now reach
on_message/on_request/on_notification at the SDK seam. Component methods keep
their interior dispatch (typed hooks, tool-exception visibility) unchanged; the
seam covers only messages the interior never dispatches, so each hook fires once.
* Document the middleware seam coverage and suspend semantics (D3)
* Align seam docs and ask-visibility test with the result-cycle MRTR model
An InputRequiredResult is the full result of a complete request->response
cycle, not a suspension: component hooks observe an asking round's
InputRequiredToolResult as an ordinary return value.
* Replace 'seam' language with plain dispatch terminology
* Keep the raw middleware __call__ signature; forward middleware message edits
* Cover fires-once across an MRTR continuation round
* Align cherry-picked coverage test with renamed recorder
* Rewrite only the message, never the dispatch destination
* 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
* Fix typos
* Document Cachable* -> Cacheable* rename as v4 breaking change
Adds the response-cache model rename to the change register, per
maintainer decision to skip compatibility aliases in favor of clear
documentation.
* Skip invalid Before import in doc test; use inline codespell ignore
The Cachable* -> Cacheable* breaking-change entry showed the old,
now-invalid import for contrast, which the doc-example test picked up
as a real import and flagged as a regression. Comment out the
deliberately-broken "Before" line (matching the McpError entry just
above it) so only the working "After" import is exercised.
Also swap the blanket codespell ignore-words-list entry for a
narrower inline `codespell:ignore` directive on the one line that
needs it, so codespell keeps flagging "cachable" everywhere else.
---------
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
* Add server-side SEP-990 identity assertion (ID-JAG)
* Test SEP-990 identity assertion token endpoint
* Format identity assertion test
* Document SEP-990 identity assertion
* Thread identity_assertion through OIDCProxy
* Harden ID-JAG: authoritative scopes and grant-type enforcement
Scopes for the issued token now derive only from the signed assertion (or server policy when it omits scopes); the client-supplied request scope may narrow but never widen them. Enforce the registered grant-type constraint the SDK check bypassed, and have proxy DCR add the jwt-bearer grant to registered clients when identity assertion is enabled.
* Harden ID-JAG: honor nbf, reject non-object payload, bound jti cache, preserve required_scopes
* Document per-process ID-JAG replay limitation and nbf check
* Harden ID-JAG round 3: resource indicator, non-object header, algorithm config
- Honor RFC 8707 resource on the jwt-bearer grant (invalid_target on
mismatch), mirroring authorize()'s invariant incl. skip-when-unconfigured
- Reject JSON-array JOSE headers with invalid_grant instead of a 500
- Add IdentityAssertion.algorithm so ES256/PS256 issuers can be verified
(JWTVerifier otherwise defaults to RS256)
* Bind ID-JAG exchange to the assertion's signed client_id and resource
SEP-990: the IdP signs which client and which resource the assertion was
minted for. With public proxy clients the presented client_id is
self-asserted, so the signed binding is what stops client B redeeming
client A's leaked assertion — and the signed resource claim stops an
assertion for server A being redeemed at server B behind the same IdP.
* Harden ID-JAG round 4: check bindings before jti consumption; validate temporal claims, algorithm, and discovery body
- Move the client_id/resource binding checks into the validator itself,
before jti is recorded as consumed, so an assertion presented with the
wrong binding is rejected without burning replay protection for whoever
it actually belongs to
- Reject non-numeric exp/iat/nbf with invalid_grant instead of a 500
- Validate IdentityAssertion.algorithm at config time (must be an
asymmetric JWS algorithm verifiable via JWKS)
- Reject a non-object OIDC discovery body with invalid_grant instead of a 500
- Centralize the resource-URL comparison helpers used by both the
validator and OAuthProxy.authorize()
* Rebase onto httpx2/SDK b2 and harden ID-JAG round 5
- Migrate identity assertion + tests to httpx2 and the local httpx2_mock
(legacy httpx is now banned; pytest-httpx no longer intercepts)
- Add is_optional to the shared httpx2_mock, mirroring pytest-httpx
- Tighten the algorithm allowlist to JWTVerifier's exact supported set
(prefix check accepted typos like RS999 -> 500 on first exchange)
- Reject non-string jti before the cache lookup (unhashable -> 500)
- Track revocation for self-contained ID-JAG tokens: revoke_token records
the jti and load_access_token rejects it until natural expiry
- Dedupe resource-URL helpers: proxy now imports the shared
normalize_resource_url/server_url_has_query from identity_assertion
* Advertise 'none' token-endpoint auth method when ID-JAG is enabled without CIMD
DCR clients are public, so metadata consumers must see 'none' to use the
advertised jwt-bearer grant; previously only the CIMD path added it.
* Document 2026-07-28 protocol support as a distinct feature catalog
SEP-990 identity assertion leads: the SDK provides the wire contract and
provider hook; FastMCP provides the complete server-side implementation.
Inventories the full modern-era capability set for v4.
* Harden ID-JAG round 6: lazy re-export, dual-form audience, per-issuer algorithms, discovery backoff
- IdentityAssertion re-exported lazily from server.auth (the eager import
bypassed the package's documented lazy-import boundary)
- Accept the ID-JAG aud both with and without base_url's trailing slash;
metadata advertises the slashed form, so IdPs echoing it verbatim work
- algorithms={issuer: alg} per-issuer override, mirroring jwks_uris
- OIDC discovery serializes per-issuer and backs off 30s after a failure
(discovery runs pre-signature, so garbage could amplify into HTTP floods)
* Add ResourceSecurity screening for templated resources (defaults on)
* Add tests for resource path-security screening
* Document resource path-security; fix ty in tests
* Carry child template security policy through provider mount
Preserve a mounted template's explicit ResourceSecurity (per-param
exemptions or a deliberate opt-out) through FastMCPProviderResourceTemplate.wrap
so the parent read chokepoint honours it instead of the parent default.
* Defer mcp SDK import so fastmcp.resources loads without the [mcp] extra
* Make resource path-security docs examples self-contained and runnable
* Match exempt_params under both hyphen and underscore spellings
Template placeholders like {git-ref} extract as git_ref, so an exemption
written with the natural URI-template spelling never matched.
* Docs: describe net-depth traversal rule accurately; make example runnable
The screening only rejects .. segments that escape the starting depth
(foo/../bar passes) — saying any standalone .. is rejected overstated
the guarantee. Also define DOCS_ROOT so the example runs.
* Turn OpenTelemetry instrumentation on by default with explicit off-switch
Add FASTMCP_ENABLE_TELEMETRY setting (default true) and mcp.protocol.version
span attribute for SDK parity.
* Make disabled telemetry a transparent pass-through, not a NoOpTracer
The stock NoOpTracer.start_as_current_span attaches a NonRecordingSpan, hijacking the current OTel context from any enclosing application span. When telemetry is disabled, get_tracer() now returns a non-attaching pass-through tracer so trace.get_current_span() inside handlers still resolves to the caller's span.
Tier 2 aggressive-window removal: these shims were deprecated in 3.2,
a shorter deprecation window than the usual policy. Canonical imports
are fastmcp.apps / fastmcp.FastMCPApp.
* Fix broken code examples in docs
- Tag error output blocks as ```text instead of ```python (anthropic,
openai integration docs + v2 mirrors)
- Quote unquoted URL in Descope config example (+ v2 mirror)
- Fix GoogleGenAISamplingHandler → GoogleGenaiSamplingHandler casing
in sampling docs
- Fix import path: handlers.GoogleGenaiSamplingHandler →
handlers.google_genai.GoogleGenaiSamplingHandler in v3-features
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix remaining broken doc examples and add skip tags for false positives
- BearerTokenAuth → StaticTokenVerifier in deployment/http.mdx
- providers.oauth → server.auth import in authentication.mdx
- ListToolsNext → updated list_tools API in v3-features.mdx
- OAuthClientProvider → OAuth in v2/storage-backends.mdx
- Add test="skip" for upgrade guides, contrib placeholders, f-string backticks
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Ratchet doc example baselines to zero
All 1444 examples now pass syntax and import checks.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add pytest-examples dev dep, fix client_id in StaticTokenVerifier example, commit missed openapi fixes
- Add pytest-examples to dev dependencies (fixes CI ModuleNotFoundError)
- Include required client_id in StaticTokenVerifier token payload
- Commit previously unstaged HTTPRoute import fixes in openapi.mdx
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update deprecated import paths across docs
- fastmcp.server.openapi → fastmcp.server.providers.openapi
- fastmcp.server.proxy → fastmcp.server.providers.proxy
- fastmcp.server.apps → fastmcp.apps
- Tag upgrade guide "Before" examples with test="skip"
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add Google GenAI sampling handler
Co-authored-by: Bill Easton <strawgate@users.noreply.github.com>
* chore: Update SDK documentation
* Filter non-Gemini model hints in _get_model
Match the Anthropic/OpenAI handler pattern of only selecting
provider-compatible models from hints.
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Bill Easton <strawgate@users.noreply.github.com>
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
* Add upgrade guides for users coming from the MCP SDK
* Fix incorrect Image import path in LLM migration prompt
* Align LLM prompts with prose across all three upgrade guides
* Move upgrade guides under getting-started/upgrading, add install section and --upgrade flag
* Update repository references from jlowin/fastmcp to prefecthq/fastmcp
* Retrigger CI after repo transfer
* chore: Update SDK documentation
* Only run deep triage on bug issues for jlowin
---------
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
* Overhaul v3.0 upgrade guide
Rewrites the upgrade guide with educational context for each breaking
change, adds an LLM migration prompt users can copy into any AI assistant,
and covers previously missing items (removed constructor kwargs, module
path deprecations, import_server deprecation).
* Address CodeRabbit review feedback on upgrade guide
Split message_path from other transport kwargs (env-var only, not a
run() kwarg), move decorator change to breaking changes in the LLM
prompt since accessing component attributes will crash, and add
DiskStore/OAuth storage change to the prompt's numbered list.
* Move decorator change under Breaking Changes in prose
* Add before/after pattern to auth provider section
* Add Warning callout, WSTransport and OpenAPI migration examples
* Add missing imports to FastMCPOpenAPI migration example
* Reorganize docs navigation and add Apps documentation
Collapse Providers, Transforms, and Deployment under Servers. Add Apps
section with overview and low-level API pages. Add card images to welcome
page and README. Add NEW tags to recent features.
* Fix missing imports in Apps low-level API code examples
* Rename ui= to app= and consolidate ToolUI/ResourceUI into AppConfig
* Remove backward-compat aliases for ToolUI/ResourceUI/ui_to_meta_dict
* Add extra=allow to AppConfig model_config for forward compatibility