- Shipped — merged to
main, with the PR cited. - Designed — the approach is settled and an API sketch exists; implementation has not started.
- Planned — the shape is agreed but design details remain open.
- Not started — identified as v4 scope, not yet designed.
Sampling removal
Status: Deprecation and era-gating shipped (#4448); removal slated for 4.0. Sampling is the push-shaped API where a server borrows the client’s model mid-call (ctx.sample, ctx.sample_step). The 2026-07-28 era removes server-initiated requests, so this API cannot work on modern connections. Background-task sampling is dead under v2 — a worker’s back-channel is gone once the submitting request returns, and no sampling relay was ever built (sdk-feedback #9).
The plan is Option A: deprecate the push-sampling API now and remove it in the 4.0 release. The first two steps shipped in #4448:
- Done:
ctx.sample/ctx.sample_stepemit aFastMCPDeprecationWarning(once per process, gated onsettings.deprecation_warnings). - Done: both are era-gated to raise a clear, era-aware
ToolErroron2026-07-28before the wire, which also fixed the opaque “Method not found” of sdk-feedback #10. - Pending 4.0: remove
ctx.sample,ctx.sample_step,server/sampling/,SamplingTool, and structured-result sampling.
MCPDeprecationWarning on ordinary ctx.sample usage (the SDK deprecated the capability wire-side per SEP-2577). FastMCP’s own deprecation — the warning with migration guidance, plus the era-gating — shipped in #4448; only the final removal remains for 4.0.
MRTR elicitation
Status: Guard form shipped (4.0). DeclarativeResolve layer designed.
Elicitation survives the modern era through multi-round-trip (MRTR). The 2026 wire envelope carries elicitation as a multi-round input-request: a tool returns an InputRequiredResult and re-runs per round, each round a complete request→response cycle. Imperative ctx.elicit relies on the session back-channel, which is gone on 2026-07-28 foreground calls; on the modern era, elicitation is reachable through MRTR instead.
The guard form of this is shipped in 4.0 (see Elicitation on the modern protocol): a tool returns an InputRequiredResult and reads the client’s answers off ctx.input_responses / ctx.request_state, re-running each round. It mirrors the SDK’s base guard model exactly — no FastMCP-invented DX, the framework owns request_state sealing, and returning this result on a handshake-era connection produces a clear era error.
What remains is the declarative Resolve(...) layer that sits on top of that shipped primitive. It is designed, not built: a new fastmcp.elicitation module — Resolve, Elicit, and ElicitationResult — thin wrappers over the SDK’s resolver, wired into FastMCP’s own tool layer (FastMCP tools do not inherit the SDK’s auto-resolver wiring). It would detect Annotated[_, Resolve(...)] parameters, build resolver plans, and return the SDK’s InputRequiredResult instead of the tool body on the first round.
Imperative ctx.elicit is not re-plumbed to survive the modern era. It works on the legacy eras through the session back-channel, and on 2026-07-28 foreground calls it is era-gated to raise a clear error (shipped in #4448) pointing at the guard form. The earlier plan to keep imperative ctx.elicit alive on modern connections through a background-task relay is dead twice over: the guard model shipped in its place, and the 2025 task machinery the relay depended on is slated for removal (see Known Gaps).
The intended declarative DX (sketch — the module does not exist yet):
Elicit/Resolve) and not for sampling. The wire carries all three input-request types and the client dispatches all three; only elicitation can produce one server-side. That is why elicitation survives 4.0 via MRTR and push-sampling does not.
Middleware root dispatch
Status: Shipped (#4553). The migration already routedinitialize interception through the SDK’s ServerMiddleware list via FastMCPServerMiddleware. #4553 made that entry the root of middleware dispatch: FastMCP’s method-agnostic hooks (on_message, on_request, on_notification) now fire for every inbound message — client cancellations, progress notifications, and requests that fail routing or validation — not only the ones that reach a component handler. The component methods keep running their own chain interior, and a method set plus a dispatch flag keep the two passes disjoint so each hook fires exactly once per message.
First-class 2026 client
Status: Partly shipped (#4572, #4574); full composition blocked upstream.fastmcp.Client now defaults to mode="auto" (#4572): it probes server/discover, falls back to the classic handshake, and answers multi-round-trip input_required requests through its existing handlers. The same PR surfaced extensions= and result_claims= (SEP-2133). The client also dropped its forked protocol helpers — extension folding, the evicting message handler, discover synthesis — in favor of the SDK’s own (#4574).
The decision here was compose, not wrap (D16): rebuild fastmcp.Client on the SDK’s high-level mcp.Client rather than wrapping mcp.ClientSession. The parts that compose cleanly have shipped. The rest is blocked upstream on two counts. First, mcp.Client constructs its ClientSession at a single hardcoded site with no injection hook, while FastMCP’s session_class is load-bearing (ProxyClient substitutes a session that skips result validation so a backend’s schema violation surfaces at the end client rather than becoming a proxy error) — a session_factory= hook on mcp.Client, the same shape as the notification_bindings= parameter added earlier, would solve this. Second, mcp.Client.__aenter__ refuses reentry, but FastMCP’s client is deliberately reentrant (its refcounted context manager exists to fix a proxy session-reuse deadlock), so the rebuild also needs the SDK client to tolerate reentrant entry. Both must land upstream before the full rebuild is possible; session_factory= alone is necessary but not sufficient.
This workstream also owns the server-side statelessness design holes — ctx.session_id / set_state round-tripping and stateful-proxy affinity — since they turn on the same “what is a session without a session?” question. See Statelessness on 2026-07-28 for the full accounting.
Subscriptions, cache hints, extensions, OTel
Status: Mixed — cache hints and OTel shipped; subscriptions not started. A cluster of protocol features tracked for v4. Their statuses have diverged:- Cache hints — shipped (#4464). Server-level authoring (
FastMCP(cache_ttl=..., cache_scope=...), SEP-2549) stamps every cacheable result, and the FastMCP client honors hints with an opt-in response cache. - OpenTelemetry — shipped (#4481). Spans are on by default (a no-op without an exporter), with SDK-aligned attributes and a
FASTMCP_ENABLE_TELEMETRY=falseoff-switch. - Extensions — client side shipped (#4572).
Client(extensions=..., result_claims=...)advertises opt-in client extensions (SEP-2133). The server side is a Designed workstream in its own right (see FastMCP-native extension API). The cross-era reconciliation of theextensions/ MCP Apps capability advertisement is still open (the capability is stripped at pre-2026 negotiated versions — sdk-feedback #2). - Subscriptions — not started. A
subscriptions/listensurface backed by a subscription bus.
FastMCP-native extension API
Status: Shipped (#4602). MCP extensions (SEP-2133) are optional, capability-negotiated protocol features identified by a reverse-DNS string —io.modelcontextprotocol/ui (MCP Apps), io.modelcontextprotocol/tasks (SEP-2663). They are a genuinely new abstraction in SDK v2; they did not exist in v1. The SDK exposes them through an Extension server class that contributes a capability, additive request methods, and a tools/call interceptor, plus a symmetric ClientExtension with result claims and notification bindings.
FastMCP already forwards ClientExtension natively (Client(extensions=...), #4572). The server side does not use the SDK’s Extension class at all: MCP Apps predates the abstraction, so FastMCP hand-splices the ui capability into get_capabilities() on the low-level server and walks tool metadata directly. That worked for one extension, but every new protocol extension currently means bespoke surgery on core.
The Designed work is a FastMCP-native server extension API — a single registration point (mcp.add_extension(...)) that contributes a negotiated capability, request methods, and a tools/call interceptor, with access to FastMCP-level constructs the SDK’s Extension withholds (the component registry, Context, auth scope). It is designed against the SEP-2663 tasks extension because tasks exercises the full surface — capability and methods and interception and client claims/notifications — where MCP Apps exercises only a subset. Tasks is the pathfinder; MCP Apps migrates onto the extension API as a fast-follow, deleting the hand-rolled splices, and confirms the design generalizes. The discriminator that keeps the extension API distinct from middleware: an extension is a negotiated contract change the client must understand, where middleware is unilateral server behavior the client never sees. Delete a capability advertisement and nothing about the client changes — that is middleware, not an extension.
Background tasks (SEP-2663)
Status: Shipped (#4603). Background tasks return to the modern era asfastmcp-tasks, an in-repo optional package rebuilt on the io.modelcontextprotocol/tasks extension (SEP-2663, Final, merged upstream 2026-05-15). SEP-2663 supersedes SEP-1686 but keeps its polling core: a client that advertises the tasks capability issues an augmented tools/call; the server decides whether to run it as a task and returns a CreateTaskResult carrying a server-generated task id; the client polls tasks/get until terminal and reads the result inlined there. FastMCP’s existing SEP-1686 wire layer is removed while the Docket/Redis execution engine underneath moves into fastmcp-tasks intact — the spec moved toward what FastMCP already built, so the rebuild is mostly deletion plus a thin wire adapter. task=True stays the authoring surface (gated by the fastmcp[tasks] extra and an explicit mcp.add_extension(TasksExtension(...)), the first consumer of the extension API above), so a server that already uses tasks needs no code change. Scope for v1 is polling-only and tools/call-only.
The full design — wire delta, the engine/wire split, packaging, client experience, sequencing, risks, and the five resolved decisions — is on the dedicated Background Tasks (SEP-2663) page.
SDK delegation, round two
Status: Planned (gated on upstream). The real HTTP simplification is a v4 project, not this PR. FastMCP can collapse itscreate_streamable_http_app onto the SDK’s Server.streamable_http_app() once upstream adds three things:
- per-session event-store scoping,
- a user-middleware injection hook,
- a lifespan hook.
session_idle_timeout is accepted by the manager but never set by create_streamable_http_app — a one-line plumb if FastMCP wants to expose it.
