fastmcp
FastMCP is the Prefect-maintained Python framework for the Model Context Protocol, the standard that connects LLMs to tools and data. You write a plain Python function, decorate it with @mcp.tool, and FastMCP generates the JSON schema, validation, and protocol plumbing so Claude, Cursor, or any MCP client can call it. It covers all three sides of MCP: servers (tools, resources, prompts), clients (connect to any server by URL with transport negotiation and auth handled), and apps (interactive UIs rendered in the conversation). FastMCP 1.0 was folded into the official MCP Python SDK in 2024; the standalone project kept moving and is now on its own 3.x line with far more features than the SDK version.
The default choice for building MCP servers in Python: the decorator API is genuinely pleasant and the ecosystem momentum is real. Pin your major version and budget time for upgrade guides, because this project moves faster than the protocol it implements.
Use it if
- You are building an MCP server in Python and want to expose existing functions as tools without hand-writing JSON schemas or protocol lifecycle code
- You need a programmatic MCP client, for example to test your own server in-process or to script calls against a remote server from Python
- You want production concerns handled: HTTP transport, OAuth and bearer auth, server composition via mounting, and middleware are built in rather than DIY
- You are already on the official MCP SDK's FastMCP 1.x and keep hitting missing features; the standalone project is where active development happens
- You want a zero-dependency footprint or maximum protocol control; the official mcp package's low-level Server API is smaller and closer to the wire, at the cost of more boilerplate
- Your stack is TypeScript; use the official @modelcontextprotocol/sdk or the team's own fastmcp-ts instead of bridging to Python
- You dislike fast-moving APIs: the project shipped major versions 2 and 3 within roughly a year, and the docs maintain five separate upgrade guides, which tells you how often code needs migrating
- You only need to bolt MCP onto an existing FastAPI service; fastapi-mcp generates a server from your routes with less new surface area to learn
Setup reality
pip install fastmcp (the team recommends uv) needs Python 3.10+ and pulls the mcp SDK plus pydantic, httpx, and a CLI. The confusing part is naming: `from mcp.server.fastmcp import FastMCP` (official SDK, old 1.x code) and `from fastmcp import FastMCP` (this project) are different packages with diverging APIs, and tutorials mix them freely. Decorator syntax changed across majors (@mcp.tool() vs @mcp.tool both work now), stdio vs HTTP transport trips people up when moving from Claude Desktop to a hosted server, and if you came from 2.x expect to read the v3 upgrade guide because defaults moved.
Patterns
Minimal server with one toolcreate-server-with-tool
from fastmcp import FastMCP
mcp = FastMCP("Demo")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
if __name__ == "__main__":
mcp.run()The docstring becomes the tool description and type hints become the JSON schema. mcp.run() defaults to stdio transport, which is what Claude Desktop and most local clients expect.
Serve over HTTP instead of stdiorun-http-server
from fastmcp import FastMCP
mcp = FastMCP("Demo")
@mcp.tool
def ping() -> str:
return "pong"
if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8000)HTTP transport serves at /mcp by default. Clients connect to http://host:8000/mcp, not the bare root URL; forgetting the path is a common first failure.
Expose read-only data as a resourcedefine-resource
from fastmcp import FastMCP
mcp = FastMCP("Demo")
@mcp.resource("data://config")
def get_config() -> dict:
return {"env": "prod", "debug": False}Resources are for data the client reads, not actions. The URI scheme is arbitrary but must be a valid URI; returning a dict serializes to JSON automatically.
Parameterized resource templateresource-template
from fastmcp import FastMCP
mcp = FastMCP("Demo")
@mcp.resource("users://{user_id}/profile")
def user_profile(user_id: str) -> dict:
return {"id": user_id, "name": "Ada"}Placeholders in the URI must match the function parameter names exactly or registration fails at import time.
Reusable prompt templatedefine-prompt
from fastmcp import FastMCP
mcp = FastMCP("Demo")
@mcp.prompt
def review_code(code: str) -> str:
return f"Review this code for bugs and style:\n\n{code}"Prompts are user-invoked templates, not tools; clients surface them as slash commands or menu items.
Log and report progress from inside a toolcontext-logging-progress
from fastmcp import FastMCP, Context
mcp = FastMCP("Demo")
@mcp.tool
async def crunch(items: list[str], ctx: Context) -> int:
await ctx.info(f"processing {len(items)} items")
for i, _ in enumerate(items):
await ctx.report_progress(progress=i + 1, total=len(items))
return len(items)Just add a Context-typed parameter and FastMCP injects it; it is excluded from the tool's public schema. Context methods are async, so the tool must be async too.
Call a remote server from Pythonclient-call-tool
import asyncio
from fastmcp import Client
async def main():
async with Client("http://localhost:8000/mcp") as client:
tools = await client.list_tools()
result = await client.call_tool("add", {"a": 2, "b": 3})
print(result)
asyncio.run(main())Client infers the transport from what you pass: an http URL uses HTTP, a .py path spawns a stdio subprocess, a server object runs in-memory.
Test a server without a network or subprocessin-memory-testing
import asyncio
from fastmcp import FastMCP, Client
mcp = FastMCP("Demo")
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
async def test_add():
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
assert result.data == 3
asyncio.run(test_add())Passing the server object to Client runs the full protocol in-memory. This is the intended pytest pattern and needs no ports or fixtures.
Compose servers by mountingmount-subserver
from fastmcp import FastMCP
weather = FastMCP("Weather")
@weather.tool
def forecast(city: str) -> str:
return f"Sunny in {city}"
main = FastMCP("Main")
main.mount(weather, prefix="weather")
# tool is now exposed as weather_forecastMounting prefixes tool names with underscore separators. Older tutorials show mount(prefix, server) argument order; current versions take the server first.
Return a controlled error to the clienttool-error-handling
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError
mcp = FastMCP("Demo")
@mcp.tool
def divide(a: float, b: float) -> float:
if b == 0:
raise ToolError("Cannot divide by zero.")
return a / bToolError messages are always sent to the client. Other exceptions may be masked depending on the server's error-masking settings, so use ToolError for anything the LLM should read.
Run any server file with the fastmcp CLIrun-with-cli
# server.py contains: mcp = FastMCP("Demo")
fastmcp run server.py:mcp
# or serve over HTTP on a port
fastmcp run server.py:mcp --transport http --port 8000The CLI ignores your __main__ block and calls run() itself with the flags you give it, which surprises people who set transport in code.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| mcp | PyPI | You want the official Anthropic-governed Python SDK with the slowest-moving, most conservative API |
| fastapi-mcp | PyPI | You already have a FastAPI app and just want its endpoints exposed as MCP tools |
| @modelcontextprotocol/sdk | npm | Your server or client is TypeScript, not Python |