* Signal component-level scope shortfalls as insufficient_scope (SEP-2350)
* Fix ty type narrowing in scope step-up test
* Respect check short-circuit when reporting scope shortfall (P2)
* Report union of unmet scopes and document step-up contract
* Aggregate scope shortfall across the AuthMiddleware chain
* Stop chain scope aggregation at the first unevaluated gate
* Unpublish v4 development notes; prep docs for beta 1
* Nest development notes under dev-docs/
* Rewrite site-root links in dev notes as absolute URLs for GitHub rendering
Server-side completions (@mcp.completion) shipped in #4582 but the
What's New page didn't mention it. Adds it to the authoring-capabilities
cluster with a runnable example and a link to the servers/completions page.
* v4 docs quality pass: fix stale task/era claims, broken links, writing polish
* whats-new: add the client-side protocol negotiation story
The page told the server half of the era story (serves every era) but
never the client half — that a default Client(url) now negotiates the
modern era, where earlier versions pinned the handshake. Completes the
mental model and links to the client negotiation docs.
* Address review: drop 'complete' over-claim; link mounted-state to Session State
* Fix stale Mac/Windows-vs-Linux OAuth key/storage docs
#2223 replaced platform-aware keyring/MemoryStore defaults with
deterministic key derivation and an always-on-disk encrypted store,
but the docs update in that PR missed several spots.
* Fix OIDCProxy doc referring to internal upstream_client_secret name
Codex review: the public OIDCProxy constructor takes client_secret;
upstream_client_secret is only OAuthProxy's internal parameter name.
Five review fixes. ctx.session_id / get_state / set_state now work in a Docket
worker by falling back to the snapshotted session id. Task management wire calls
(submission, tasks/get/update/cancel) create client spans and propagate trace
context. TasksClientSettings loads .env like DocketSettings, and the docs use
its real env var name. A state-only guard round (request_state, no input
requests) fails with a clear error instead of silently completing wrong.
* Archive v3 docs under /v3 and publish v4 as the primary version
* Label primary docs version v4.0.0 (alpha 1)
* Add What's New in v4 page; fix upgrade-guide phrasing; point banner at What's New
* Rewrite What's New around v4's new capabilities, not the sampling deprecation
* Lead What's New with the SDK v2 engine swap and the SEPs it brings
* State ships now (link Session State); tasks arrive next alpha
* Exclude docs/v3 frozen snapshots from doc-example import validation
The example README pointed at github.com/PrefectHQ/docket (404); the canonical
repo is chrisguidry/docket. Point the docs' Docket-docs link at the canonical
docket.lol.
* Design doc: stateless session state
* Add stateless session-state primitives: Scope, SessionCodec, scoped state
* Add SessionProvider and Session() annotation for stateless session state
* Rewrite session-state design to final shape (Session object, two patterns, no seal)
* Rework stateless session state to final Session/SessionId design
Remove Scope, SessionCodec/sealing, and scoped ctx.get_state. Add the
Session object (get/set/delete/clear over one dict per (principal,
session_id) key), injected session: Session (keyed by principal, requires
auth), session_id: SessionId argument with auto-filled description, and
SessionProvider contributing create_session/end_session.
* Rename injected marker to UserSession; auto-wire SessionProvider on SessionId
* Document stateless session state as a v4 feature
* Require SessionProvider and make sessions create-then-validate
Remove the implicit SessionProvider auto-wiring; a SessionProvider must now be
registered explicitly. create_session records an owned session and get_session
validates the id, rejecting uncreated or foreign ids.
* Add Session.id (public id for session_id sessions, None for UserSession)
* Fix ty: narrow Tool | None and ToolResult.structured_content in session tests
* Fix session-provider enforcement gap for non-local tools; stop embedding raw principal in UserSession key
* Fix disabled session_id tools blocking listing; reject local tools shadowing SessionProvider lifecycle names
* Decouple SessionId description from lifecycle tool name so it survives namespaced mounts
* Remove SessionProvider enforcement; get_session validation is the guarantee
* Fix stale enforcement/key-format docs; document store-owned session TTL
* Dedup SessionId contract description; tighten context.mdx session-state lead
* Make session store/description resolution work in Docket task workers and for partial tools
* Expose get_session as a standalone task-safe function; drop foreground-only Context.get_session
* Move get_session to dependencies alongside the other request accessors
* Reframe context state docs as Request State; cross-request persistence points to Session State
* Address UserSession injection edge cases from review
- inject a UserSession instance (not bare Session) so isinstance holds
- support session: UserSession | None = None (inject None when unauth)
- detect SessionId params past a partial's positional binding
- Client task support is opt-in via importing fastmcp_tasks (drop the core
auto-load of companion packages); a plain Client never advertises tasks.
- A worker restores the submitting caller's auth token and headers from the
task snapshot into the standard ambient context, so get_access_token() /
get_http_headers() work in a distributed worker with no new core hooks.
- worker_cli validates the loaded extension's resolved backend, not env defaults,
so a constructor-configured Redis worker starts.
- Thread the per-call read timeout through task polling; bound ToolTask.wait by
its deadline; set_elicitation_callback rebuilds internal extensions so a
later-set handler answers in-task input.
- README imports TaskConfig from fastmcp.utilities.tasks.
Co-Authored-By: Claude <noreply@anthropic.com>
Server (servers/tasks.mdx) and client (clients/tasks.mdx) docs rewritten for
the extension model: add_extension(TasksExtension()), the guard pattern for
in-task input (no imperative ctx.elicit()), tools-only, and the modern-protocol
requirement (the inverse of the old SEP-1686 legacy-only note). Mechanical
fixes elsewhere for the same reason: telemetry.mdx's tasks/{operation} method
list (get/update/cancel, not result/list), client.mdx's legacy-only feature
list (tasks moved to modern-only) and extension-composition paragraph
(describes the tasks ClientExtension, not the removed notification binding),
and stale SEP-1686 references in the FastMCP 2 upgrade guide. v4-notes status
lines updated to Shipped (#4602, #4603).
Engine modules (keys, context snapshot, docket lifespan, worker CLI,
client handles) move intact; SEP-1686 wire modules park in _legacy_wire
for adaptation to SEP-2663. Core keeps task=True declaration on tools
only and raises at serve time until the tasks extension is registered.
Co-Authored-By: Claude <noreply@anthropic.com>
* Add M2M client credentials auth providers
Wrap the SDK's client_credentials and private_key_jwt OAuth providers as
FastMCP-idiomatic ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider,
enabling browser-free client authentication via Client(auth=...).
* Fix M2M token cache collision and explicit-scope drop
Namespace the token cache by client_id so distinct clients sharing one store don't overwrite each other's tokens; pin caller-supplied scopes so the token request keeps them; fix CodeQL URL-substring check in tests; drop unused logger.
* Preserve step-up scope union, scope-aware token cache, restore token expiry
Only pin the caller's explicit scopes on initial authorization, leaving the SDK's step-up scope union intact; namespace the token cache by requested scopes as well as client_id; restore persisted absolute expiry on init so an expired stored token is re-fetched.
* Skip expiry restore for non-expiring reloaded tokens
* Distinguish expires_in=0 from omitted when restoring expiry
* Scope step-up flag to the flow via ContextVar; runnable JWT signing example
* Add server-side argument completion (@mcp.completion)
* Reference CompletionValues directly in cast so the import reads as used
* Import completion types from mcp_types, not the fastmcp.types mirror
* Fix test imports after dropping the fastmcp.types mirror
* Fix change-register example import after dropping the types mirror
* Enforce 100-value completion cap; make docs example runnable
* Document completion authorization contract
* Offload sync completion handlers to threadpool
* Exclude bare str from completion return type
* Pass Any-typed value in bare-string rejection test
* Point completion authoring types to mcp_types in v4 notes
* Trim fastmcp.types to FastMCP-unique types only
fastmcp.types re-exported 29 mcp_types symbols verbatim, which was
pointless indirection users had to discover. It now holds only Textarea,
the one type FastMCP actually defines; everything else imports from
mcp_types directly. These mirrors were added during unreleased SDK v2
migration work and never shipped, so this is not a breaking change.
* Keep historical mcp.types import in v2/v3 migration examples
fastmcp.types re-exported 29 mcp_types symbols verbatim, which was
pointless indirection users had to discover. It now holds only Textarea,
the one type FastMCP actually defines; everything else imports from
mcp_types directly. These mirrors were added during unreleased SDK v2
migration work and never shipped, so this is not a breaking change.
* Delegate forked client protocol helpers to the SDK
Replace FastMCP's copies of _fold_extensions, _evicting_message_handler,
and _synthesize_discover with imports from mcp.client.client. The fork had
drifted: it was missing validate_extension_identifier, so non-reverse-DNS
extension identifiers were silently accepted.
Full lifecycle composition over mcp.Client stays blocked upstream —
mcp.Client hardcodes ClientSession construction (no session_class hook)
and forbids reentry.
* Drop duplicate local helpers reintroduced by the merge; use SDK versions
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.
Client negotiates the newest mutual protocol era by default (probe
server/discover, fall back to the initialize handshake). ProxyClient and the
inspect utility explicitly pin the handshake era so proxy forwarding and
server_info reads are unchanged. SSE and multi-server config transports are
legacy-only. extensions= and result_claims= (SEP-2133) are thin passthroughs
to the SDK session.
* 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