Build an MCP server in Python with FastMCP
Decorators in, protocol out. FastMCP turns typed Python functions into MCP tools, resources, and prompts — with the JSON-RPC plumbing handled for you.
FastMCP's pitch is that an MCP server should look like the Python you already write. Decorate a function and it becomes a tool; its type hints become the input schema; its docstring becomes the description the model reads. The protocol layer — JSON-RPC framing, capability negotiation, transport handling — never appears in your code.
This guide covers the full surface a real server uses: tools, resources, prompts, running locally versus remotely, and how to test before any client is involved.
Install and scaffold
Install with pip install fastmcp (or add it via uv, which most of the Python MCP ecosystem has standardized on). A server is an instance of the FastMCP class plus whatever you decorate onto it — no project structure requirements, no config files. One module is a complete, runnable server.
Tools, resources, and prompts from decorators
Tools are functions the model invokes to do things. Resources are read-only data the client can load into context, addressed by URI templates. Prompts are reusable message templates the user can trigger. All three are decorators over ordinary functions:
from fastmcp import FastMCP
mcp = FastMCP("city-data")
@mcp.tool
def population(city: str, year: int = 2025) -> str:
"""Look up the population of a city for a given year."""
return lookup_population(city, year)
@mcp.resource("citydata://countries/{code}")
def country_profile(code: str) -> str:
"""Country profile as JSON, by ISO code."""
return load_profile(code)
@mcp.prompt
def compare_cities(a: str, b: str) -> str:
return f"Compare {a} and {b} on population, growth, and density."
if __name__ == "__main__":
mcp.run() # stdio by defaultType hints are doing real work
FastMCP generates each tool's JSON Schema from the function signature, so the annotations aren't decoration — they're the contract clients validate against. Defaults become optional parameters; unions and Pydantic models translate into structured schemas. The practical implication: precise hints and a specific docstring directly improve how reliably models pick and call your tool.
Validation comes free in the same direction. A call with a string where your signature says int is rejected before your function body runs, which removes a whole class of defensive parsing from your code.
stdio for local, HTTP for the world
mcp.run() with no arguments speaks stdio — right for a server users run on their own machines via a client's config. The same code becomes a remote server with mcp.run(transport="http", host="0.0.0.0", port=8000), serving Streamable HTTP at a URL any internet-connected agent can reach.
The choice matters beyond deployment mechanics. A remote server is where monetization becomes possible: HTTP requests can be gated with x402 so agents pay per call in USDC before a tool executes. stdio servers have no request boundary to price.
Test without a client, then ship
The fastmcp dev command launches your server under MCP Inspector, giving you a browser UI to list tools, inspect generated schemas, and invoke functions with arbitrary arguments — far faster than restarting a desktop client per change. The fastmcp install command can then wire the finished server into Claude Desktop for an end-to-end check.
For distribution, publish to PyPI so users run it with uvx, or deploy the HTTP variant and list the URL on Loomal — where the listing carries your tool schemas and per-call price (from $0.01) in a form agents query directly. The platform fee is 5% on settled transactions, currently waived.
FAQ
Is FastMCP the same as the official Python SDK?
They're related but distinct. FastMCP 1.0 was absorbed into the official MCP Python SDK as its high-level API; the standalone FastMCP 2.x project continued beyond it with deployment, auth, and composition features. For defining tools with decorators, code looks nearly identical in both — check the FastMCP docs for what's 2.x-only.
When should I use a resource instead of a tool?
Use a resource when the model needs data in context rather than an action performed — documents, profiles, reference tables, addressed by URI. Use a tool when there are side effects or computed answers. Rule of thumb: resources are nouns the client reads, tools are verbs the model calls.
Can FastMCP tools be async?
Yes — decorate async def functions and FastMCP awaits them, which is what you want for tools doing network or database I/O. Sync and async tools can coexist on one server without any special handling.
How do I charge for a FastMCP server's tools?
Run it over the HTTP transport and put x402 payment gating in front of tool-call requests: unpaid calls get a 402 with a USDC price, a facilitator verifies the payment, and the tool runs after settlement on Base. The Python-specific integration has its own guide linked below.
Study servers that shipped.
Browse live Python and TypeScript MCP listings on the index.