mcp-fastapi • FastAPI Model Context Protocol
PyPI v0.1.0 GitHub llms.txt llms-full.txt

mcp-fastapi(fast-mcp)

★ Star on GitHub

The FastAPI-native Model Context Protocol framework.

Mount Model Context Protocol (MCP) servers directly onto existing FastAPI applications in one line of code. Automatic route reflection, ASGI scope bridging for header & bearer auth, dynamic progressive tool discovery, resilient error recovery, and interactive in-chat MCP Apps (SEP-1865).

108 Tests Passed
Python 3.11+
FastAPI 0.110+
MCP Specification 1.0+
License: MIT
pip install mcp-fastapi
01 / Getting Started

Installation & Quickstart

Install the package using your preferred Python package manager, or run it instantaneously using uvx without prior installation.

pip install mcp-fastapi
main.py — Minimal Complete Working Example
from fastapi import FastAPI, Depends, Header, HTTPException
from pydantic import BaseModel
from fast_mcp import FastMCP

app = FastAPI(title="Store API", version="1.0.0")
mcp = FastMCP(app=app, name="store-mcp")

# 1. Existing FastAPI route automatically reflected as an MCP tool
class Product(BaseModel):
    id: int
    name: str
    price: float

@app.get("/products/{product_id}", tags=["mcp"])
async def get_product(product_id: int) -> Product:
    """Fetch product details by ID."""
    if product_id == 404:
        raise HTTPException(status_code=404, detail="Product not found")
    return Product(id=product_id, name="Smart Widget", price=29.99)

# 2. Custom composite AI tool with native FastAPI dependency injection
def verify_token(authorization: str = Header(...)) -> str:
    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid token")
    return authorization.split(" ")[1]

@mcp.tool(name="order_status", description="Check customer order status")
def check_order(order_id: str, user: str = Depends(verify_token)) -> dict:
    return {"order_id": order_id, "customer": user, "status": "Shipped"}

# 3. Mount MCP endpoints (/mcp/sse, /mcp/messages, /mcp/docs)
mcp.mount()

Run with standard ASGI servers (uvicorn main:app --reload) or launch over stdio for desktop AI clients with fast-mcp stdio main:app.

02 / Interactive Tour

The Seven Core Architectural Capabilities

Click through each capability card below to explore working code examples, protocol behavior, and technical rationale.

1. Automatic Route Reflection (tags=["mcp"])

Expose existing endpoints without rewriting handlers into standalone functions. Tagging a FastAPI route with tags=["mcp"] automatically inspects Pydantic schemas, path/query variables, and docstrings (Google, Sphinx, NumPy format) into full MCP JSON tool definitions.

Python Route Definition
class InventoryQuery(BaseModel):
    category: str
    max_price: float = 100.0

@app.post("/inventory/search", tags=["mcp"])
async def search_inventory(query: InventoryQuery, limit: int = 10) -> list[dict]:
    """Search catalog items by category and maximum price.

    Args:
        query: Filter specification with category and price limit.
        limit: Maximum number of records to return.
    """
    return [{"item": "Keyboard", "price": 49.99}]

2. Custom AI Tools (@mcp.tool)

Define AI-specialized composite tools alongside standard REST endpoints. Supports bare and parameterized decorators, custom names, docstring extraction, and metadata tagging.

Custom Tool Registration
# Bare decorator
@mcp.tool
def calculate_quote(quantity: int, discount: float = 0.0) -> float:
    """Calculate quotation with volume discount."""
    return quantity * 100.0 * (1.0 - discount)

# Parameterized decorator with custom name & tags
@mcp.tool(name="stock_check", description="Lookup stock quantity", tags=["inventory"])
async def check_inventory(sku: str) -> dict:
    return {"sku": sku, "in_stock": True, "count": 42}

3. ASGI Scope Bridging & Native Auth

Client authorization headers (Authorization: Bearer <token>, cookies, API keys) passed during SSE connection or message post are preserved and bridged into an in-memory ASGI Request. FastAPI's Depends() and Security() resolve seamlessly.

Scope Bridging Example
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fast_mcp import FastMCP, get_current_request

security = HTTPBearer()

def get_current_user(creds: HTTPAuthorizationCredentials = Depends(security)) -> str:
    token = creds.credentials
    if token != "secret-ai-token":
        raise HTTPException(status_code=401, detail="Invalid token")
    return "authorized_agent"

@mcp.tool
async def sensitive_action(user: str = Depends(get_current_user)) -> dict:
    # Access low-level request if needed
    request = get_current_request()
    return {"status": "ok", "caller": user, "ip": request.client.host}

4. Dynamic Progressive Tool Discovery

Avoid overwhelming LLM context windows when hosting hundreds of API routes. When enabled, tools/list only advertises baseline tools and the search_tools(query: str) meta-tool. FastMCP ranks candidates using the zero-dependency KeywordTagRouter.

Dynamic Discovery Configuration
mcp = FastMCP(
    app=app,
    dynamic_discovery=True,  # Or set dynamic_discovery_threshold=20
    baseline_tools=["search_tools", "get_health_status"],
    baseline_tag="core",
)

