
This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.
FastMCP is a Python framework for building MCP servers and clients. You write normal Python functions, add a @mcp.tool decorator, and FastMCP turns the type hints and docstring into the schema a model reads. This tutorial takes you from an empty folder to a tested server you can add to Claude or run over HTTP.
FastMCP is maintained by Prefect, and it is a separate package from the official MCP Python SDK. As of November 2026 the current release is FastMCP 4, which runs on top of version 2 of the official SDK. If you are still deciding between the two, read FastMCP vs the official Python MCP SDK first.
Everything below was checked against the FastMCP documentation and the PyPI listing for fastmcp on 2 November 2026. The latest release on that date was fastmcp 4.1.5 (uploaded 1 November 2026), Apache-2.0 licensed, requiring Python 3.10 or later. The code was checked against the docs, not run by eSEOspace.
Key Takeaways
- Install with
uv add fastmcporpip install fastmcp, then confirm withfastmcp version, which also prints the MCP SDK version underneath. - FastMCP 4.1.5 depends on the official
mcppackage 2.3.0 or later (below 3.0), so it sits on SDK v2. - A tool is a decorated function: the docstring becomes the description and type hints become the input schema.
- Test interactively with
fastmcp dev inspector, from the terminal withfastmcp listandfastmcp call, and in CI with pytest and the in-memoryClient. - Use
mcp.run()for stdio andmcp.run(transport="http")ormcp.http_app()for remote use; SSE is legacy. - Pin an exact FastMCP version in production, because the docs say minor releases may contain breaking changes.
Prerequisites
You need three things before you start:
- Python 3.10 or later. That is the floor in the PyPI metadata for fastmcp 4.1.5.
- uv (recommended) or pip. The installation page recommends uv for installing and managing FastMCP.
- An MCP client for the last step, such as Claude Desktop, Claude Code or Cursor. You can do everything else without one.
If MCP terms like "tool", "host" or "transport" are new to you, the free MCP course covers them in short lessons.
Step 1: Install FastMCP
Create a project and add FastMCP:
uv init bookshop-mcp
cd bookshop-mcp
uv add fastmcp
With pip, run pip install fastmcp inside a virtual environment instead. Then check the install:
fastmcp version
The installation docs show this command printing the FastMCP version, the MCP SDK version, the Python version and the platform. That second line matters. FastMCP 4 is built on the official SDK v2, and the PyPI metadata for its fastmcp-slim core lists mcp>=2.3.0,<3.0.0 for both the server and client extras.
The docs also flag a licensing detail some companies care about. FastMCP's CLI depends on Cyclopts, and Cyclopts v4 pulls in docutils, which "has complex licensing that may trigger compliance reviews." The docs suggest the Cyclopts v5 alpha if that is a problem.
Step 2: Write your first server
Create server.py. This example exposes a small book catalog with one read-only tool:
from typing import Annotated
from pydantic import Field
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError
mcp = FastMCP("Bookshop")
CATALOG = {
"Dune": "Frank Herbert",
"Neuromancer": "William Gibson",
"The Left Hand of Darkness": "Ursula K. Le Guin",
}
@mcp.tool(annotations={"readOnlyHint": True})
def search_books(
query: Annotated[str, Field(description="Part of a title or author name")],
limit: Annotated[int, Field(description="Maximum results", ge=1, le=20)] = 5,
) -> list[str]:
"""Search the catalog by title or author and return matching titles."""
needle = query.lower()
hits = [t for t, a in CATALOG.items() if needle in t.lower() or needle in a.lower()]
if not hits:
raise ToolError(f"No books match '{query}'.")
return hits[:limit]
if __name__ == "__main__":
mcp.run()
Here is what each part does, according to the FastMCP Tools guide:
FastMCP("Bookshop")creates the server. The name is what clients display.@mcp.toolregisters the function. FastMCP reads the docstring for the tool description and generates the input schema from the parameters and type annotations.Annotated[..., Field(...)]adds a per-argument description and limits. Thege=1, le=20bounds end up in the JSON Schema the model sees.annotations={"readOnlyHint": True}tells clients this tool does not change anything. The decorator also acceptsdestructiveHint,idempotentHintandopenWorldHint.ToolErrorsends a clear message back to the model. The guide saysToolErrormessages always reach the client, even when you turn onmask_error_details=Trueto hide other exception details.mcp.run()with no arguments uses stdio, which the Running Your Server page calls the default transport for local tools.
Sync functions are fine. The Tools guide says FastMCP runs synchronous tools in a threadpool so they do not block other calls. Use async def when the tool awaits network calls.
One validation detail is worth knowing. By default FastMCP uses Pydantic's flexible validation, so a model that sends "10" for an integer gets coerced. Pass strict_input_validation=True to FastMCP(...) if you would rather reject those inputs.
Step 3: Add a second tool
Adding tools is the same pattern every time. Here is a lookup tool that also uses the docstring for argument descriptions:
@mcp.tool(annotations={"readOnlyHint": True})
def get_author(title: str) -> str:
"""Return the author of a book.
Args:
title: The exact book title, for example "Dune".
"""
if title not in CATALOG:
raise ToolError(f"Unknown title. Known titles: {', '.join(CATALOG)}")
return CATALOG[title]
The Tools guide says FastMCP parses Google, NumPy and Sphinx docstring styles. The text above Args becomes the tool description, and each Args entry becomes that parameter's description. An explicit Field(description=...) wins over the docstring if you use both.
Good descriptions do most of the work here. The model sees nothing but the name, description and schema, so write them for a reader who has never seen your code. Name tools with verbs and say what each one returns.
Step 4: Test with the MCP Inspector
FastMCP wraps the official MCP Inspector in one command. The Running Servers CLI page documents it:
fastmcp dev inspector server.py
This opens the Inspector in your browser with auto-reload on, so saving server.py restarts the server. List the tools, then call search_books with a query like gibson.
One trap: the docs warn that fastmcp dev inspector imports your file without running the if __name__ == "__main__": block. Anything you set up only in that block will be missing. Register tools at module level, as in the example, or use a factory function.
For quick checks without a browser, the CLI overview lists list and call commands:
fastmcp list server.py
fastmcp call server.py search_books query=dune
Our MCP Inspector lesson explains what to look for in the Inspector's output.
Step 5: Write automated tests
The Inspector is for exploring. For tests that run in CI, the FastMCP testing guide recommends pytest with the in-memory FastMCP client. Install pytest-asyncio and set asyncio_mode = "auto" under [tool.pytest.ini_options] in pyproject.toml. Then:
import pytest
from fastmcp.client import Client
from server import mcp
@pytest.fixture
async def client():
async with Client(mcp) as c:
yield c
async def test_lists_two_tools(client):
tools = await client.list_tools()
assert {t.name for t in tools} == {"search_books", "get_author"}
async def test_get_author(client):
result = await client.call_tool("get_author", {"title": "Dune"})
assert result.data == "Frank Herbert"
Passing the server object straight to Client connects in memory, so there is no subprocess or network port. The testing guide uses result.data for the tool's typed return value.
Step 6: Add the server to Claude or Cursor
FastMCP can write the client config for you. The Claude Desktop integration page documents:
fastmcp install claude-desktop server.py
The same install command supports claude-code, cursor, gemini-cli and goose, per the CLI overview. Extra flags add dependencies, such as --with pandas or --with-requirements requirements.txt, and --python 3.11 picks the interpreter. Restart Claude Desktop afterward.
If you prefer to write the JSON by hand, our MCP config generator builds the block for each client.
Step 7: Serve it over HTTP
To make the server reachable by URL, switch the transport:
if __name__ == "__main__":
mcp.run(transport="http", host="127.0.0.1", port=8000)
The running-server docs say the endpoint is then http://localhost:8000/mcp. You can also run it without editing the file: fastmcp run server.py:mcp --transport http --port 8000.
For production, the HTTP deployment guide recommends building an ASGI app and running it with a server such as Uvicorn:
app = mcp.http_app()
uvicorn server:app --host 0.0.0.0 --port 8000
Three settings from that guide matter before you go public:
- Authentication. The guide calls it "highly recommended" and notes some clients refuse unauthenticated remote servers. FastMCP supports bearer tokens, JWT and OAuth.
- Host and Origin checks.
host_origin_protection=Truewithallowed_hostsandallowed_originsguards against DNS rebinding. It is off by default. - Path.
http_app(path="/api/mcp")moves the endpoint if/mcpclashes with your app.
FastMCP 4 also changed what HTTP deployments look like. Its What's New page says one server negotiates both protocol eras per connection. Modern clients get the sessionless 2026-07-28 protocol, so "any replica behind an ordinary load balancer can serve any request." Older handshake-era clients keep working. That is why FastMCP 4 servers can scale out without sticky sessions.
For general hosting choices, see Lesson 21: Deploy a Remote Server. Prefect also runs a managed host, Prefect Horizon, which our Prefect MCP profile describes.
Common pitfalls
- Old import paths.
from mcp.server.fastmcp import FastMCPis the FastMCP 1.0 that shipped inside SDK v1. The SDK v2 renamed its built-in class toMCPServerand removed that path. With the standalone package, importfrom fastmcp import FastMCP. - Mixing up the two projects. Code written for the SDK's
MCPServerand code written forfastmcp.FastMCPlook alike but are different libraries. FastMCP's installation page links separate upgrade guides for SDK v1 and SDK v2 users. - Following SSE tutorials. The running-server docs keep
transport="sse"only for older clients and recommend HTTP for all new projects. - Loose version ranges. The installation page says breaking changes may occur in minor versions and tells you to pin an exact version, such as
fastmcp==4.1.5. - A broken pip upgrade from 3.2 or earlier. The docs describe a case where
import fastmcpfails after a pip upgrade to 3.3 or later. The fix ispip install --force-reinstall fastmcp. - Printing to stdout. On stdio, the protocol uses stdout. Send logs to stderr or a log file, never to stdout.
Next step
You now have a tested FastMCP server running locally and over HTTP. To compare it with the official SDK route, read How to Build Your First MCP Server. If you want a team to design, secure and ship a production server for your business, see our MCP development service.
More in the MCP Guide
Free tools: server.json Validator · MCP Error Code Lookup
Latest research: State of MCP: October 2026
Frequently asked questions
Is FastMCP the same as the official MCP Python SDK?
No. FastMCP is a separate open-source framework maintained by Prefect. A much older FastMCP was folded into SDK v1 as mcp.server.fastmcp, which is why the names get mixed up. As of November 2026, FastMCP 4 depends on SDK v2 rather than replacing it.
What Python version does FastMCP need?
The PyPI metadata for fastmcp 4.1.5 requires Python 3.10 or later. Check fastmcp version after installing to see the exact FastMCP and MCP SDK versions in your environment.
Does FastMCP support the 2026-07-28 MCP spec?
FastMCP's What's New page says FastMCP 4 serves the sessionless protocol to modern clients and falls back to the handshake protocol for older ones, from one deployment. Pin your version and check the changelog before upgrading.
How do I test a FastMCP server without Claude?
Use fastmcp dev inspector server.py for a browser UI, fastmcp list and fastmcp call in the terminal, or pytest with Client(mcp) for automated tests. None of these need an AI client.
Can I add authentication to a FastMCP server?
Yes. The HTTP deployment guide points to FastMCP's authentication docs, which cover bearer tokens, JWT verification and OAuth with external identity providers. Authentication applies to HTTP servers; stdio servers run locally under the user's own account.











