FastMCP vs MCP SDK: Which to Build On
FastMCP hides the protocol; the low-level SDK hands it to you — here's exactly when each is the right call.

Building an MCP server in Python? Use FastMCP — it turns a decorated function into a working server in about ten lines, and the official SDK's FastMCP class is literally FastMCP v1 vendored in. The choice of FastMCP vs MCP SDK is really a choice about how much of the protocol you want to touch: FastMCP hides it, the lower-level SDK hands it to you. This guide is for people deciding what to build on, not what the acronyms mean.
Short version: default to FastMCP for anything server-shaped, and drop to the raw SDK primitives only when you're writing a client, embedding a server inside another app, or doing something the decorators don't cover.
FastMCP vs the MCP SDK: what's actually different
FastMCP is a Pythonic framework on top of the Model Context Protocol; the MCP SDK is the reference implementation that gives you the protocol's building blocks directly. The confusing part is that they overlap. The official mcp package includes a FastMCP class — it's FastMCP v1, donated upstream by the same author. The standalone fastmcp package (v2) is that idea taken further: auth, server composition, a testing client, deployment helpers.
So there are three things people mean by "the SDK," and they matter:
from mcp.server.fastmcp import FastMCP— the vendored v1 inside the official SDK. Decorator-based, batteries-lighter.from fastmcp import FastMCP— the standalone v2 package. Same ergonomics, far more surface area.- The low-level
mcp.server.Server— raw handlers, no decoration magic. This is "the SDK" when engineers say it's "closer to the metal."
If someone says "just use the SDK," ask which of these they mean. Most of the time the honest answer is "use FastMCP and stop overthinking it." For the conceptual background, see what an MCP server is.
When FastMCP is the right call
Reach for FastMCP whenever you're exposing tools, resources, or prompts and you'd rather write business logic than protocol plumbing. It handles the JSON-RPC framing, generates input schemas from your type hints, and wires up stdio or HTTP transport with one argument.
A minimal server is genuinely this small:
from fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool()
def get_forecast(city: str) -> str:
"""Return today's forecast for a city."""
return fetch(city)
if __name__ == "__main__":
mcp.run() # stdio by default; mcp.run(transport="http") to go remote
That default matters. About 90% of the servers we track run locally over stdio, and mcp.run() gives you exactly that with no config. The Python-heavy corner of the ecosystem leans on this: BlenderMCP, Vizro-MCP, and codemcp are all Python, all stdio — the shape FastMCP is built for. If your server is a wrapper over an SDK you already have in Python, FastMCP is almost always the shorter path.
The v2 package adds the parts you feel later: bearer/OAuth auth, mounting sub-servers under one process, and an in-memory test client so your tests don't spawn subprocesses. Those aren't in the vendored v1, which is the main reason to install fastmcp over mcp alone.
When to drop to the lower-level SDK
Use the raw SDK primitives when you're building a client, embedding a server inside a larger application's own event loop, or when a decorator can't express what you need. FastMCP is a server-authoring tool first; the full SDK is the whole protocol, both ends.
Concrete cases where the low-level API earns its verbosity:
- You're writing the client, not the server — an agent or app that consumes MCP servers. FastMCP's client helps, but connection lifecycle, sampling, and roots live in the base SDK.
- Fine-grained control over capabilities and lifespan — custom initialization, resource cleanup, or advertising capabilities FastMCP doesn't wrap yet.
- You're not in Python. FastMCP is Python (and now a TypeScript port). The MCP Language Server is written in Go against the Go SDK — a reminder that "MCP SDK" isn't one language, and if your host app is Go, Rust, or C#, FastMCP isn't on the table.
The trade is real: the low-level server means writing list_tools / call_tool handlers and your own JSON schemas by hand. You get precision and you pay in lines. For most servers that precision buys nothing.
Side-by-side
The short answer in one view: FastMCP v2 wins on features, the vendored v1 wins on zero dependencies, and the low-level server wins only on control. Here's how they line up.
FastMCP (v2, fastmcp) | FastMCP in official SDK (v1) | Low-level mcp.server.Server | |
|---|---|---|---|
| Style | Decorators | Decorators | Explicit handlers |
| Tool schema | Auto from type hints | Auto from type hints | You write it |
| Auth (OAuth/bearer) | Built in | Not included | DIY |
| Server composition | Yes (mount sub-servers) | No | Manual |
| Test client | In-memory | No | No |
| Client-building | Helper included | Limited | Full protocol |
| Best for | Most servers | Quick servers, no extra dep | Clients, deep control, embedding |
Rule of thumb: v1 if you want zero extra dependencies and a simple tool server; v2 if you want auth, composition, or tests; low-level only when you've hit a wall. Very few projects hit that wall.
What this doesn't change: the tool budget
Neither framework saves you from the client-side limit, so design tools like they're scarce. Cursor and similar clients degrade past roughly a 40-tool budget across all connected servers, and that ceiling is unrelated to how you built any single server.
FastMCP makes it easy to expose thirty tools, which is exactly the trap. A user who installs your server plus four others is near the ceiling before yours even loads. Ship fewer, sharper tools; fold read variants into one parameterized tool. This is an authoring decision the framework won't make for you — the math is in Cursor's tool limit, and Capabilities shows how real servers spend their budget.
If you're weighing MCP against just calling an API directly, that's a separate decision covered in MCP vs API. And once you've built something, the best MCP servers is a useful bar for what "good" looks like in the wild.