# When LLM asks to create an invoice, it first calls:
# search_tools(query="invoice")
# -> Returns matching tools: [create_invoice, get_invoice, cancel_invoice]

5. Resilient Error Interception & Custom Serializers

Traps route HTTPException (400, 404, 422) and Pydantic validation errors into clean CallToolResult(is_error=True) payloads. The LLM receives actionable error text to self-correct instead of crashing the transport. Serializers format responses into token-saving minified JSON or customized markdown.

Custom Serializer Example
class Report(BaseModel):
    title: str
    metrics: dict[str, int]

@mcp.serializer(Report)
def format_report(report: Report) -> str:
    """Format report into concise markdown table for LLM consumption."""
    md = f"### {report.title}\n"
    for k, v in report.metrics.items():
        md += f"- **{k}**: {v}\n"
    return md

6. Embedded /mcp/docs & In-Chat MCP Apps (SEP-1865)

Features an embedded browser inspector at /mcp/docs for human developers, plus native support for in-chat interactive iframes in desktop AI clients (Claude Desktop, Cursor, VS Code) via _meta.ui.resourceUri, the built-in inspect() tool, and @mcp.app().

MCP App Registration
@mcp.app(
    name="order_dashboard",
    resource_uri="ui://store/dashboard",
    html="""
    <div style="font-family: sans-serif; padding: 1.5rem; background: #f8fafc; border: 1px solid #e2e8f0;">
      <h2>Live Store Dashboard</h2>
      <p>Active Users: <strong>1,420</strong></p>
    </div>
    """
)
def live_dashboard() -> str:
    """Launch interactive store dashboard widget."""
    return '<iframe src="ui://store/dashboard" width="100%" height="400"></iframe>'

7. Local stdio CLI Runner & Desktop AI Bridge

Connect desktop AI clients (Claude Desktop, Cursor) directly to your FastAPI backend over standard input/output pipes with zero network setup, port conflicts, or external proxies.

CLI Usage & Programmatic stdio
# Shell Command:
fast-mcp stdio main:app

# Or Programmatically in Python:
import asyncio
from fast_mcp import FastMCP, run_stdio

mcp = FastMCP(name="my-stdio-server")

@mcp.tool()
def add(a: int, b: int) -> int:
    return a + b

if __name__ == "__main__":
    asyncio.run(run_stdio(mcp))
03 / Integration

MCP Client Configuration (Claude Desktop & Cursor)

Configure desktop AI assistants to connect directly to your FastMCP instance. Use uvx for zero-install instant execution, or connect via local CLI or SSE transport.

Add to claude_desktop_config.json. Uses uvx to automatically pull and execute mcp-fastapi in an isolated environment without manual installation:

claude_desktop_config.json
{
  "mcpServers": {
    "my-fastapi-backend": {
      "command": "uvx",
      "args": [
        "--from",
        "mcp-fastapi",
        "fast-mcp",
        "stdio",
        "main:app"
      ]
    }
  }
}

For environments where mcp-fastapi is already installed in your virtual environment:

claude_desktop_config.json
{
  "mcpServers": {
    "my-fastapi-backend": {
      "command": "fast-mcp",
      "args": [
        "stdio",
        "main:app"
      ]
    }
  }
}

In Cursor Settings > Features > MCP Servers, configure an stdio server:

Cursor MCP Configuration
{
  "mcpServers": {
    "fastapi-cursor-tools": {
      "command": "uvx",
      "args": ["--from", "mcp-fastapi", "fast-mcp", "stdio", "main:app"]
    }
  }
}

If your FastAPI app is already running via Uvicorn/Hypercorn, connect over Server-Sent Events (SSE):

SSE Client Configuration
{
  "mcpServers": {
    "fastapi-remote-sse": {
      "url": "http://127.0.0.1:8000/mcp/sse",
      "transport": "sse"
    }
  }
}
04 / Ecosystem

Supported Capabilities & Registry Compliance

mcp-fastapi implements the complete Model Context Protocol specification and complies with major MCP directory crawlers (Smithery, Glama, Pulse MCP, MCP.so, awesome-mcp-servers).

Capability Support Level Implementation Details
Tools Full Support (Native) Reflected FastAPI endpoints (tags=["mcp"]) and custom @mcp.tool decorators. Automatic Pydantic schema generation, docstring parsing, and dynamic discovery (search_tools).
Resources Full Support (SEP-1865) Custom ui:// scheme support, SEP-1865 interactive HTML iframe bundles, and developer web inspector at /mcp/docs.
Prompts Extensible Supports prompt registration and templating conforming to MCP prompt specs.
Transports Dual (stdio + sse) Standard I/O pipes for desktop AI clients (stdio) and HTTP/SSE ASGI mount (/mcp/sse & /mcp/messages).
Auth Bridging Native ASGI Bridges SSE/POST headers into in-memory ASGI Request, transparently solving FastAPI Depends() and Security().
05 / Deep Dive

Architecture & The Dual-Citizen ASGI Seam

How mcp-fastapi eliminates subprocess overhead, proxy latency, and authorization disconnects.

Traditional Subprocess Approach

