> ## Documentation Index

> Fetch the complete documentation index at: /llms.txt

> Use this file to discover all available pages before exploring further.

Skip to main content
Web Frameworks

# FastAPI 🤝 FastMCP

Integrate FastMCP with FastAPI applications

FastMCP provides two powerful ways to integrate with FastAPI applications:

1. Generate an MCP server FROM your FastAPI app - Convert existing API endpoints into MCP tools
2. Mount an MCP server INTO your FastAPI app - Add MCP functionality to your web application

When generating an MCP server from FastAPI, FastMCP uses OpenAPIProvider (v3.0.0+) under the hood to source tools from your FastAPI app’s OpenAPI spec. See Providers to understand how FastMCP sources components.
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.

##
​
Example FastAPI Application

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 this FastAPI server into other code blocks in this guide

    from fastapi import FastAPI, HTTPException
    from pydantic import BaseModel

    # Models
    class Product(BaseModel):
        name: str
        price: float
        category: str
        description: str | None = None

    class ProductResponse(BaseModel):
        id: int
        name: str
        price: float
        category: str
        description: str | None = None

    # Create FastAPI app
    app = FastAPI(title="E-commerce API", version="1.0.0")

    # In-memory database
    products_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`.

##
​
Generating an MCP Server

New in version `2.0.0` One 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.

###
​
Basic Conversion

Convert the FastAPI app to an MCP server with a single line:

    # Assumes the FastAPI app from above is already defined
    from fastmcp import FastMCP

    # Convert to MCP server
    mcp = FastMCP.from_fastapi(app=app)

    if __name__ == "__main__":
        mcp.run()

###
​
Adding Components

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.

    # Assumes the FastAPI app from above is already defined
    from fastmcp import FastMCP

    # Convert to MCP server
    mcp = FastMCP.from_fastapi(app=app)

    # Add a new tool
    @mcp.tool
    def get_product(product_id: int) -> ProductResponse:
        """Get a product by ID."""
        return products_db[product_id]

    # Run the MCP server
    if __name__ == "__main__":
        mcp.run()

###
​
Interacting with the MCP Server

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.

    # Assumes the FastAPI app from above is already defined
    from fastmcp import FastMCP
    from fastmcp.client import Client
    import asyncio

    # Convert to MCP server
    mcp = 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())

###
​
Custom Route Mapping

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:

    # Assumes the FastAPI app from above is already defined
    from fastmcp import FastMCP
    from fastmcp.server.providers.openapi import RouteMap, MCPType

    # Custom mapping rules
    mcp = 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

To learn more about customizing the conversion process, see the OpenAPI Integration guide.

###
​
Authentication and Headers

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:

    # Assumes the FastAPI app from above is already defined
    from fastmcp import FastMCP

    # Add authentication to your FastAPI app
    from fastapi import Depends, Header
    from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

    security = 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 headers
    mcp = FastMCP.from_fastapi(
        app=app,
        httpx_client_kwargs={
            "headers": {
                "Authorization": "Bearer secret-token",
            }
        }
    )

##
​
Mounting an MCP Server

New in version `2.3.1` In 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.

###
​
Basic Mounting

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.

    from fastmcp import FastMCP
    from fastapi import FastAPI

    # Create MCP server
    mcp = FastMCP("Analytics Tools")

    @mcp.tool
    def 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 server
    mcp_app = mcp.http_app(path='/mcp')

    # Key: Pass lifespan to FastAPI
    app = FastAPI(title="E-commerce API", lifespan=mcp_app.lifespan)

    # Mount the MCP server
    app.mount("/analytics", mcp_app)

    # Now: API at /products/*, MCP at /analytics/mcp/

##
​
Offering an LLM-Friendly API

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:

    # Assumes the FastAPI app from above is already defined
    from fastmcp import FastMCP
    from fastapi import FastAPI

    # 1. Generate MCP server from your API
    mcp = FastMCP.from_fastapi(app=app, name="E-commerce MCP")

    # 2. Create the MCP's ASGI app
    mcp_app = mcp.http_app(path='/mcp')

    # 3. Create a new FastAPI app that combines both sets of routes
    combined_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.

##
​
Key Considerations

###
​
Operation IDs

FastAPI operation IDs become MCP component names. Always specify meaningful operation IDs:

    # Good - explicit operation_id
    @app.get("/users/{user_id}", operation_id="get_user_by_id")
    def get_user(user_id: int):
        return {"id": user_id}

    # Less ideal - auto-generated name
    @app.get("/users/{user_id}")
    def get_user(user_id: int):
        return {"id": user_id}

###
​
Lifespan Management

When mounting MCP servers, always pass the lifespan context:

    # Correct - lifespan passed, path="/" since we mount at /mcp
    mcp_app = mcp.http_app(path="/")
    app = FastAPI(lifespan=mcp_app.lifespan)
    app.mount("/mcp", mcp_app)  # MCP endpoint at /mcp

    # Incorrect - missing lifespan
    app = FastAPI()
    app.mount("/mcp", mcp.http_app(path="/"))  # Session manager won't initialize

If you’re mounting an authenticated MCP server under a path prefix, see Mounting Authenticated Servers for important OAuth routing considerations.

###
​
CORS Middleware

If your FastAPI app uses `CORSMiddleware` and you’re mounting an OAuth-protected FastMCP server, avoid adding application-wide CORS middleware. FastMCP and the MCP SDK already handle CORS for OAuth routes, and layering CORS middleware can cause conflicts (such as 404 errors on `.well-known` routes or OPTIONS requests). If you need CORS on your own FastAPI routes, use the sub-app pattern: mount your API and FastMCP as separate apps, each with their own middleware, rather than adding top-level `CORSMiddleware` to the combined application.

###
​
Combining Lifespans

If your FastAPI app already has a lifespan (for database connections, startup tasks, etc.), you can’t simply replace it with the MCP lifespan. Use `combine_lifespans` to run both:

    from fastapi import FastAPI
    from fastmcp import FastMCP
    from fastmcp.utilities.lifespan import combine_lifespans
    from contextlib import asynccontextmanager

    # Your existing lifespan
    @asynccontextmanager
    async def app_lifespan(app: FastAPI):
        print("Starting up the app...")
        yield
        print("Shutting down the app...")

    # Create MCP server
    mcp = FastMCP("Tools")
    mcp_app = mcp.http_app(path="/")

    # Combine both lifespans
    app = FastAPI(lifespan=combine_lifespans(app_lifespan, mcp_app.lifespan))
    app.mount("/mcp", mcp_app)  # MCP endpoint at /mcp

`combine_lifespans` enters lifespans in order and exits in reverse order.

###
​
Performance Tips

1. Use in-memory transport for testing - Pass MCP servers directly to clients
2. Design purpose-built MCP tools - Better than auto-converting complex APIs
3. Keep tool parameters simple - LLMs perform better with focused interfaces

For more details on configuration options, see the OpenAPI Integration guide.
WorkOS 🤝 FastMCP
Previous
OpenAPI 🤝 FastMCP
Next
Ctrl+I
Assistant
Responses are generated using AI and may contain mistakes.
\n\n
\n
\n \n
\n
","contacts":" \n
\n\n Prefab\n \n \n \n \n\n
\n
\n \n
\n
","dashboard":" \n
\n\n Prefab\n \n \n \n \n\n
\n
\n \n
\n
","data-table":" \n
\n\n Prefab\n \n \n \n \n\n
\n
\n \n
\n
","hitchhikers":" \n
\n\n Prefab Showcase\n \n \n \n \n\n
\n
\n \n
\n
","pie-chart":" \n
\n\n Prefab\n \n \n \n \n\n
\n
\n \n
\n
","reactive":" \n
\n\n Prefab\n \n \n \n \n\n
\n
\n \n
\n
","team-directory-reactive":" \n
\n\n Prefab\n \n \n \n \n\n
\n
\n \n
\n
","team-directory":" \n
\n\n Prefab\n \n \n \n \n\n
\n
\n \n
\n
"};