Back to Fastmcp

What's New in FastMCP 4

docs/getting-started/whats-new.mdx

4.0.0b112.8 KB
Original Source

FastMCP 4 is a major version because its engine changed. The framework is now built on the MCP Python SDK v2, a ground-up rebuild of the protocol layer, and on that foundation it adds a new protocol era, first-class extensions, stateless state, enterprise identity, and more. Most FastMCP 3 servers run on it untouched — the major version signals how much moved underneath, and what that movement unlocks.

<Note> FastMCP 4 is in **beta**. Pin an exact version and expect sharp edges. See [Install the v4 prerelease](/getting-started/upgrading/from-fastmcp-3#install-the-v4-prerelease). </Note>

Built on the MCP Python SDK v2

The defining change in FastMCP 4 is the one you mostly can't see. The MCP Python SDK v2 rewrote the protocol layer end to end: it moved the protocol types into a standalone mcp_types package that stays importable as mcp.types, renamed every model field from camelCase to snake_case in Python, replaced the server's request-handling model, and made server-side middleware and multi-era serving first-class. FastMCP absorbs nearly all of it — your reads stay working through a compatibility bridge, and the handful of changes left in your code are mechanical.

The major version is the signal. Even where your surface is unchanged, the behavior underneath is substantially different, and bumping to 4.0 is how we tell you that plainly rather than slipping a new engine in under a patch release.

The rebuild also pulls the protocol's recent evolution forward in a single step. A batch of accepted MCP proposals arrives with SDK v2, and FastMCP 4 surfaces each one: capability-negotiated extensions (SEP-2133), multi-round-trip elicitation for sessionless connections (SEP-2322), response cache hints (SEP-2549), spec-standard error codes (SEP-2164), the enterprise identity-assertion grant (SEP-990), and the sessionless 2026-07-28 protocol itself, which removes server-initiated requests (SEP-2577). The rest of this page is what those add up to.

Every protocol era

A FastMCP 4 server answers clients across the protocol transition from one deployment. The MCP SDK negotiates the era per connection — the sessionless 2026-07-28 protocol for clients that have moved forward, the session-based handshake for everyone else — and any replica behind a plain load balancer can serve a modern request. This supersedes FastMCP's earlier "latest protocol only" stance: you adopt the new protocol without forking your deployment or gating clients by version.

The same negotiation runs from the client, and its default flipped. A plain Client(url) now probes for the modern protocol and adopts it when the server offers it, falling back to the handshake otherwise — where every earlier FastMCP version pinned the handshake outright. That flip is what brings the modern capabilities within reach of ordinary client code: a task-enabled tool hands back a handle to poll, and multi-round-trip elicitation resolves across successive requests, neither requiring the caller to opt in. Set mode="legacy" to pin the handshake when you need the session-based back-channel or the classic initialize result. Once connected, client.protocol_version, client.server_info, client.server_capabilities, and client.instructions read the same regardless of which era you negotiated — code that inspects the connection no longer branches on how it got there. See Protocol negotiation.

The modern protocol is sessionless, so it drops the server's ability to call back into the client mid-request (SEP-2577), and FastMCP 4's server API reflects that. ctx.elicit moves to a request-shaped pattern that works on modern connections: the tool returns a description of the input it needs, and the client answers with a fresh call. ctx.sample, ctx.sample_step, and ctx.list_roots are gone from the API, because each of them pushed a request down a live connection and a method that only works against old clients is a trap.

Both capabilities survive in the same request-shaped form. Asking for roots that way is the natural replacement, since one round trip buys the whole answer. Generation usually belongs in the server instead, because a loop of asking rounds spends the round-trip budget over and over — call an LLM from your server. Logging is untouched: ctx.info and its siblings are notifications, and notifications ride the response stream on every era. Everything else about writing a server is unchanged.

State without a session

A stateless protocol raises an obvious question: if every request is a fresh connection, where does a tool keep a shopping cart, a conversation, or a running total? FastMCP 4 follows the MCP working group's own decision to reject protocol-level sessions in favor of explicit state handles (SEP-2567) — the server hands out an identifier, and the client passes it back.

Two shapes cover the cases. UserSession is injected like Context and keyed to the authenticated user, so a tool reads and writes one bucket of state with nothing to pass around. SessionId is an explicit handle a tool mints and the caller supplies as an argument, for when one user holds many independent states. Both store their data server-side in the storage backend, keyed to the authenticated user — so a handle is inert in anyone else's hands. See Session State.

Background tasks

Long-running work runs as a background task: the server accepts the call, returns a handle, and the client polls for the result while the work proceeds. Tasks left the core MCP spec during the SDK v2 rebuild and returned as the io.modelcontextprotocol/tasks extension (SEP-2663), which FastMCP implements end to end in the optional fastmcp-tasks package. The durable execution engine that made FastMCP 3's tasks reliable — Docket — carries straight over, and @mcp.tool(task=True) remains the authoring surface, so the wire protocol modernizing underneath costs you no code change. See Background Tasks.

Server extensions

Background tasks are the first capability built on a more general one: FastMCP 4 makes MCP extensions — capability-negotiated protocol features named by a reverse-DNS string (SEP-2133) — a first-class surface. FastMCP.add_extension() lets an extension advertise a capability, add request methods, intercept tools/call, and run a lifespan hook, all with full access to the component registry, Context, and auth. The same extensions flow through the client with Client(extensions=...). A cross-cutting protocol feature stops being surgery on core and becomes a supported plugin.

Argument completion

When a client offers autocomplete for a prompt argument or a resource-template parameter, it asks the server which values fit — narrowing the list as the user types. FastMCP 4 lets a server answer. A single @mcp.completion handler receives the reference being completed, the argument and its partial value, and the arguments the user has already supplied, and returns the candidates the client surfaces as suggestions. Because the handler sees the earlier arguments, completions can depend on them — a repo parameter suggesting only repositories under the owner already chosen.

python
from fastmcp import FastMCP
from mcp.types import PromptReference

mcp = FastMCP("Docs")


@mcp.prompt
def write_poem(theme: str) -> str:
    return f"Write a poem about {theme}"


@mcp.completion
def complete(ref, argument, context):
    if isinstance(ref, PromptReference) and argument.name == "theme":
        options = ["nature", "love", "adventure"]
        return [o for o in options if o.startswith(argument.value)]
    return None

Registering a handler advertises the completions capability during negotiation, so a client only sends requests to a server that answers them — the same on both protocol eras. See Argument Completion.

Enterprise identity

FastMCP 4 ships a complete server-side implementation of identity assertion (SEP-990): enterprise "on-behalf-of" access, where a corporate identity provider issues a signed assertion, the user's agent presents it, and the server mints a short-lived token — no browser login and no per-user consent screen. Behind one parameter on the existing auth providers, FastMCP performs the full signature verification, binding checks, replay rejection, and scoped token issuance.

python
from fastmcp import FastMCP
from fastmcp.server.auth import IdentityAssertion, OAuthProxy

auth = OAuthProxy(
    # existing upstream configuration unchanged
    identity_assertion=IdentityAssertion(trusted_issuers=["https://login.acme-corp.com"]),
)
mcp = FastMCP("Internal API", auth=auth)

The asserted subject flows into the normal auth context, so tools read it through get_access_token() like any other identity. See Identity Assertion.

Authorizing a caller by role is a related, provider-agnostic need. Scopes are standardized, so require_scopes behaves the same everywhere, but roles and groups are not part of OIDC and every provider files them under a different claim. require_roles handles the comparison and takes an extract callable naming where to look, so Keycloak's realm_access.roles, Cognito's cognito:groups, and Auth0's per-tenant namespaced claims all work without FastMCP guessing.

python
from fastmcp import FastMCP
from fastmcp.server.auth import require_roles

mcp = FastMCP("Internal API")

@mcp.tool(auth=require_roles("admin", extract=lambda c: c["realm_access"]["roles"]))
def rotate_credentials() -> str:
    """Only callable by a caller holding the 'admin' role."""
    return "Rotated"

This illustrates the check in isolation — enforcing it for real needs an HTTP-transport server with a token-validating auth provider configured (a JWTVerifier, a RemoteAuthProvider, or a provider built on one, such as KeycloakAuthProvider, all expose claims directly), since STDIO has no OAuth concept and skips every check. See Authorization for the full picture.

The client side of enterprise auth arrived too. Not every FastMCP client has a user behind it — a backend service, a scheduled job, one MCP server calling another — and ClientCredentialsOAuthProvider authenticates one of those to a protected server with the OAuth 2.0 client-credentials grant: no browser, no redirect, no consent screen.

python
import asyncio

from fastmcp import Client
from fastmcp.client.auth import ClientCredentialsOAuthProvider

auth = ClientCredentialsOAuthProvider(
    client_id="my-client-id",
    client_secret="my-client-secret",
    scopes=["read", "write"],
)


async def main():
    async with Client("https://example.com/mcp", auth=auth) as client:
        await client.list_tools()


asyncio.run(main())

See Machine-to-Machine Authentication.

Faster and safer

Two more capabilities arrive by default. Response caching (SEP-2549) lets a server stamp freshness hints on its results that a caching client reuses without a round trip, and a distributed KeyValueResponseCacheStore backs that cache with Redis or any key-value store, so a fleet of clients or proxy replicas shares fills.

python
from fastmcp import FastMCP

mcp = FastMCP("Weather", cache_ttl=300, cache_scope="public")

Security tightened in the same release: every templated resource screens its parameters for path traversal, absolute paths, and null bytes before the handler runs — path security on by default, covering mounted and proxied templates too.

The OAuth flow got more precise as well. Dynamic Client Registration now honors a client's declared application_type (SEP-837): the permissive loopback and app-scheme callbacks MCP clients rely on stay the default for "native", while a client that registers as "web" is held to stricter browser-app redirect rules. And when AuthMiddleware denies a call specifically for a missing scope, it raises InsufficientScopeError naming exactly which scopes would fix it (SEP-2350), so a caller re-authorizes precisely instead of retrying blind. See Application Type and Signaling Scope Shortfalls.

A gateway or load balancer in front of your server can now route a request without parsing its JSON-RPC body: on a modern connection, FastMCP's client attaches the method, target name, and opted-in argument values as HTTP headers (SEP-2243), so an intermediary dispatches on headers alone. See Gateway Routing Headers.

When you're ready to move a server to v4, Upgrading from FastMCP 3 walks through every change and what it looks like in practice.