Merge pull request #184 from jlowin/docs

Improve integration documentation
This commit is contained in:
Jeremiah Lowin 2025-04-16 10:26:48 -04:00 committed by GitHub
commit 08c40e7d67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 221 additions and 233 deletions

View file

@ -1,114 +1,120 @@
---
title: FastAPI Integration
sidebarTitle: FastAPI
description: Automatically create FastMCP servers directly from FastAPI applications.
description: Generate MCP servers from FastAPI apps
icon: square-bolt
---
If you build your APIs using the popular [FastAPI](https://fastapi.tiangolo.com/) framework, FastMCP offers a seamless way to expose your FastAPI application as an MCP server. This leverages the OpenAPI integration internally but simplifies the setup significantly.
## The Goal: FastAPI App -> MCP Server
FastMCP can automatically convert FastAPI applications into MCP servers.
FastAPI automatically generates an OpenAPI specification for your application. FastMCP uses this built-in capability to create an MCP server that mirrors your API routes.
<Tip>
FastMCP does *not* include FastAPI as a dependency; you must install it separately to run these examples.
</Tip>
- FastAPI path operations (`@app.get`, `@app.post`, etc.) become MCP tools, resources, or templates.
- Pydantic models used in FastAPI for request/response validation are used to generate MCP schemas.
- Communication happens directly in memory, making it very efficient.
## Creating from FastAPI App
```python {2, 22, 25}
from fastapi import FastAPI
from fastmcp import FastMCP
Use the `FastMCP.from_fastapi()` class method. You only need your FastAPI `app` instance.
# A FastAPI app
app = FastAPI()
@app.get("/items")
def list_items():
return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {"id": item_id, "name": f"Item {item_id}"}
@app.post("/items")
def create_item(name: str):
return {"id": 3, "name": name}
# Create an MCP server from your FastAPI app
mcp = FastMCP.from_fastapi(app=app)
if __name__ == "__main__":
mcp.run() # Start the MCP server
```
## Route Mapping
By default, FastMCP will map FastAPI routes to MCP components according to the following rules:
| FastAPI Route Type | FastAPI Example | MCP Component | Notes |
|--------------------|--------------|---------|-------|
| GET without path params | `@app.get("/stats")` | Resource | Simple resources for fetching data |
| GET with path params | `@app.get("/users/{id}")` | Resource Template | Path parameters become template parameters |
| POST, PUT, DELETE, etc. | `@app.post("/users")` | Tool | Operations that modify data |
For more details on route mapping or custom mapping rules, see the [OpenAPI integration documentation](/patterns/openapi#route-mapping); FastMCP uses the same mapping rules for both FastAPI and OpenAPI integrations.
## Complete Example
Here's a more detailed example with a data model:
```python
import asyncio
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from fastmcp import FastMCP, Client # Import FastMCP and Client
# 1. Define your FastAPI application
api_app = FastAPI(title="MyFastAPIApp")
from fastmcp import FastMCP, Client
# Define your Pydantic model
class Item(BaseModel):
name: str
price: float
is_offer: bool | None = None
@api_app.get("/")
def read_root():
return {"Hello": "World"}
# Create your FastAPI app
app = FastAPI()
items = {} # In-memory database
@api_app.get("/items/{item_id}") # -> Resource Template
def read_item(item_id: int, q: str | None = None):
# This will become resource://openapi/read_item_items__item_id__get/{item_id}
return {"item_id": item_id, "q": q, "description": f"Details for item {item_id}"}
@app.get("/items")
def list_items():
"""List all items"""
return list(items.values())
@api_app.post("/items/") # -> Tool
@app.get("/items/{item_id}")
def get_item(item_id: int):
"""Get item by ID"""
if item_id not in items:
raise HTTPException(404, "Item not found")
return items[item_id]
@app.post("/items")
def create_item(item: Item):
# This will become the 'create_item_items__post' tool
print(f"Creating item: {item.name}")
return {"item_name": item.name, "status": "created"}
"""Create a new item"""
item_id = len(items) + 1
items[item_id] = {"id": item_id, **item.model_dump()}
return items[item_id]
# 2. Create the FastMCP server directly from the FastAPI app
# This is an async class method
async def create_mcp_server_from_fastapi():
mcp_server = await FastMCP.from_fastapi(
app=api_app,
name="FastAPI_MCP_Bridge" # Optional name for the MCP server
)
return mcp_server
# 3. (Example) Run the MCP server and test with an in-memory client
async def run_and_test():
server = await create_mcp_server_from_fastapi()
print(f"Created MCP server '{server.name}' from FastAPI app '{api_app.title}'")
# List discovered components
tools = await server.list_tools()
templates = await server.list_resource_templates()
print("Discovered Tools:", [t.name for t in tools])
print("Discovered Templates:", [t.uriTemplate for t in templates])
# Test using an in-memory client
client = Client(server) # Uses FastMCPTransport
async with client:
# Call the tool derived from POST /items/
create_result = await client.call_tool(
"create_item_items__post",
{"name": "MCP Special", "price": 99.99} # Pydantic model fields become args
)
print("Create Item Tool Result:", create_result[0].text) # JSON string
# Read the resource derived from GET /items/{item_id}
read_result = await client.read_resource(
"resource://openapi/read_item_items__item_id__get/42" # Match template URI
)
print("Read Item Resource Result:", read_result[0].text) # JSON string
# In a real scenario, you might run the MCP server via stdio or sse
# print("Running MCP server via stdio...")
# server.run()
# Test your MCP server with a client
async def test():
# Create MCP server from FastAPI app
mcp = await FastMCP.from_fastapi(app=app)
# List the components that were created
tools = await mcp.list_tools()
resources = await mcp.list_resources()
templates = await mcp.list_resource_templates()
print(f"Generated {len(tools)} tools")
print(f"Generated {len(resources)} resources")
print(f"Generated {len(templates)} templates")
# In a real scenario, you would run the server:
# mcp.run()
if __name__ == "__main__":
# Requires fastapi, uvicorn, httpx:
# uv pip install "fastapi[all]" httpx
try:
asyncio.run(run_and_test())
except ImportError as e:
print(f"Error: {e}. Please install required packages: uv pip install \"fastapi[all]\" httpx")
# Example Output might include:
# Created MCP server 'FastAPI_MCP_Bridge' from FastAPI app 'MyFastAPIApp'
# Discovered Tools: ['read_root___get', 'create_item_items__post']
# Discovered Templates: ['resource://openapi/read_item_items__item_id__get/{item_id}']
# Create Item Tool Result: {"item_name": "MCP Special", "status": "created"}
# Read Item Resource Result: {"item_id": 42, "q": null, "description": "Details for item 42"}
asyncio.run(test())
```
### How it Works Internally
## Benefits
1. **OpenAPI Generation**: `from_fastapi` asks the FastAPI `app` for its OpenAPI schema dictionary (`app.openapi()`).
2. **In-Memory Client**: It creates an `httpx.AsyncClient` configured with an `ASGITransport`. This special transport allows `httpx` to call the FastAPI application directly in memory without needing a running web server process.
3. **OpenAPI Integration**: It calls `FastMCP.from_openapi()`, passing the generated schema and the in-memory `httpx` client.
4. **MCP Server Creation**: The standard OpenAPI integration logic then proceeds to parse the schema and create the `Tool`, `Resource`, and `ResourceTemplate` components that wrap calls to the in-memory FastAPI app.
This provides a highly efficient way to expose your FastAPI logic through the MCP protocol, leveraging FastAPI's routing, dependency injection, and validation features.
- **Leverage existing FastAPI apps** - No need to rewrite your API logic
- **Schema reuse** - FastAPI's Pydantic models and validation are inherited
- **Full feature support** - Works with FastAPI's authentication, dependencies, etc.
- **ASGI transport** - Direct communication without additional HTTP overhead

View file

@ -1,174 +1,153 @@
---
title: OpenAPI Integration
sidebarTitle: OpenAPI
description: Automatically create FastMCP servers from existing OpenAPI specifications.
description: Generate MCP servers from OpenAPI specs
icon: code-branch
---
If you have existing REST APIs documented with the OpenAPI Specification (OAS), FastMCP can automatically generate MCP tools, resources, and resource templates directly from that specification. This provides a quick way to make your existing HTTP APIs accessible to MCP clients and LLMs.
FastMCP can automatically generate an MCP server from an OpenAPI specification. Users only need to provide an OpenAPI specification (3.0 or 3.1) and an API client.
FastMCP supports both OpenAPI 3.0 and 3.1 specifications for maximum compatibility with existing API definitions.
```python
import httpx
from fastmcp import FastMCP
## The Goal: API -> MCP Server
# Create a client for your API
api_client = httpx.AsyncClient(base_url="https://api.example.com")
The core idea is to map OpenAPI paths and operations (like `GET /users/{id}` or `POST /orders`) to their corresponding MCP components:
# Load your OpenAPI spec
spec = {...}
- `GET` requests often map to MCP **Resources** (for fetching single items) or **Resource Templates** (if the path has parameters).
- `POST`, `PUT`, `PATCH`, `DELETE` requests typically map to MCP **Tools** (for actions that create or modify data).
# Create an MCP server from your OpenAPI spec
mcp = FastMCP.from_openapi(openapi_spec=spec, client=api_client)
FastMCP automates this mapping process.
if __name__ == "__main__":
mcp.run()
```
## Creating from OpenAPI Spec
## Route Mapping
Use the `FastMCP.from_openapi()` class method. You need:
By default, OpenAPI routes are mapped to MCP components based on these rules:
1. The OpenAPI specification as a Python dictionary.
2. An `httpx.AsyncClient` configured to make requests to the actual API backend.
| OpenAPI Route | Example |MCP Component | Notes |
|- | - | - | - |
| `GET` without path params | `GET /stats` | Resource | Simple resources for fetching data |
| `GET` with path params | `GET /users/{id}` | Resource Template | Path parameters become template parameters |
| `POST`, `PUT`, `PATCH`, `DELETE`, etc. | `POST /users` | Tool | Operations that modify data |
<CodeGroup>
```python server.py
Internally, FastMCP uses a priority-ordered set of `RouteMap` objects to determine the component type. Route maps indicate that a specific HTTP method (or methods) and path pattern should be treated as a specific component type. This is the default set of route maps:
```python
# Simplified version of the actual mapping rules
DEFAULT_ROUTE_MAPPINGS = [
# GET with path parameters -> ResourceTemplate
RouteMap(methods=["GET"], pattern=r".*\{.*\}.*",
route_type=RouteType.RESOURCE_TEMPLATE),
# GET without path parameters -> Resource
RouteMap(methods=["GET"], pattern=r".*",
route_type=RouteType.RESOURCE),
# All other methods -> Tool
RouteMap(methods=["POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
pattern=r".*", route_type=RouteType.TOOL),
]
```
### Custom Route Maps
Users can add custom route maps to override the default mapping behavior. User-supplied route maps are always applied first, before the default route maps.
```python
from fastmcp.server.openapi import RouteMap, RouteType
# Custom mapping rules
custom_maps = [
# Force all analytics endpoints to be Tools
RouteMap(methods=["GET"],
pattern=r"^/analytics/.*",
route_type=RouteType.TOOL)
]
# Apply custom mappings
mcp = await FastMCP.from_openapi(
openapi_spec=spec,
client=api_client,
route_maps=custom_maps
)
```
## How It Works
1. FastMCP parses your OpenAPI spec to extract routes and schemas
2. It applies mapping rules to categorize each route
3. When an MCP client calls a tool or accesses a resource:
- FastMCP constructs an HTTP request based on the OpenAPI definition
- It sends the request through the provided httpx client
- It translates the HTTP response to the appropriate MCP format
## Complete Example
```python
import asyncio
import httpx
from fastmcp import FastMCP
# load the OpenAPI specification from the openapi_spec.py file
petstore_spec = PETSTORE_SPEC
# Client to communicate with the actual Pet Store API backend
# The base_url should match the server URL in the OpenAPI spec
http_client = httpx.AsyncClient(base_url="http://petstore.example.com/api")
# Create the FastMCP server from the spec
# This is an async class method
async def create_openapi_server():
mcp_server = await FastMCP.from_openapi(
openapi_spec=petstore_spec,
client=http_client,
name="PetStoreMCP" # Optional name for the MCP server
)
return mcp_server
async def run_server():
server = await create_openapi_server()
print(f"Starting OpenAPI-based server '{server.name}'...")
# List discovered components
tools = await server.list_tools()
resources = await server.list_resources()
templates = await server.list_resource_templates()
print("Discovered Tools:", [t.name for t in tools])
print("Discovered Resources:", [r.uri for r in resources]) # Should be empty if no parameterless GETs
print("Discovered Templates:", [t.uriTemplate for t in templates])
# Run the server (e.g., via stdio)
# server.run()
if __name__ == "__main__":
# Example: Create the server and print discovered components
# Requires httpx: uv pip install httpx
asyncio.run(run_server())
# Expected Output might include:
# Discovered Tools: ['listPets', 'createPet']
# Discovered Resources: []
# Discovered Templates: ['resource://openapi/showPetById/{petId}']
```
```python openapi_spec.py
# Example OpenAPI Specification (simplified Pet Store)
PETSTORE_SPEC = {
"openapi": "3.1.0",
"info": {"title": "Simple Pet Store", "version": "1.0.0"},
"servers": [{"url": "http://petstore.example.com/api"}], # Base URL for API calls
# Sample OpenAPI spec for a Pet Store API
petstore_spec = {
"openapi": "3.0.0",
"paths": {
"/pets": {
"get": {
"summary": "List all pets",
"operationId": "listPets",
"tags": ["pets"],
"parameters": [{ # Query parameter -> Tool argument
"name": "limit", "in": "query", "schema": {"type": "integer"}
}],
"responses": {"200": {"description": "A list of pets."}},
"summary": "List all pets"
},
"post": { # POST -> Tool
"summary": "Create a pet",
"post": {
"operationId": "createPet",
"tags": ["pets"],
"requestBody": { # Request body -> Tool arguments
"required": True,
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PetInput"}}}
},
"responses": {"201": {"description": "Pet created."}},
},
"summary": "Create a new pet"
}
},
"/pets/{petId}": { # Path parameter -> Resource Template
"get": { # GET with path param -> Resource Template / FunctionResource
"summary": "Info for a specific pet",
"operationId": "showPetById",
"tags": ["pets"],
"parameters": [{ # Path parameter -> Template function argument
"name": "petId", "in": "path", "required": True, "schema": {"type": "string"}
}],
"responses": {"200": {"description": "Information about the pet."}},
},
},
},
"components": {
"schemas": {
"PetInput": {"type": "object", "properties": {"name": {"type": "string"}, "tag": {"type": "string"}}},
"/pets/{petId}": {
"get": {
"operationId": "getPet",
"summary": "Get a pet by ID",
"parameters": [
{
"name": "petId",
"in": "path",
"required": True,
"schema": {"type": "string"}
}
]
}
}
}
}
```
</CodeGroup>
### How it Works Internally
1. **Parsing**: `from_openapi` parses the spec using utilities that leverage `openapi-pydantic`. It extracts paths, operations, parameters, request bodies, and responses.
2. **Mapping**: It applies mapping rules (see below) to decide whether each OpenAPI operation (`GET /pets`, `POST /pets`, `GET /pets/{petId}`) becomes an MCP `Tool`, `Resource`, or `ResourceTemplate`.
3. **Component Creation**: It creates specialized internal components (`OpenAPITool`, `OpenAPIResource`, `OpenAPIResourceTemplate`).
4. **HTTP Execution**: When an MCP client calls a tool or reads a resource from this server:
* The corresponding OpenAPI component constructs an HTTP request based on the OpenAPI definition and the arguments provided by the MCP client.
* It uses the provided `httpx.AsyncClient` to send the request to the backend API.
* It processes the HTTP response and returns it to the MCP client in the appropriate MCP format.
5. **Schema Generation**: The schemas for MCP tools are derived by combining OpenAPI parameters (path, query, header) and request body schemas. Resource template function arguments are derived from path parameters.
6. **Descriptions**: Tool/Resource descriptions are enhanced with information from OpenAPI responses to give the LLM more context about potential outcomes.
### Default Mapping Rules
FastMCP uses the following default rules to map OpenAPI operations:
- `GET` operation with path parameters (e.g., `/users/{id}`) -> **`ResourceTemplate`**
- `GET` operation without path parameters (e.g., `/users`) -> **`Resource`**
- `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD` -> **`Tool`**
### Customize Route Mapping
You can customize the mapping rules by providing a list of `RouteMap` objects directly to `FastMCP.from_openapi()` using the `route_maps` parameter:
```python
from fastmcp.server.openapi import RouteMap, RouteType
from fastmcp import FastMCP
# Custom mapping: Treat GET /admin/stats as a Tool, not a Resource
custom_maps = [
RouteMap(methods=["GET"], pattern=r"^/admin/stats$", route_type=RouteType.TOOL)
]
async def create_server_with_custom_mapping():
mcp_server = await FastMCP.from_openapi(
async def main():
# Client for the Pet Store API
client = httpx.AsyncClient(base_url="https://petstore.example.com/api")
# Create the MCP server
mcp = await FastMCP.from_openapi(
openapi_spec=petstore_spec,
client=http_client,
name="PetStoreMCP",
route_maps=custom_maps # Pass custom mapping rules
client=client,
name="PetStore"
)
return mcp_server
# List what components were created
tools = await mcp.list_tools()
resources = await mcp.list_resources()
templates = await mcp.list_resource_templates()
print(f"Tools: {len(tools)}") # Should include createPet
print(f"Resources: {len(resources)}") # Should include listPets
print(f"Templates: {len(templates)}") # Should include getPet
# Start the MCP server
mcp.run()
if __name__ == "__main__":
asyncio.run(main())
```
Each `RouteMap` maps one or more HTTP methods and a regular expression pattern for the route path to an MCP `RouteType`. Route maps are processed in order, and the first match wins.
All parameters passed to `FastMCP.from_openapi()` will be forwarded to the underlying `FastMCPOpenAPI` constructor, so you can customize any aspect of the OpenAPI integration directly through this method call.

View file

@ -5,13 +5,13 @@ description = "The fast, Pythonic way to build MCP servers."
authors = [{ name = "Jeremiah Lowin" }]
dependencies = [
"dotenv>=0.9.9",
"exceptiongroup>=1.2.2",
"httpx>=0.28.1",
"mcp>=1.6.0,<2.0.0",
"openapi-pydantic>=0.5.1",
"rich>=13.9.4",
"typer>=0.15.2",
"websockets>=15.0.1",
"fastapi>=0.115.12",
"openapi-pydantic>=0.5.1",
"exceptiongroup>=1.2.2",
]
requires-python = ">=3.10"
readme = "README.md"
@ -38,6 +38,11 @@ classifiers = [
[dependency-groups]
dev = [
"copychat>=0.5.2",
"dirty-equals>=0.9.0",
"fastapi>=0.115.12",
"ipython>=8.12.3",
"pdbpp>=0.10.3",
"pre-commit",
"pyright>=1.1.389",
"pytest>=8.3.3",
@ -45,10 +50,6 @@ dev = [
"pytest-flakefinder",
"pytest-xdist>=3.6.1",
"ruff",
"copychat>=0.5.2",
"ipython>=8.12.3",
"pdbpp>=0.10.3",
"dirty-equals>=0.9.0",
]
[project.scripts]

View file

@ -13,7 +13,6 @@ import anyio
import httpx
import pydantic_core
import uvicorn
from fastapi import FastAPI
from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.server.lowlevel.server import LifespanResultT
from mcp.server.lowlevel.server import Server as MCPServer
@ -846,11 +845,12 @@ class FastMCP(Generic[LifespanResultT]):
@classmethod
def from_fastapi(
cls, app: FastAPI, name: str | None = None, **settings: Any
cls, app: "Any", name: str | None = None, **settings: Any
) -> "FastMCPOpenAPI":
"""
Create a FastMCP server from a FastAPI application.
"""
from .openapi import FastMCPOpenAPI
client = httpx.AsyncClient(

8
uv.lock generated
View file

@ -254,12 +254,12 @@ wheels = [
[[package]]
name = "fastmcp"
version = "2.1.3.dev31+8d922d9"
version = "2.1.3.dev42+be2ccc6"
source = { editable = "." }
dependencies = [
{ name = "dotenv" },
{ name = "exceptiongroup" },
{ name = "fastapi" },
{ name = "httpx" },
{ name = "mcp" },
{ name = "openapi-pydantic" },
{ name = "rich" },
@ -271,6 +271,7 @@ dependencies = [
dev = [
{ name = "copychat" },
{ name = "dirty-equals" },
{ name = "fastapi" },
{ name = "ipython" },
{ name = "pdbpp" },
{ name = "pre-commit" },
@ -286,7 +287,7 @@ dev = [
requires-dist = [
{ name = "dotenv", specifier = ">=0.9.9" },
{ name = "exceptiongroup", specifier = ">=1.2.2" },
{ name = "fastapi", specifier = ">=0.115.12" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "mcp", specifier = ">=1.6.0,<2.0.0" },
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
{ name = "rich", specifier = ">=13.9.4" },
@ -298,6 +299,7 @@ requires-dist = [
dev = [
{ name = "copychat", specifier = ">=0.5.2" },
{ name = "dirty-equals", specifier = ">=0.9.0" },
{ name = "fastapi", specifier = ">=0.115.12" },
{ name = "ipython", specifier = ">=8.12.3" },
{ name = "pdbpp", specifier = ">=0.10.3" },
{ name = "pre-commit" },