from mcp.server.mcpserver import MCPServer, you’re using the high-level server API introduced in v2 of the mcp package. Moving to FastMCP is a mechanical migration: the two APIs share a lineage, so most of your code carries over with a rename.
MCPServer is the SDK’s successor to FastMCP 1.0, the high-level server that shipped inside SDK v1; FastMCP is the standalone framework that grew from the same starting point. Both derive the protocol layer from your function signatures — type hints become JSON Schema, docstrings become descriptions, return values are serialized for you. What separates them is scope: MCPServer is the SDK’s ergonomic surface over the protocol, while FastMCP builds on that same SDK v2 and adds the machinery a server needs in production — composition, middleware, proxying, authentication providers, tool transformation, a client, and a testing story.
Building on the low-level
Server class instead? See Upgrading from the Low-Level SDK v2. Still on SDK v1’s mcp.server.fastmcp.FastMCP? Your upgrade is a single import — see Upgrading from MCP SDK v1.Copy this prompt into any LLM along with your server code to get automated upgrade guidance.
Install
FastMCP 4 is in prerelease, so pin the exact version rather than installing unqualified — a barepip install fastmcp or uv add fastmcp resolves to the latest stable release, which today is FastMCP 3:
--pre or --prerelease allow for a version this specific, only for an open-ended range. For a reproducible lockfile that also pins the prerelease protocol dependencies, see Install the v4 Prerelease.
FastMCP 4 depends on the MCP SDK v2, so nothing you already import from mcp_types moves. That is the practical benefit of migrating at this version rather than an earlier one: you and FastMCP are on the same protocol layer, with the same snake_case field names and the same type package, so the migration touches only the server API.
The Mechanical Part
Most of the work is renaming. This table covers the surfaces a typicalMCPServer server touches:
Two of these are worth a sentence each. The decorators lose their parentheses:
MCPServer required @server.tool() and raised a TypeError telling you so if you wrote @server.tool, while FastMCP accepts both forms, so @mcp.tool is the idiomatic spelling and @mcp.tool() keeps working if you’d rather not touch every line. And the streamable HTTP transport is named "http" in FastMCP rather than "streamable-http" — the transport is the same, and mcp.run() still defaults to stdio.
Here is a complete server before and after. Nothing in the logic changes:
Constructor Arguments
FastMCP() describes your server’s identity and behavior; how it gets deployed is decided when you serve it. Several MCPServer constructor arguments move accordingly, and each raises a TypeError naming its replacement rather than being silently ignored.
name, version, instructions, icons, website_url, lifespan, resource_security, and request_state_security all mean what they meant before. The rest map like this:
middleware= is the row most likely to be mistaken for a rename. Both constructors take a middleware= sequence, but an MCPServer wants the SDK’s ServerMiddleware — one hook wrapping every raw JSON-RPC message — while FastMCP wants its own Middleware, which adds typed per-operation hooks (on_call_tool, on_list_tools, and the rest) on top of the same message-level pass. Keeping the keyword and swapping the base class is the migration; see Middleware.
Authentication is the largest of these, and it consolidates rather than moves. MCPServer exposes the SDK’s raw auth plumbing — a token verifier, an authorization-server provider, and an AuthSettings object, configured separately. FastMCP takes one auth= provider that carries the whole configuration, and ships providers for the common cases: JWTVerifier for validating tokens you already issue, RemoteAuthProvider for delegating to an external authorization server, OAuthProxy for wrapping a provider that lacks Dynamic Client Registration, and named providers for GitHub, Google, Auth0, Keycloak, WorkOS, and others. See Authentication.
Two rows are worth reading before you delete the argument. resources= has no constructor equivalent, so pre-built Resource objects need registering through @mcp.resource or mcp.add_resource() instead — dropping the keyword silently drops the resources with it. And subscriptions=, which an MCPServer uses to plug in an external pub/sub bus so resource-update notifications reach clients across replicas, has no FastMCP equivalent at all. A multi-replica deployment that relies on it should confirm it can live without cross-replica subscription fan-out before migrating, because a mechanical rename removes that behavior without any error to warn you.
Serving HTTP
Renamingstreamable_http_app() to http_app() is only mechanical for a call with no arguments. The keywords were renamed and regrouped, so an existing call carries arguments http_app() does not accept:
json_response, stateless_http, event_store, and retry_interval keep their names. See Deploying HTTP servers for the host and origin settings.
Stricter Arguments
One behavior change survives the rename and is worth knowing before you migrate.MCPServer binds the arguments it recognizes and ignores the rest, so a call carrying an unexpected key succeeds. FastMCP declares "additionalProperties": false on every generated schema and enforces it, so the same call fails:
Asking for Input
This is the one part of the migration that is not a rename, so read it before you start if your tools use resolvers.MCPServer asks the client for things through dependency-injection resolvers. A tool parameter annotated Annotated[T, Resolve(fn)] is filled by running fn before the tool body, and the resolver can return a request marker — Elicit[T] to ask the user, Sample to borrow the client’s model, ListRoots to fetch its roots — which the framework turns into the right wire interaction for whichever protocol era the connection negotiated:
ctx.elicit(), and the call blocks until the answer arrives. Where the resolver returned a value or aborted the call, ctx.elicit() hands you the outcome to branch on, so declining and cancelling become cases your tool answers for itself:
ctx.input_responses.
The two are era-gated in both directions: ctx.elicit() raises on a modern connection, and a guard result raises on a handshake one. A server that must serve both branches on ctx.request_context.protocol_version. See Elicitation for both shapes side by side.
Resolvers that return Sample or ListRoots have no injected equivalent — FastMCP has no ctx.sample() or ctx.list_roots() — but the underlying request survives, so this is a change of shape rather than a loss of capability. On a modern connection both ride the same guard pattern as elicitation: the tool returns an InputRequiredResult describing the sampling or roots request, and the client answers on the next call.
Which shape you want differs by capability. For roots, the guard route is the natural replacement, since one round buys the whole answer — and taking the paths as ordinary tool arguments is simpler still whenever the caller can supply them. For generation, prefer calling an LLM from your server with your own API key: your tool then behaves identically for every client, including the many that never implemented sampling, and you avoid paying a full request-response cycle per generation step. Reach for the guard route when using the caller’s model is specifically the point.
One schema detail is easy to miss during the rewrite. A resolved parameter never appears in the tool’s input schema — book_flight above advertises no arguments at all. When you replace a resolver with an explicit tool argument, the schema the client sees gains a field, which is usually what you want but is a visible change to your tool’s contract.
What You Gain
The migration is worth doing for what sits on the other side of it. FastMCP is a framework rather than a protocol surface, and these are the capabilities that most often motivate the move: Server composition mounts one server inside another, so a large surface splits into modules that are developed and tested independently. Middleware runs across every request for logging, rate limiting, error handling, and caching, with hooks at whichever level of specificity you need. Proxy servers put a FastMCP server in front of any existing MCP server, bridging transports and adding auth to a backend you don’t control. The OpenAPI integration generates a whole server from an existing API specification. Tool transformation rewrites the tools a server exposes — renaming, hiding, and reshaping arguments — without touching the code that defines them. FastMCP also ships a client, whichMCPServer has no counterpart for. It speaks every transport, drives both protocol eras, and connects to a server object in-process — so testing a server means calling its tools in the same Python process, with no subprocess and no network.