Standard MCP implementations spawn external child processes or require external reverse proxies.

  • Requires separate worker processes and port allocation.
  • HTTP headers (Bearer tokens, cookies) do not reach internal dependencies.
  • Code duplication between REST API endpoints and MCP tool handlers.
  • Uncaught exceptions crash the transport pipe.

mcp-fastapi In-Process ASGI Mount

Mounts in-memory directly onto your existing FastAPI application.

  • Zero subprocess overhead; runs inside standard ASGI server.
  • ASGI Scope Bridging captures handshake headers into in-memory Request context.
  • Existing Depends() and Security() providers resolve without changes.
  • Traps HTTPException into actionable CallToolResult(is_error=True).
FASTAPI APPLICATION RUNTIME (IN-PROCESS ASGI SEAM) CLIENT TIER AI Agent Claude Desktop Cursor IDE / LLM Python MCP Client Bearer Token Auth GET /sse POST /msg TRANSPORT SseServerTransport Captures Handshake Scope DISPATCHER /mcp/messages JSON-RPC: tools/call CORE ARCHITECTURAL SEAM ASGIScopeBridge Reconstructs in-memory Request Preserves Headers & Cookies solve_dependencies() FastAPI Depends() & Security() DB sessions, Auth providers Args EXECUTION Tool Invocation Reflected Endpoint or Custom @mcp.tool RESULT TRAP CallToolResult Minified JSON / MRO HTTPException shielded Streamed SSE Protocol Response (CallToolResult)
STAGE 01
Handshake & Calls
AI client establishes SSE stream on /mcp/sse and posts JSON-RPC tool invocations to /mcp/messages.
STAGE 02
Scope Bridging
ASGIScopeBridge extracts handshake headers (Bearer tokens, cookies) and synthesizes an in-memory ASGI Request context.
STAGE 03
Dependency Solver
FastAPI’s native solve_dependencies() resolves Depends() and Security() providers with zero code modifications.
STAGE 04
Tool Execution
Dispatches to reflected route endpoint or custom @mcp.tool handler with fully resolved parameters and async/sync runtime support.
STAGE 05
Shielded Output
Traps route HTTPException into informative isError=True; serializes successful results to minified, token-efficient JSON.
06 / Specification

Comprehensive API Reference

Complete reference for all exported classes, methods, and functions in the fast_mcp package.

Symbol & Signature Type Description & Usage
FastMCP(...) Class Main framework coordinator. Accepts app, name, version, mount_path, route_tag, dynamic_discovery, router, baseline_tools, debug, and enable_ui.
mcp.mount(app=None) Method Mounts MCP server endpoints (/mcp/sse, /mcp/messages, /mcp/docs) onto the bound FastAPI application.
@mcp.tool(...) Decorator Registers a custom composite AI tool with automatic schema and docstring extraction. Accepts name, description, tags, meta, and ui.
@mcp.serializer(...) Decorator Registers custom response serialization functions for specific return types or Pydantic models. Resolves inheritance via MRO.
@mcp.app(...) Decorator Registers an interactive in-chat MCP App widget (SEP-1865) with custom HTML and ui:// resource URI.
run_stdio(mcp_or_app) Coroutine Executes the MCP server over standard I/O (stdin/stdout) for desktop AI assistants like Claude Desktop and Cursor.
get_current_request() Function Returns the active synthesized Starlette/FastAPI Request object during tool execution.
get_current_scope() Function Returns the active ASGI scope dictionary during tool execution.
RouteReflector(tag="mcp") Class Scans FastAPI routes, extracting docstrings, Pydantic body schemas, query parameters, and path variables into MCP tools.
KeywordTagRouter() Class Zero-dependency keyword, tag, and tokenized ranker used for progressive dynamic tool discovery.
format_validation_error() Function Formats Pydantic or FastAPI validation exceptions into concise, human-readable strings for LLM self-correction.
07 / Verification

Testing Across the ASGI Protocol Seam

Verify your MCP server end-to-end using standard pytest and httpx.AsyncClient with ASGITransport. Zero network sockets required.

tests/test_mcp_integration.py
import pytest
from httpx import AsyncClient, ASGITransport
from fastapi import FastAPI
from fast_mcp import FastMCP

@pytest.mark.asyncio
async def test_mcp_endpoints_and_tools():
    app = FastAPI()
    mcp = FastMCP(app=app, name="test-store")

    @app.get("/items/{item_id}", tags=["mcp"])
    def get_item(item_id: int):
        return {"id": item_id, "name": "Widget"}

    mcp.mount()

    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        # 1. Verify docs inspector
        res = await client.get("/mcp/docs")
        assert res.status_code == 200

        # 2. Verify tool reflection
        tools_res = await client.get("/mcp/docs/tools")
        assert tools_res.status_code == 200
        tools = tools_res.json()["tools"]
        assert any(t["name"] == "get_item" for t in tools)

        # 3. Test execution endpoint
        call_res = await client.post("/mcp/docs/call", json={"name": "get_item", "arguments": {"item_id": 42}})
        assert call_res.status_code == 200
        assert call_res.json()["result"]["id"] == 42
Copied to clipboard!