FastMCP — build MCP servers fast
A Pythonic framework that turns ordinary functions into MCP tools, resources, and prompts with simple decorators — so you can expose your own data and actions to any MCP client without writing protocol code by hand.
What you'll learn
- What FastMCP adds on top of raw MCP — decorators that auto-generate the tool schema the model reads
- The three primitives: tools (actions), resources (readable data), prompts (reusable templates)
- How to run your server over stdio or HTTP and let Claude Desktop or an agent discover it
Before you start
MCP — the Model Context Protocol — is a standard so that any LLM client (Claude Desktop, an ADK agent, Cursor) can call external tools and read external data using the same wire format, regardless of who built them. If you have not read the MCP intro yet, start there.
FastMCP is a Python framework that sits on top of the official MCP SDK and removes the boilerplate. Instead of manually registering handlers and constructing JSON schemas, you decorate ordinary Python functions and FastMCP generates the schema, validates inputs, and speaks the protocol on your behalf. The result: your first working MCP server fits in about twenty lines.
Why decorators and type hints are the whole trick
When Claude (or any MCP client) connects to your server it asks: what tools do you have, and what arguments does each one take? The answer must be a precise JSON schema — field names, types, descriptions. Writing that schema by hand for every function is tedious and error-prone.
FastMCP solves this by reading your Python type hints and docstring at import time and synthesising the schema automatically. Your function signature
is the contract the model reads. Change a parameter name or type hint, and the schema updates instantly. Add a sentence to the docstring, and the model sees a richer description immediately.
This is the core bet: a well-typed, well-documented Python function is already most of what a good tool schema needs.
The three primitives
MCP defines three kinds of things a server can expose:
| Primitive | Purpose | FastMCP decorator |
|---|---|---|
| Tool | An action the model can invoke — runs code, returns a result | @mcp.tool |
| Resource | Read-only data the model (or client) can fetch — files, DB rows, configs | @mcp.resource |
| Prompt | A reusable message template — lets the server ship canned system prompts | @mcp.prompt |
Most MCP servers you will build start with tools. Resources and prompts become useful once your server grows: resources let you expose structured data without tool overhead; prompts let you version your best system messages alongside the server code that uses them.
Building a minimal FastMCP server
Install with pip install fastmcp (see the current
FastMCP docs for the exact version constraints).
from fastmcp import FastMCP
# Create the server — the name shows up in client UIs
mcp = FastMCP("my-tools")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two integers and return the sum."""
return a + b
@mcp.tool
def lookup_order(order_id: str) -> dict:
"""Return status information for an order.
Args:
order_id: The unique order identifier (e.g. 'ORD-12345').
"""
# Replace with your real database call
return {"order_id": order_id, "status": "shipped", "eta": "2026-06-07"}
if __name__ == "__main__":
mcp.run() # defaults to stdio transport
That is a complete, runnable MCP server. The add tool exposes an
a: int, b: int schema; the model cannot pass a string for a — FastMCP
validates it. The docstrings become the tool descriptions the model reads when
deciding whether to call a tool.
Exposing a resource
A resource is addressed by a URI. The client reads it; the model does not invoke it like a function.
@mcp.resource("config://app-settings")
def get_settings() -> dict:
"""Return the current application configuration."""
return {"debug": False, "max_retries": 3, "region": "us-east-1"}
The URI scheme (config://) is arbitrary — choose something that makes sense
for your domain. See the FastMCP docs for dynamic resource URIs that embed
path parameters.
Adding a reusable prompt
@mcp.prompt
def support_system_prompt(customer_tier: str) -> str:
"""Return the system prompt for a support agent, tuned by customer tier."""
if customer_tier == "enterprise":
return "You are a senior support engineer with SLA: 1 h. Be concise."
return "You are a friendly support agent. Resolve the issue in one reply."
Clients that support prompts can pull this template and inject it at the start of a conversation — keeping your prompt versions colocated with the code that implements them.
Transport: stdio vs HTTP
FastMCP supports two transports:
stdio — the server process communicates over stdin/stdout. This is the
default (mcp.run()) and is what Claude Desktop uses when you add a server
to its config. The client spawns your process; no port, no auth, no network.
Streamable HTTP — the server listens on a port so remote clients can
connect. Pass transport="http" and a port to mcp.run(). The older
SSE-only transport (transport="sse") was deprecated in the 2026-07-28 spec
revision; it still works through the twelve-month deprecation window, but point
new servers at "http". Check the current FastMCP docs for the exact call
signature and authentication options, as these evolve with the spec.
if __name__ == "__main__":
# For remote clients:
mcp.run(transport="http", port=8000)
One consequence of the stateless spec
Since the 2026-07-28 revision there is no initialize handshake and no
Mcp-Session-Id header: each request carries its own protocol version and
client identity, and any request may land on any instance of your server. Your
framework speaks that wire format for you, so the decorators above are
unchanged — but your design has to agree with it. If a tool stashes something
in a module-level dict keyed by connection and expects the next call to find it
there, that call may arrive at a different process. Return an explicit handle
instead — a cart ID, a job ID — and take it back as an ordinary argument on the
next call. Then check it belongs to the caller; a handle is a name, not a
password.
How Claude Desktop discovers and calls your tools
The diagram below shows the full path from your decorated functions to the model invoking them.
Decorated Python functions → FastMCP server (protocol plumbing) → MCP client invokes them
To wire up Claude Desktop, add an entry to its claude_desktop_config.json
pointing at your server script:
{
"mcpServers": {
"my-tools": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}
Claude Desktop will spawn your server process on startup, call
tools/list to discover what you expose, and then invoke individual tools
during conversations exactly as it would invoke any built-in capability.
What you have now
A FastMCP server is a standard Python process. You can import your existing database clients, HTTP libraries, and business logic unchanged. FastMCP handles the protocol; you handle the domain logic. Because MCP is a vendor- neutral open standard, the same server works with Claude Desktop today, an ADK agent tomorrow, and whatever MCP-compatible client ships next quarter — write once, connect anywhere.
In one breath
- FastMCP sits on the official MCP SDK and removes the boilerplate — you decorate ordinary Python functions and it speaks the protocol for you.
- The whole trick: it reads your type hints + docstring at import time to synthesise the JSON schema the model reads — your function signature is the contract.
- Three primitives, three decorators:
@mcp.tool(an action),@mcp.resource(read-only data at a URI),@mcp.prompt(a reusable template). - Run over stdio (default; Claude Desktop spawns your process) or streamable HTTP (
transport="http", for remote clients; the SSE-only transport is deprecated). - The protocol is stateless since 2026-07-28 — any call can hit any instance, so keep cross-call state in an explicit handle you return and re-accept, not in per-connection memory.
- Tools run real code the model invokes without a second prompt — scope each narrowly, validate inputs, and write a precise docstring (it’s the contract).
Quick check
Quick check
Practice this in an interview
All questionsMCP is an open protocol originally introduced by Anthropic that standardizes how an AI host discovers and uses tools, resources, and prompt templates exposed by separate servers. It solves bespoke integration sprawl by giving each server one common interface, while leaving model orchestration, permissions, and user approval to the host.
In a normal MCP deployment, blindly forwarding the client’s bearer token to a downstream API violates the token’s audience boundary and can create confused-deputy, replay, and audit risks. The server should validate a token intended for itself, authorize the tool call, and obtain a separate, narrowly scoped credential for the downstream resource.
Tool poisoning is malicious instruction content in a tool description or result; cross-server shadowing uses one server’s names or content to influence or misroute another server’s capability; a rug pull changes a previously reviewed capability later. Defenses combine origin-aware namespaces, isolated trust contexts, capability snapshots and change review, exact-argument authorization, sandboxing, and runtime monitoring.
gRPC uses HTTP/2 and Protocol Buffers to deliver lower latency, strongly typed contracts, and built-in streaming, making it the better choice for high-throughput internal model services. REST remains the standard for public-facing APIs where broad client compatibility and human-readable payloads matter more than raw performance.