Model Context Protocol (MCP) is an emerging standard that allows AI agents, IDE copilots, and LLM-based applications to call external tools in a structured way. Instead of sending arbitrary text prompts, an MCP client can discover available tools, inspect their schemas, and invoke them with validated inputs and outputs.
FastAPI-MCP is a library that bridges your FastAPI application with MCP. It automatically maps your existing routes into MCP tools, keeping your Pydantic validation, docs, and response models intact. This unlocks new ways for agents, copilots, and automation systems to interact with your API.
Why MCP Matters
- Interoperability: Any MCP-compatible client (Claude Desktop, Windsurf, IDE extensions) can discover and call your tools without custom integration.
- Safety: Tools are schema-driven, with validated inputs/outputs. Clients know what to expect.
- Productivity: Turn your internal API into usable agent tools instantly. Great for workflows, bots, and copilots.
- Maintainability: No duplicate logic. Your FastAPI models become the single source of truth.
Use cases:
- Exposing CRM or ERP endpoints as tools for sales ops agents.
- Wrapping DevOps commands (deploy, scale, restart) for internal copilots.
- Letting IDE copilots run queries against your existing internal services.
- Turning prototypes into agent-ready backends with minimal effort.
Installation
uv add fastapi-mcp
# or
pip install fastapi-mcpHow Routes Become Tools
Each FastAPI endpoint becomes an MCP tool:
- Tool name → defined by
operation_id. Always set this for stability. - Inputs → derived from query params or request body models.
- Outputs → defined by the
response_model. - Hidden endpoints → only the routes on the wrapped app are exposed.
Example:
class TicketIn(BaseModel):
title: str
priority: int = 3
class TicketOut(BaseModel):
id: str
title: str
priority: int
@app.post("/tickets", operation_id="ticket_create", response_model=TicketOut)
def create_ticket(payload: TicketIn) -> TicketOut:
return TicketOut(id="t_123", **payload.dict())MCP will expose a tool ticket_create that takes a validated payload and returns a TicketOut object.
Minimal Setup
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP
app = FastAPI()
mcp = FastApiMCP(app)
mcp.mount_http()Run with Uvicorn. /mcp now exposes your tool list.
Connecting Clients
Direct HTTP:
{
"mcpServers": { "fastapi-mcp": { "url": "http://localhost:8000/mcp" } }
}SSE (legacy):
{
"mcpServers": { "fastapi-mcp": { "url": "http://localhost:8000/sse" } }
}Using mcp-remote: required for OAuth or older clients.
{
"mcpServers": {
"fastapi-mcp": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8000/mcp", "8080"]
}
}
}Authentication Options
Token Passthrough
Require a bearer token for access:
from fastapi import Depends
from fastapi_mcp import FastApiMCP, AuthConfig
def verify_auth():
return True
mcp = FastApiMCP(app, auth_config=AuthConfig(dependencies=[Depends(verify_auth)]))
mcp.mount_http()OAuth 2.0
Use when agents need full identity integration:
mcp = FastApiMCP(
app,
auth_config=AuthConfig(
issuer="https://auth.example.com/",
authorize_url="https://auth.example.com/authorize",
oauth_metadata_url="https://auth.example.com/.well-known/oauth-authorization-server",
audience="my-audience",
client_id="my-client-id",
client_secret="my-client-secret",
setup_proxies=True,
),
)
mcp.mount_http()Callback: http://127.0.0.1:8080/oauth/callback
Transport and Reliability
- ASGI (default): in-process, fastest.
- Custom HTTP client: route to remote services and adjust timeouts.
import httpx
client = httpx.AsyncClient(base_url="https://api.example.com", timeout=30.0)
mcp = FastApiMCP(app, http_client=client)- Increase default timeout (5s) for longer calls.
- Ensure writes are idempotent (e.g., via
Idempotency-Key). - For large responses, return URLs to storage instead of embedding.
Deployment Patterns
- Same app: simple for dev, exposes
/mcpalongside API. - Split apps: better for prod, separate auth and logs.
api_app = FastAPI()
# endpoints
mcp_app = FastAPI()
mcp = FastApiMCP(api_app)
mcp.mount_http(mcp_app)- Deploy behind nginx/Cloudflare with TLS.
- Use structured logging for tool traces.
Development Workflow
- Add new routes → call
mcp.setup_server()to refresh tool registry. - Use
npx @modelcontextprotocol/inspectorto inspect and test tools.
Best Practices
- Keep tool count small and focused.
- Favor read-only tools first.
- Use explicit
operation_ids for stable names. - Document params with descriptions.
- Validate at the edge, fail fast with 4xx.
- Add new endpoints instead of changing old ones.
- Log and trace every tool call.
- Rate-limit writes.
Practical Example: CRM Tools
class Contact(BaseModel):
id: str
email: str
name: str
DB = [
Contact(id="1", email="ada@acme.com", name="Ada"),
Contact(id="2", email="ben@acme.com", name="Ben"),
]
@app.get("/contacts", operation_id="crm_list_contacts")
def crm_list_contacts() -> list[Contact]:
return DB
@app.get("/contacts/{contact_id}", operation_id="crm_get_contact")
def crm_get_contact(contact_id: str) -> Contact | None:
return next((c for c in DB if c.id == contact_id), None)
mcp = FastApiMCP(app)
mcp.mount_http()Agent clients can now query CRM data safely with validated inputs/outputs.
Debugging and Gotchas
- Tools not showing? Call
mcp.setup_server()or ensure wrapper created after routes. - Timeouts on long calls? Adjust with
httpx.AsyncClient. - Client doesn’t support HTTP transport? Use
mcp-remote. - OAuth issues? Fix callback port and URLs.