Your FastAPI application serves HTTP requests from human callers and automated scripts perfectly. But AI agents cannot discover your endpoints, understand their parameters, or use them safely without hand-written integration code for every route. The Model Context Protocol fixes this. This tutorial shows two concrete patterns for combining FastAPI and MCP — one for teams with an existing FastAPI codebase, and one for teams building a new AI-native service from scratch.
In this tutorial, you will learn to:
- Understand the two architectural patterns for combining FastAPI and MCP
- Use the
fastapi_mcplibrary to auto-expose FastAPI routes as MCP tools - Use FastMCP to build an MCP server alongside your FastAPI app
- Choose the right pattern based on your codebase situation
- Secure MCP endpoints added to a FastAPI app
Page Contents
The Two Patterns
There are two fundamentally different ways to bring FastAPI and MCP together, and picking the right one depends on what you already have.
Pattern 1 — Wrap existing FastAPI routes as MCP tools. Use fastapi_mcp, the library from tadata-org. It introspects your FastAPI app’s route table and automatically generates MCP tool definitions from your endpoint signatures. No duplication — your existing routes become AI-callable tools automatically. Best for teams with a mature FastAPI codebase who want AI agents to consume the same API humans do.
Pattern 2 — Build a companion MCP server alongside your FastAPI app. Use FastMCP, the higher-level framework from PrefectHQ. It gives you FastAPI-style decorators (@mcp.tool(), @mcp.resource()) to define MCP primitives, then mounts the resulting server as a separate HTTP/SSE endpoint inside your FastAPI app. Best for new projects where you design the MCP surface area from scratch.
Prerequisites
This tutorial continues from Build Your First MCP Server with Python SDK — Fundamentals, where we built a standalone weather MCP server using the official Python SDK. If you have not read that article yet, start there first.
You will also need uv installed and a FastAPI project. If you do not have one, create a minimal app:
uv pip install fastapi uvicorn fastapi-mcp
Pattern 1: fastapi_mcp — Wrap Existing Routes as MCP Tools
The fastapi_mcp library (GitHub: tadata-org/fastapi_mcp) takes the opposite approach of the low-level MCP Python SDK. Instead of writing MCP server code, you point it at an existing FastAPI app and it generates MCP tool definitions from your route signatures automatically.
Suppose you have an e-commerce FastAPI app with endpoints for listing products, fetching details, and placing orders. Here is how to make all of them AI-callable with four lines of code.
# main.py — an existing FastAPI application
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel
app = FastAPI(title="ShopAPI")
class Product(BaseModel):
id: int
name: str
price: float
stock: int
class Order(BaseModel):
product_id: int
quantity: int
# WARNING: mock data — replace with a real database in production
_products_db = [
{"id": 1, "name": "Mechanical Keyboard", "price": 89.99, "stock": 23},
{"id": 2, "name": "USB-C Hub", "price": 45.50, "stock": 11},
{"id": 3, "name": "Monitor Arm", "price": 120.00, "stock": 5},
]
@app.get("/products", response_model=list[Product])
def list_products(category: str | None = None):
"""List all products, optionally filtered by category."""
if category:
return [p for p in _products_db if category.lower() in p["name"].lower()]
return _products_db
@app.get("/products/{product_id}", response_model=Product)
def get_product(product_id: int):
"""Get a single product by ID."""
product = next((p for p in _products_db if p["id"] == product_id), None)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
return product
@app.post("/orders", status_code=201)
def place_order(order: Order):
"""Place an order for a product."""
product = next((p for p in _products_db if p["id"] == order.product_id), None)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
if product["stock"] < order.quantity:
raise HTTPException(status_code=400, detail="Insufficient stock")
return {"order_id": 999, "status": "confirmed", "product": product["name"]}
Now mount the MCP server on top of this app. The library reads your route decorators — @app.get, @app.post — and exposes each one as an MCP tool with the same parameters and return types.
# main.py — add MCP to the existing FastAPI app
from fastapi import FastAPI, HTTPException, Query
from fastapi_mcp import FastApiMcp
from pydantic import BaseModel
app = FastAPI(title="ShopAPI")
# Mount the MCP server — auto-generates tools from all route decorators
mcp = FastApiMcp(app)
mcp.mount(app)
# ... rest of your existing routes unchanged ...
class Product(BaseModel):
id: int
name: str
price: float
stock: int
class Order(BaseModel):
product_id: int
quantity: int
_products_db = [
{"id": 1, "name": "Mechanical Keyboard", "price": 89.99, "stock": 23},
{"id": 2, "name": "USB-C Hub", "price": 45.50, "stock": 11},
{"id": 3, "name": "Monitor Arm", "price": 120.00, "stock": 5},
]
@app.get("/products", response_model=list[Product])
def list_products(category: str | None = None):
if category:
return [p for p in _products_db if category.lower() in p["name"].lower()]
return _products_db
@app.get("/products/{product_id}", response_model=Product)
def get_product(product_id: int):
product = next((p for p in _products_db if p["id"] == product_id), None)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
return product
@app.post("/orders", status_code=201)
def place_order(order: Order):
product = next((p for p in _products_db if p["id"] == order.product_id), None)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
if product["stock"] < order.quantity:
raise HTTPException(status_code=400, detail="Insufficient stock")
return {"order_id": 999, "status": "confirmed", "product": product["name"]}
Run the server and navigate to http://localhost:8000/mcp — the library mounts an MCP endpoint at /mcp following the HTTP/SSE transport. AI agents that support MCP over HTTP can now discover and call your FastAPI endpoints directly. The MCP tool names are derived from the route paths: list_products, get_product, place_order.
uv pip install fastapi uvicorn fastapi-mcp
uvicorn main:app --reload
The library also supports fine-grained control: expose only specific routes, add authentication middleware to the MCP endpoint, or prefix tool names to avoid collisions with other MCP servers.
# Expose only routes tagged "mcp" — skip admin/internal routes
mcp = FastApiMcp(
app,
name="shop-mcp",
description="AI-accessible endpoints of the ShopAPI",
include_tags=["mcp"],
)
# Add authentication to the MCP endpoint
from fastapi import Security
from fastapi.security import HTTPBearer
auth = HTTPBearer()
@app.get("/products", tags=["mcp"])
def list_products(Authorization: str = Security(auth)):
# Now requires a Bearer token when called via MCP
...
Pattern 2: FastMCP — Build a Companion MCP Server
FastMCP (from PrefectHQ, at gofastmcp.com) is a higher-level framework purpose-built for MCP. It uses FastAPI under the hood but gives you MCP-native primitives: @mcp.tool(), @mcp.resource(), and @mcp.prompt() decorators that feel familiar if you have used FastAPI’s route decorators.
FastMCP is the better choice when you are starting a new AI-native service, because you design the MCP tool surface directly rather than deriving it from existing HTTP routes. You can also mount FastMCP as a sub-application inside an existing FastAPI app, giving you a separate HTTP/SSE endpoint for AI agents.
uv pip install fastmcp
Here is the same weather server from the first article, rewritten with FastMCP. The behaviour is identical to the raw SDK version, but the decorator-based API is cleaner and more concise.
# weather_fastmcp.py — FastMCP version of the weather server
import fastmcp
# FastMCP handles transport, initialization, and type coercion automatically
mcp = fastmcp.FastMCP("weather-server")
@mcp.tool()
def get_forecast(city: str, country: str = "") -> str:
"""
Get a 3-day weather forecast for a given city.
Args:
city: Name of the city (e.g. Bangalore, London)
country: Country code (e.g. IN, GB). Optional.
"""
# WARNING: Mock data. Replace with a real API call before production.
forecast = {
"city": city,
"country": country or "Unknown",
"days": [
{"day": "Monday", "condition": "Sunny", "high": 28, "low": 18},
{"day": "Tuesday", "condition": "Partly Cloudy", "high": 26, "low": 17},
{"day": "Wednesday", "condition": "Rainy", "high": 22, "low": 15},
],
}
return str(forecast)
@mcp.resource("weather://supported-cities")
def supported_cities() -> str:
"""List of cities with forecast data available."""
return "Bangalore, London, New York, Tokyo, Sydney"
if __name__ == "__main__":
mcp.run()
FastMCP runs the server over stdio by default. To mount it inside an existing FastAPI app using HTTP/SSE transport, use the ASGI interface:
# app_with_mcp.py — mount FastMCP inside an existing FastAPI app
from fastapi import FastAPI
from fastmcp import FastMCP
app = FastAPI(title="MyApp")
# Create the FastMCP server
mcp_server = FastMCP("my-server")
@mcp_server.tool()
def search_products(query: str, limit: int = 10) -> str:
"""Search the product catalogue."""
# Replace with a real search call
return f"Found 3 products matching '{query}'"
# Mount at /mcp using ASGI — FastMCP adds the route automatically
mcp_server.mount(app, path="/mcp")
Starting from FastMCP 3.0, the library added first-class support for FastAPI dependency injection inside tool handlers. This means you can use FastAPI’s Depends(), security scopes, and database sessions inside MCP tools — bringing the full power of FastAPI’s dependency system into the MCP world.
When to Use Which Pattern
| Criteria | fastapi_mcp | FastMCP |
|---|---|---|
| Use when | Existing FastAPI app, want AI to call same routes as humans | New AI-native service, or want to design MCP surface from scratch |
| Tool naming | Auto-derived from route function names | You define tool names explicitly |
| HTTP/SSE mount | Built-in, mounts at /mcp | Via mcp.mount(app, path="/mcp") |
| Dependency injection | Full FastAPI DI on all routes | FastMCP 3.0+ supports FastAPI Depends() |
| Authentication | Apply FastAPI security decorators to routes | Apply FastAPI security decorators to tools |
| Learning curve | Low — 4 lines to add to existing app | Medium — new decorator syntax to learn |
Security Considerations
Adding an MCP endpoint to a FastAPI app exposes your routes to AI agents in a structured, machine-readable way — which is powerful but introduces new attack surface. Treat your MCP endpoint with the same care you would treat an admin API.
- Authentication — Every AI agent that connects to your MCP endpoint should authenticate. The
fastapi_mcplibrary supports FastAPI security decorators; add anHTTPBeareror OAuth2 flow to the MCP mount path. - Tool allowlisting — Do not expose every route via MCP. Use the
include_tagsparameter infastapi_mcpto expose only routes intentionally designed for AI use. Internal admin endpoints should never become MCP tools. - Rate limiting — An AI agent can call your MCP endpoint hundreds of times per minute. Add rate limiting middleware at the FastAPI level to protect your backend.
- Tool result size — MCP tool responses go back to an LLM context window. If your
/ordersendpoint returns a 50,000-row order history, the LLM will spend tokens processing it unnecessarily. Return only what the agent needs.
Common Mistakes and Gotchas
- Mixing FastAPI Depends() with stdio transport — FastMCP and fastapi_mcp both support FastAPI’s dependency injection system, but only when the MCP server is mounted over HTTP/SSE. Stdio transport cannot carry HTTP-level authentication headers, so any
Depends()that reads headers will silently receive empty values. - Exposing POST endpoints without idempotency checks — A
POST /ordersendpoint exposed as an MCP tool can be retried by an LLM that does not receive a response. If your endpoint is not idempotent, add an idempotency key header or use an optimistic locking pattern. - Tool name collisions in fastapi_mcp — If two route functions share the same name across different APIRouters, their MCP tool names will collide. Use the
tool_name_prefixparameter to namespace them. - FastMCP stdio vs HTTP mount — FastMCP defaults to stdio transport. Mounting it inside FastAPI switches to HTTP/SSE, which requires the client to connect via HTTP. Make sure your MCP client configuration matches the transport type.
Summary and Next Steps
You now know both patterns for combining FastAPI and MCP. Use fastapi_mcp when you have an existing FastAPI app and want AI agents to consume the same routes humans do — add four lines and your entire API becomes AI-discoverable. Use FastMCP when you are building a new AI-native service and want clean MCP primitives with FastAPI-style developer ergonomics.
The first article in this series covers the MCP Python SDK fundamentals for when you need full control over the protocol layer. The third article covers production hardening: adding authentication to MCP endpoints, writing integration tests, deploying with Docker, and switching from stdio to Streamable HTTP for remote deployments.
- fastapi_mcp GitHub: github.com/tadata-org/fastapi_mcp
- FastMCP documentation: gofastmcp.com
- FastMCP GitHub: github.com/PrefectHQ/fastmcp
- MCP specification: modelcontextprotocol.io

