New in 2.11: FastMCP is introducing a next-generation OpenAPI parser. The new parser has greatly improved performance and compatibility, and is also easier to maintain. To enable it, set the environment variable FASTMCP_EXPERIMENTAL_ENABLE_NEW_OPENAPI_PARSER=true.The new parser is largely API-compatible with the existing implementation and will become the default in a future version. We encourage all users to test it and report any issues before it becomes the default.
FastMCP provides two powerful ways to integrate with FastAPI applications:
Generating MCP servers from OpenAPI is a great way to get started with FastMCP, but in practice LLMs achieve significantly better performance with well-designed and curated MCP servers than with auto-converted OpenAPI servers. This is especially true for complex APIs with many endpoints and parameters.We recommend using the FastAPI integration for bootstrapping and prototyping, not for mirroring your API to LLM clients. See the post Stop Converting Your REST APIs to MCP for more details.
FastMCP does not include FastAPI as a dependency; you must install it separately to use this integration.
Throughout this guide, we’ll use this e-commerce API as our example (click the Copy button to copy it for use with other code blocks):
Copy
# Copy this FastAPI server into other code blocks in this guidefrom fastapi import FastAPI, HTTPExceptionfrom pydantic import BaseModel# Modelsclass Product(BaseModel): name: str price: float category: str description: str | None = Noneclass ProductResponse(BaseModel): id: int name: str price: float category: str description: str | None = None# Create FastAPI appapp = FastAPI(title="E-commerce API", version="1.0.0")# In-memory databaseproducts_db = { 1: ProductResponse( id=1, name="Laptop", price=999.99, category="Electronics" ), 2: ProductResponse( id=2, name="Mouse", price=29.99, category="Electronics" ), 3: ProductResponse( id=3, name="Desk Chair", price=299.99, category="Furniture" ),}next_id = 4@app.get("/products", response_model=list[ProductResponse])def list_products( category: str | None = None, max_price: float | None = None,) -> list[ProductResponse]: """List all products with optional filtering.""" products = list(products_db.values()) if category: products = [p for p in products if p.category == category] if max_price: products = [p for p in products if p.price <= max_price] return products@app.get("/products/{product_id}", response_model=ProductResponse)def get_product(product_id: int): """Get a specific product by ID.""" if product_id not in products_db: raise HTTPException(status_code=404, detail="Product not found") return products_db[product_id]@app.post("/products", response_model=ProductResponse)def create_product(product: Product): """Create a new product.""" global next_id product_response = ProductResponse(id=next_id, **product.model_dump()) products_db[next_id] = product_response next_id += 1 return product_response@app.put("/products/{product_id}", response_model=ProductResponse)def update_product(product_id: int, product: Product): """Update an existing product.""" if product_id not in products_db: raise HTTPException(status_code=404, detail="Product not found") products_db[product_id] = ProductResponse( id=product_id, **product.model_dump(), ) return products_db[product_id]@app.delete("/products/{product_id}")def delete_product(product_id: int): """Delete a product.""" if product_id not in products_db: raise HTTPException(status_code=404, detail="Product not found") del products_db[product_id] return {"message": "Product deleted"}
All subsequent code examples in this guide assume you have the above FastAPI application code already defined. Each example builds upon this base application, app.
New in version:2.0.0One of the most common ways to bootstrap an MCP server is to generate it from an existing FastAPI application. FastMCP will expose your FastAPI endpoints as MCP components (tools, by default) in order to expose your API to LLM clients.
Your converted MCP server is a full FastMCP instance, meaning you can add new tools, resources, and other components to it just like you would with any other FastMCP instance.
Copy
# Assumes the FastAPI app from above is already definedfrom fastmcp import FastMCP# Convert to MCP servermcp = FastMCP.from_fastapi(app=app)# Add a new tool@mcp.tooldef get_product(product_id: int) -> ProductResponse: """Get a product by ID.""" return products_db[product_id]# Run the MCP serverif __name__ == "__main__": mcp.run()
Once you’ve converted your FastAPI app to an MCP server, you can interact with it using the FastMCP client to test functionality before deploying it to an LLM-based application.
Copy
# Assumes the FastAPI app from above is already definedfrom fastmcp import FastMCPfrom fastmcp.client import Clientimport asyncio# Convert to MCP servermcp = FastMCP.from_fastapi(app=app)async def demo(): async with Client(mcp) as client: # List available tools tools = await client.list_tools() print(f"Available tools: {[t.name for t in tools]}") # Create a product result = await client.call_tool( "create_product_products_post", { "name": "Wireless Keyboard", "price": 79.99, "category": "Electronics", "description": "Bluetooth mechanical keyboard" } ) print(f"Created product: {result.data}") # List electronics under $100 result = await client.call_tool( "list_products_products_get", {"category": "Electronics", "max_price": 100} ) print(f"Affordable electronics: {result.data}")if __name__ == "__main__": asyncio.run(demo())
Because FastMCP’s FastAPI integration is based on its OpenAPI integration, you can customize how endpoints are converted to MCP components in exactly the same way. For example, here we use a RouteMap to map all GET requests to MCP resources, and all POST/PUT/DELETE requests to MCP tools:
Copy
# Assumes the FastAPI app from above is already definedfrom fastmcp import FastMCPfrom fastmcp.server.openapi import RouteMap, MCPType# If using experimental parser, import from experimental module:# from fastmcp.experimental.server.openapi import RouteMap, MCPType# Custom mapping rulesmcp = FastMCP.from_fastapi( app=app, route_maps=[ # GET with path params → ResourceTemplates RouteMap( methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE ), # Other GETs → Resources RouteMap( methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE ), # POST/PUT/DELETE → Tools (default) ],)# Now:# - GET /products → Resource# - GET /products/{id} → ResourceTemplate# - POST/PUT/DELETE → Tools
You can configure headers and other client options via the httpx_client_kwargs parameter. For example, to add authentication to your FastAPI app, you can pass a headers dictionary to the httpx_client_kwargs parameter:
Copy
# Assumes the FastAPI app from above is already definedfrom fastmcp import FastMCP# Add authentication to your FastAPI appfrom fastapi import Depends, Headerfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentialssecurity = HTTPBearer()def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): if credentials.credentials != "secret-token": raise HTTPException(status_code=401, detail="Invalid authentication") return credentials.credentials# Add a protected endpoint@app.get("/admin/stats", dependencies=[Depends(verify_token)])def get_admin_stats(): return { "total_products": len(products_db), "categories": list(set(p.category for p in products_db.values())) }# Create MCP server with authentication headersmcp = FastMCP.from_fastapi( app=app, httpx_client_kwargs={ "headers": { "Authorization": "Bearer secret-token", } })
New in version:2.3.1In addition to generating servers, FastMCP can facilitate adding MCP servers to your existing FastAPI application. You can do this by mounting the MCP ASGI application.
To mount an MCP server, you can use the http_app method on your FastMCP instance. This will return an ASGI application that can be mounted to your FastAPI application.
Copy
from fastmcp import FastMCPfrom fastapi import FastAPI# Create MCP servermcp = FastMCP("Analytics Tools")@mcp.tooldef analyze_pricing(category: str) -> dict: """Analyze pricing for a category.""" products = [p for p in products_db.values() if p.category == category] if not products: return {"error": f"No products in {category}"} prices = [p.price for p in products] return { "category": category, "avg_price": round(sum(prices) / len(prices), 2), "min": min(prices), "max": max(prices), }# Create ASGI app from MCP servermcp_app = mcp.http_app(path='/mcp')# Key: Pass lifespan to FastAPIapp = FastAPI(title="E-commerce API", lifespan=mcp_app.lifespan)# Mount the MCP serverapp.mount("/analytics", mcp_app)# Now: API at /products/*, MCP at /analytics/mcp/
A common pattern is to generate an MCP server from your FastAPI app and serve both interfaces from the same application. This provides an LLM-optimized interface alongside your regular API:
Copy
# Assumes the FastAPI app from above is already definedfrom fastmcp import FastMCPfrom fastapi import FastAPI# 1. Generate MCP server from your APImcp = FastMCP.from_fastapi(app=app, name="E-commerce MCP")# 2. Create the MCP's ASGI appmcp_app = mcp.http_app(path='/mcp')# 3. Create a new FastAPI app that combines both sets of routescombined_app = FastAPI( title="E-commerce API with MCP", routes=[ *mcp_app.routes, # MCP routes *app.routes, # Original API routes ], lifespan=mcp_app.lifespan,)# Now you have:# - Regular API: http://localhost:8000/products# - LLM-friendly MCP: http://localhost:8000/mcp/# Both served from the same FastAPI application!
This approach lets you maintain a single codebase while offering both traditional REST endpoints and MCP-compatible endpoints for LLM clients.
If your FastAPI app already has a lifespan (for database connections, startup tasks, etc.), you can’t simply replace it with the MCP lifespan. Instead, you need to create a new lifespan function that manages both contexts. This ensures that both your app’s initialization logic and the MCP server’s session manager run properly:
Copy
from contextlib import asynccontextmanagerfrom fastapi import FastAPIfrom fastmcp import FastMCP# Your existing lifespan@asynccontextmanagerasync def app_lifespan(app: FastAPI): # Startup print("Starting up the app...") # Initialize database, cache, etc. yield # Shutdown print("Shutting down the app...")# Create MCP servermcp = FastMCP("Tools")mcp_app = mcp.http_app(path='/mcp')# Combine both lifespans@asynccontextmanagerasync def combined_lifespan(app: FastAPI): # Run both lifespans async with app_lifespan(app): async with mcp_app.lifespan(app): yield# Use the combined lifespanapp = FastAPI(lifespan=combined_lifespan)app.mount("/mcp", mcp_app)
This pattern ensures both your app’s initialization logic and the MCP server’s session manager are properly managed. The key is using nested async with statements - the inner context (MCP) will be initialized after the outer context (your app), and cleaned up before it. This maintains the correct initialization and cleanup order for all your resources.