Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
51ea945
1
Parent(s): a174361
Improve integration documentation
Browse files- docs/patterns/fastapi.mdx +92 -86
- docs/patterns/openapi.mdx +114 -135
- pyproject.toml +8 -7
- src/fastmcp/server/server.py +2 -2
- uv.lock +5 -3
docs/patterns/fastapi.mdx
CHANGED
|
@@ -1,114 +1,120 @@
|
|
| 1 |
---
|
| 2 |
title: FastAPI Integration
|
| 3 |
sidebarTitle: FastAPI
|
| 4 |
-
description:
|
| 5 |
icon: square-bolt
|
| 6 |
---
|
| 7 |
|
| 8 |
-
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.
|
| 9 |
|
| 10 |
-
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
- FastAPI path operations (`@app.get`, `@app.post`, etc.) become MCP tools, resources, or templates.
|
| 15 |
-
- Pydantic models used in FastAPI for request/response validation are used to generate MCP schemas.
|
| 16 |
-
- Communication happens directly in memory, making it very efficient.
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
|
| 22 |
```python
|
| 23 |
import asyncio
|
| 24 |
-
from fastapi import FastAPI
|
| 25 |
from pydantic import BaseModel
|
| 26 |
-
from fastmcp import FastMCP, Client
|
| 27 |
-
|
| 28 |
-
# 1. Define your FastAPI application
|
| 29 |
-
api_app = FastAPI(title="MyFastAPIApp")
|
| 30 |
|
|
|
|
| 31 |
class Item(BaseModel):
|
| 32 |
name: str
|
| 33 |
price: float
|
| 34 |
-
is_offer: bool | None = None
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
|
| 40 |
-
@
|
| 41 |
-
def
|
| 42 |
-
|
| 43 |
-
return
|
| 44 |
|
| 45 |
-
@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
def create_item(item: Item):
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
#
|
| 53 |
-
async def
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
print(f"
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
print("Discovered Tools:", [t.name for t in tools])
|
| 69 |
-
print("Discovered Templates:", [t.uriTemplate for t in templates])
|
| 70 |
-
|
| 71 |
-
# Test using an in-memory client
|
| 72 |
-
client = Client(server) # Uses FastMCPTransport
|
| 73 |
-
async with client:
|
| 74 |
-
# Call the tool derived from POST /items/
|
| 75 |
-
create_result = await client.call_tool(
|
| 76 |
-
"create_item_items__post",
|
| 77 |
-
{"name": "MCP Special", "price": 99.99} # Pydantic model fields become args
|
| 78 |
-
)
|
| 79 |
-
print("Create Item Tool Result:", create_result[0].text) # JSON string
|
| 80 |
-
|
| 81 |
-
# Read the resource derived from GET /items/{item_id}
|
| 82 |
-
read_result = await client.read_resource(
|
| 83 |
-
"resource://openapi/read_item_items__item_id__get/42" # Match template URI
|
| 84 |
-
)
|
| 85 |
-
print("Read Item Resource Result:", read_result[0].text) # JSON string
|
| 86 |
-
|
| 87 |
-
# In a real scenario, you might run the MCP server via stdio or sse
|
| 88 |
-
# print("Running MCP server via stdio...")
|
| 89 |
-
# server.run()
|
| 90 |
|
| 91 |
if __name__ == "__main__":
|
| 92 |
-
|
| 93 |
-
# uv pip install "fastapi[all]" httpx
|
| 94 |
-
try:
|
| 95 |
-
asyncio.run(run_and_test())
|
| 96 |
-
except ImportError as e:
|
| 97 |
-
print(f"Error: {e}. Please install required packages: uv pip install \"fastapi[all]\" httpx")
|
| 98 |
-
|
| 99 |
-
# Example Output might include:
|
| 100 |
-
# Created MCP server 'FastAPI_MCP_Bridge' from FastAPI app 'MyFastAPIApp'
|
| 101 |
-
# Discovered Tools: ['read_root___get', 'create_item_items__post']
|
| 102 |
-
# Discovered Templates: ['resource://openapi/read_item_items__item_id__get/{item_id}']
|
| 103 |
-
# Create Item Tool Result: {"item_name": "MCP Special", "status": "created"}
|
| 104 |
-
# Read Item Resource Result: {"item_id": 42, "q": null, "description": "Details for item 42"}
|
| 105 |
```
|
| 106 |
|
| 107 |
-
##
|
| 108 |
-
|
| 109 |
-
1. **OpenAPI Generation**: `from_fastapi` asks the FastAPI `app` for its OpenAPI schema dictionary (`app.openapi()`).
|
| 110 |
-
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.
|
| 111 |
-
3. **OpenAPI Integration**: It calls `FastMCP.from_openapi()`, passing the generated schema and the in-memory `httpx` client.
|
| 112 |
-
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.
|
| 113 |
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: FastAPI Integration
|
| 3 |
sidebarTitle: FastAPI
|
| 4 |
+
description: Generate MCP servers from FastAPI apps
|
| 5 |
icon: square-bolt
|
| 6 |
---
|
| 7 |
|
|
|
|
| 8 |
|
| 9 |
+
FastMCP can automatically convert FastAPI applications into MCP servers.
|
| 10 |
|
| 11 |
+
<Tip>
|
| 12 |
+
FastMCP does *not* include FastAPI as a dependency; you must install it separately to run these examples.
|
| 13 |
+
</Tip>
|
| 14 |
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
```python {2, 22, 25}
|
| 17 |
+
from fastapi import FastAPI
|
| 18 |
+
from fastmcp import FastMCP
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# A FastAPI app
|
| 22 |
+
app = FastAPI()
|
| 23 |
+
|
| 24 |
+
@app.get("/items")
|
| 25 |
+
def list_items():
|
| 26 |
+
return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]
|
| 27 |
+
|
| 28 |
+
@app.get("/items/{item_id}")
|
| 29 |
+
def get_item(item_id: int):
|
| 30 |
+
return {"id": item_id, "name": f"Item {item_id}"}
|
| 31 |
+
|
| 32 |
+
@app.post("/items")
|
| 33 |
+
def create_item(name: str):
|
| 34 |
+
return {"id": 3, "name": name}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# Create an MCP server from your FastAPI app
|
| 38 |
+
mcp = FastMCP.from_fastapi(app=app)
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
mcp.run() # Start the MCP server
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
## Route Mapping
|
| 45 |
+
|
| 46 |
+
By default, FastMCP will map FastAPI routes to MCP components according to the following rules:
|
| 47 |
+
|
| 48 |
+
| FastAPI Route Type | FastAPI Example | MCP Component | Notes |
|
| 49 |
+
|--------------------|--------------|---------|-------|
|
| 50 |
+
| GET without path params | `@app.get("/stats")` | Resource | Simple resources for fetching data |
|
| 51 |
+
| GET with path params | `@app.get("/users/{id}")` | Resource Template | Path parameters become template parameters |
|
| 52 |
+
| POST, PUT, DELETE, etc. | `@app.post("/users")` | Tool | Operations that modify data |
|
| 53 |
+
|
| 54 |
+
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.
|
| 55 |
|
| 56 |
+
## Complete Example
|
| 57 |
+
|
| 58 |
+
Here's a more detailed example with a data model:
|
| 59 |
|
| 60 |
```python
|
| 61 |
import asyncio
|
| 62 |
+
from fastapi import FastAPI, HTTPException
|
| 63 |
from pydantic import BaseModel
|
| 64 |
+
from fastmcp import FastMCP, Client
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
# Define your Pydantic model
|
| 67 |
class Item(BaseModel):
|
| 68 |
name: str
|
| 69 |
price: float
|
|
|
|
| 70 |
|
| 71 |
+
# Create your FastAPI app
|
| 72 |
+
app = FastAPI()
|
| 73 |
+
items = {} # In-memory database
|
| 74 |
|
| 75 |
+
@app.get("/items")
|
| 76 |
+
def list_items():
|
| 77 |
+
"""List all items"""
|
| 78 |
+
return list(items.values())
|
| 79 |
|
| 80 |
+
@app.get("/items/{item_id}")
|
| 81 |
+
def get_item(item_id: int):
|
| 82 |
+
"""Get item by ID"""
|
| 83 |
+
if item_id not in items:
|
| 84 |
+
raise HTTPException(404, "Item not found")
|
| 85 |
+
return items[item_id]
|
| 86 |
+
|
| 87 |
+
@app.post("/items")
|
| 88 |
def create_item(item: Item):
|
| 89 |
+
"""Create a new item"""
|
| 90 |
+
item_id = len(items) + 1
|
| 91 |
+
items[item_id] = {"id": item_id, **item.model_dump()}
|
| 92 |
+
return items[item_id]
|
| 93 |
+
|
| 94 |
+
# Test your MCP server with a client
|
| 95 |
+
async def test():
|
| 96 |
+
# Create MCP server from FastAPI app
|
| 97 |
+
mcp = await FastMCP.from_fastapi(app=app)
|
| 98 |
+
|
| 99 |
+
# List the components that were created
|
| 100 |
+
tools = await mcp.list_tools()
|
| 101 |
+
resources = await mcp.list_resources()
|
| 102 |
+
templates = await mcp.list_resource_templates()
|
| 103 |
+
|
| 104 |
+
print(f"Generated {len(tools)} tools")
|
| 105 |
+
print(f"Generated {len(resources)} resources")
|
| 106 |
+
print(f"Generated {len(templates)} templates")
|
| 107 |
+
|
| 108 |
+
# In a real scenario, you would run the server:
|
| 109 |
+
# mcp.run()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
if __name__ == "__main__":
|
| 112 |
+
asyncio.run(test())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
```
|
| 114 |
|
| 115 |
+
## Benefits
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
+
- **Leverage existing FastAPI apps** - No need to rewrite your API logic
|
| 118 |
+
- **Schema reuse** - FastAPI's Pydantic models and validation are inherited
|
| 119 |
+
- **Full feature support** - Works with FastAPI's authentication, dependencies, etc.
|
| 120 |
+
- **ASGI transport** - Direct communication without additional HTTP overhead
|
docs/patterns/openapi.mdx
CHANGED
|
@@ -1,174 +1,153 @@
|
|
| 1 |
---
|
| 2 |
title: OpenAPI Integration
|
| 3 |
sidebarTitle: OpenAPI
|
| 4 |
-
description:
|
| 5 |
icon: code-branch
|
| 6 |
---
|
| 7 |
|
| 8 |
-
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
#
|
|
|
|
| 13 |
|
| 14 |
-
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
##
|
| 22 |
|
| 23 |
-
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
<CodeGroup>
|
| 29 |
|
| 30 |
-
``
|
| 31 |
-
import asyncio
|
| 32 |
-
import httpx
|
| 33 |
-
from fastmcp import FastMCP
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
# The base_url should match the server URL in the OpenAPI spec
|
| 40 |
-
http_client = httpx.AsyncClient(base_url="http://petstore.example.com/api")
|
| 41 |
|
| 42 |
-
|
| 43 |
-
# This is an async class method
|
| 44 |
-
async def create_openapi_server():
|
| 45 |
-
mcp_server = await FastMCP.from_openapi(
|
| 46 |
-
openapi_spec=petstore_spec,
|
| 47 |
-
client=http_client,
|
| 48 |
-
name="PetStoreMCP" # Optional name for the MCP server
|
| 49 |
-
)
|
| 50 |
-
return mcp_server
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
print(f"Starting OpenAPI-based server '{server.name}'...")
|
| 55 |
-
|
| 56 |
-
# List discovered components
|
| 57 |
-
tools = await server.list_tools()
|
| 58 |
-
resources = await server.list_resources()
|
| 59 |
-
templates = await server.list_resource_templates()
|
| 60 |
-
print("Discovered Tools:", [t.name for t in tools])
|
| 61 |
-
print("Discovered Resources:", [r.uri for r in resources]) # Should be empty if no parameterless GETs
|
| 62 |
-
print("Discovered Templates:", [t.uriTemplate for t in templates])
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
# Discovered Tools: ['listPets', 'createPet']
|
| 74 |
-
# Discovered Resources: []
|
| 75 |
-
# Discovered Templates: ['resource://openapi/showPetById/{petId}']
|
| 76 |
```
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
"paths": {
|
| 85 |
"/pets": {
|
| 86 |
"get": {
|
| 87 |
-
"summary": "List all pets",
|
| 88 |
"operationId": "listPets",
|
| 89 |
-
"
|
| 90 |
-
"parameters": [{ # Query parameter -> Tool argument
|
| 91 |
-
"name": "limit", "in": "query", "schema": {"type": "integer"}
|
| 92 |
-
}],
|
| 93 |
-
"responses": {"200": {"description": "A list of pets."}},
|
| 94 |
},
|
| 95 |
-
"post": {
|
| 96 |
-
"summary": "Create a pet",
|
| 97 |
"operationId": "createPet",
|
| 98 |
-
"
|
| 99 |
-
|
| 100 |
-
"required": True,
|
| 101 |
-
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PetInput"}}}
|
| 102 |
-
},
|
| 103 |
-
"responses": {"201": {"description": "Pet created."}},
|
| 104 |
-
},
|
| 105 |
},
|
| 106 |
-
"/pets/{petId}": {
|
| 107 |
-
"get": {
|
| 108 |
-
"
|
| 109 |
-
"
|
| 110 |
-
"
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
"schemas": {
|
| 120 |
-
"PetInput": {"type": "object", "properties": {"name": {"type": "string"}, "tag": {"type": "string"}}},
|
| 121 |
}
|
| 122 |
}
|
| 123 |
}
|
| 124 |
-
```
|
| 125 |
-
|
| 126 |
-
</CodeGroup>
|
| 127 |
-
|
| 128 |
-
### How it Works Internally
|
| 129 |
-
|
| 130 |
-
1. **Parsing**: `from_openapi` parses the spec using utilities that leverage `openapi-pydantic`. It extracts paths, operations, parameters, request bodies, and responses.
|
| 131 |
-
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`.
|
| 132 |
-
3. **Component Creation**: It creates specialized internal components (`OpenAPITool`, `OpenAPIResource`, `OpenAPIResourceTemplate`).
|
| 133 |
-
4. **HTTP Execution**: When an MCP client calls a tool or reads a resource from this server:
|
| 134 |
-
* The corresponding OpenAPI component constructs an HTTP request based on the OpenAPI definition and the arguments provided by the MCP client.
|
| 135 |
-
* It uses the provided `httpx.AsyncClient` to send the request to the backend API.
|
| 136 |
-
* It processes the HTTP response and returns it to the MCP client in the appropriate MCP format.
|
| 137 |
-
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.
|
| 138 |
-
6. **Descriptions**: Tool/Resource descriptions are enhanced with information from OpenAPI responses to give the LLM more context about potential outcomes.
|
| 139 |
-
|
| 140 |
-
### Default Mapping Rules
|
| 141 |
-
|
| 142 |
-
FastMCP uses the following default rules to map OpenAPI operations:
|
| 143 |
-
|
| 144 |
-
- `GET` operation with path parameters (e.g., `/users/{id}`) -> **`ResourceTemplate`**
|
| 145 |
-
- `GET` operation without path parameters (e.g., `/users`) -> **`Resource`**
|
| 146 |
-
- `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD` -> **`Tool`**
|
| 147 |
-
|
| 148 |
-
### Customize Route Mapping
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
# Custom mapping: Treat GET /admin/stats as a Tool, not a Resource
|
| 157 |
-
custom_maps = [
|
| 158 |
-
RouteMap(methods=["GET"], pattern=r"^/admin/stats$", route_type=RouteType.TOOL)
|
| 159 |
-
]
|
| 160 |
-
|
| 161 |
-
async def create_server_with_custom_mapping():
|
| 162 |
-
mcp_server = await FastMCP.from_openapi(
|
| 163 |
openapi_spec=petstore_spec,
|
| 164 |
-
client=
|
| 165 |
-
name="
|
| 166 |
-
route_maps=custom_maps # Pass custom mapping rules
|
| 167 |
)
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
|
|
|
|
| 1 |
---
|
| 2 |
title: OpenAPI Integration
|
| 3 |
sidebarTitle: OpenAPI
|
| 4 |
+
description: Generate MCP servers from OpenAPI specs
|
| 5 |
icon: code-branch
|
| 6 |
---
|
| 7 |
|
| 8 |
+
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.
|
| 9 |
|
| 10 |
+
```python
|
| 11 |
+
import httpx
|
| 12 |
+
from fastmcp import FastMCP
|
| 13 |
|
| 14 |
+
# Create a client for your API
|
| 15 |
+
api_client = httpx.AsyncClient(base_url="https://api.example.com")
|
| 16 |
|
| 17 |
+
# Load your OpenAPI spec
|
| 18 |
+
spec = {...}
|
| 19 |
|
| 20 |
+
# Create an MCP server from your OpenAPI spec
|
| 21 |
+
mcp = FastMCP.from_openapi(openapi_spec=spec, client=api_client)
|
| 22 |
|
| 23 |
+
if __name__ == "__main__":
|
| 24 |
+
mcp.run()
|
| 25 |
+
```
|
| 26 |
|
| 27 |
+
## Route Mapping
|
| 28 |
|
| 29 |
+
By default, OpenAPI routes are mapped to MCP components based on these rules:
|
| 30 |
|
| 31 |
+
| OpenAPI Route | Example |MCP Component | Notes |
|
| 32 |
+
|- | - | - | - |
|
| 33 |
+
| `GET` without path params | `GET /stats` | Resource | Simple resources for fetching data |
|
| 34 |
+
| `GET` with path params | `GET /users/{id}` | Resource Template | Path parameters become template parameters |
|
| 35 |
+
| `POST`, `PUT`, `PATCH`, `DELETE`, etc. | `POST /users` | Tool | Operations that modify data |
|
| 36 |
|
|
|
|
| 37 |
|
| 38 |
+
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:
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
```python
|
| 41 |
+
# Simplified version of the actual mapping rules
|
| 42 |
+
DEFAULT_ROUTE_MAPPINGS = [
|
| 43 |
+
# GET with path parameters -> ResourceTemplate
|
| 44 |
+
RouteMap(methods=["GET"], pattern=r".*\{.*\}.*",
|
| 45 |
+
route_type=RouteType.RESOURCE_TEMPLATE),
|
| 46 |
+
|
| 47 |
+
# GET without path parameters -> Resource
|
| 48 |
+
RouteMap(methods=["GET"], pattern=r".*",
|
| 49 |
+
route_type=RouteType.RESOURCE),
|
| 50 |
+
|
| 51 |
+
# All other methods -> Tool
|
| 52 |
+
RouteMap(methods=["POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
|
| 53 |
+
pattern=r".*", route_type=RouteType.TOOL),
|
| 54 |
+
]
|
| 55 |
+
```
|
| 56 |
|
| 57 |
+
### Custom Route Maps
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
+
```python
|
| 62 |
+
from fastmcp.server.openapi import RouteMap, RouteType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
# Custom mapping rules
|
| 65 |
+
custom_maps = [
|
| 66 |
+
# Force all analytics endpoints to be Tools
|
| 67 |
+
RouteMap(methods=["GET"],
|
| 68 |
+
pattern=r"^/analytics/.*",
|
| 69 |
+
route_type=RouteType.TOOL)
|
| 70 |
+
]
|
| 71 |
|
| 72 |
+
# Apply custom mappings
|
| 73 |
+
mcp = await FastMCP.from_openapi(
|
| 74 |
+
openapi_spec=spec,
|
| 75 |
+
client=api_client,
|
| 76 |
+
route_maps=custom_maps
|
| 77 |
+
)
|
|
|
|
|
|
|
|
|
|
| 78 |
```
|
| 79 |
|
| 80 |
+
## How It Works
|
| 81 |
+
|
| 82 |
+
1. FastMCP parses your OpenAPI spec to extract routes and schemas
|
| 83 |
+
2. It applies mapping rules to categorize each route
|
| 84 |
+
3. When an MCP client calls a tool or accesses a resource:
|
| 85 |
+
- FastMCP constructs an HTTP request based on the OpenAPI definition
|
| 86 |
+
- It sends the request through the provided httpx client
|
| 87 |
+
- It translates the HTTP response to the appropriate MCP format
|
| 88 |
+
|
| 89 |
+
## Complete Example
|
| 90 |
+
|
| 91 |
+
```python
|
| 92 |
+
import asyncio
|
| 93 |
+
import httpx
|
| 94 |
+
from fastmcp import FastMCP
|
| 95 |
+
|
| 96 |
+
# Sample OpenAPI spec for a Pet Store API
|
| 97 |
+
petstore_spec = {
|
| 98 |
+
"openapi": "3.0.0",
|
| 99 |
"paths": {
|
| 100 |
"/pets": {
|
| 101 |
"get": {
|
|
|
|
| 102 |
"operationId": "listPets",
|
| 103 |
+
"summary": "List all pets"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
},
|
| 105 |
+
"post": {
|
|
|
|
| 106 |
"operationId": "createPet",
|
| 107 |
+
"summary": "Create a new pet"
|
| 108 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
},
|
| 110 |
+
"/pets/{petId}": {
|
| 111 |
+
"get": {
|
| 112 |
+
"operationId": "getPet",
|
| 113 |
+
"summary": "Get a pet by ID",
|
| 114 |
+
"parameters": [
|
| 115 |
+
{
|
| 116 |
+
"name": "petId",
|
| 117 |
+
"in": "path",
|
| 118 |
+
"required": True,
|
| 119 |
+
"schema": {"type": "string"}
|
| 120 |
+
}
|
| 121 |
+
]
|
| 122 |
+
}
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
}
|
| 125 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
async def main():
|
| 128 |
+
# Client for the Pet Store API
|
| 129 |
+
client = httpx.AsyncClient(base_url="https://petstore.example.com/api")
|
| 130 |
+
|
| 131 |
+
# Create the MCP server
|
| 132 |
+
mcp = await FastMCP.from_openapi(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
openapi_spec=petstore_spec,
|
| 134 |
+
client=client,
|
| 135 |
+
name="PetStore"
|
|
|
|
| 136 |
)
|
| 137 |
+
|
| 138 |
+
# List what components were created
|
| 139 |
+
tools = await mcp.list_tools()
|
| 140 |
+
resources = await mcp.list_resources()
|
| 141 |
+
templates = await mcp.list_resource_templates()
|
| 142 |
+
|
| 143 |
+
print(f"Tools: {len(tools)}") # Should include createPet
|
| 144 |
+
print(f"Resources: {len(resources)}") # Should include listPets
|
| 145 |
+
print(f"Templates: {len(templates)}") # Should include getPet
|
| 146 |
+
|
| 147 |
+
# Start the MCP server
|
| 148 |
+
mcp.run()
|
| 149 |
|
| 150 |
+
if __name__ == "__main__":
|
| 151 |
+
asyncio.run(main())
|
| 152 |
+
```
|
| 153 |
|
pyproject.toml
CHANGED
|
@@ -5,13 +5,13 @@ description = "The fast, Pythonic way to build MCP servers."
|
|
| 5 |
authors = [{ name = "Jeremiah Lowin" }]
|
| 6 |
dependencies = [
|
| 7 |
"dotenv>=0.9.9",
|
|
|
|
|
|
|
| 8 |
"mcp>=1.6.0,<2.0.0",
|
|
|
|
| 9 |
"rich>=13.9.4",
|
| 10 |
"typer>=0.15.2",
|
| 11 |
"websockets>=15.0.1",
|
| 12 |
-
"fastapi>=0.115.12",
|
| 13 |
-
"openapi-pydantic>=0.5.1",
|
| 14 |
-
"exceptiongroup>=1.2.2",
|
| 15 |
]
|
| 16 |
requires-python = ">=3.10"
|
| 17 |
readme = "README.md"
|
|
@@ -38,6 +38,11 @@ classifiers = [
|
|
| 38 |
|
| 39 |
[dependency-groups]
|
| 40 |
dev = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
"pre-commit",
|
| 42 |
"pyright>=1.1.389",
|
| 43 |
"pytest>=8.3.3",
|
|
@@ -45,10 +50,6 @@ dev = [
|
|
| 45 |
"pytest-flakefinder",
|
| 46 |
"pytest-xdist>=3.6.1",
|
| 47 |
"ruff",
|
| 48 |
-
"copychat>=0.5.2",
|
| 49 |
-
"ipython>=8.12.3",
|
| 50 |
-
"pdbpp>=0.10.3",
|
| 51 |
-
"dirty-equals>=0.9.0",
|
| 52 |
]
|
| 53 |
|
| 54 |
[project.scripts]
|
|
|
|
| 5 |
authors = [{ name = "Jeremiah Lowin" }]
|
| 6 |
dependencies = [
|
| 7 |
"dotenv>=0.9.9",
|
| 8 |
+
"exceptiongroup>=1.2.2",
|
| 9 |
+
"httpx>=0.28.1",
|
| 10 |
"mcp>=1.6.0,<2.0.0",
|
| 11 |
+
"openapi-pydantic>=0.5.1",
|
| 12 |
"rich>=13.9.4",
|
| 13 |
"typer>=0.15.2",
|
| 14 |
"websockets>=15.0.1",
|
|
|
|
|
|
|
|
|
|
| 15 |
]
|
| 16 |
requires-python = ">=3.10"
|
| 17 |
readme = "README.md"
|
|
|
|
| 38 |
|
| 39 |
[dependency-groups]
|
| 40 |
dev = [
|
| 41 |
+
"copychat>=0.5.2",
|
| 42 |
+
"dirty-equals>=0.9.0",
|
| 43 |
+
"fastapi>=0.115.12",
|
| 44 |
+
"ipython>=8.12.3",
|
| 45 |
+
"pdbpp>=0.10.3",
|
| 46 |
"pre-commit",
|
| 47 |
"pyright>=1.1.389",
|
| 48 |
"pytest>=8.3.3",
|
|
|
|
| 50 |
"pytest-flakefinder",
|
| 51 |
"pytest-xdist>=3.6.1",
|
| 52 |
"ruff",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
]
|
| 54 |
|
| 55 |
[project.scripts]
|
src/fastmcp/server/server.py
CHANGED
|
@@ -13,7 +13,6 @@ import anyio
|
|
| 13 |
import httpx
|
| 14 |
import pydantic_core
|
| 15 |
import uvicorn
|
| 16 |
-
from fastapi import FastAPI
|
| 17 |
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
| 18 |
from mcp.server.lowlevel.server import LifespanResultT
|
| 19 |
from mcp.server.lowlevel.server import Server as MCPServer
|
|
@@ -846,11 +845,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 846 |
|
| 847 |
@classmethod
|
| 848 |
def from_fastapi(
|
| 849 |
-
cls, app:
|
| 850 |
) -> "FastMCPOpenAPI":
|
| 851 |
"""
|
| 852 |
Create a FastMCP server from a FastAPI application.
|
| 853 |
"""
|
|
|
|
| 854 |
from .openapi import FastMCPOpenAPI
|
| 855 |
|
| 856 |
client = httpx.AsyncClient(
|
|
|
|
| 13 |
import httpx
|
| 14 |
import pydantic_core
|
| 15 |
import uvicorn
|
|
|
|
| 16 |
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
| 17 |
from mcp.server.lowlevel.server import LifespanResultT
|
| 18 |
from mcp.server.lowlevel.server import Server as MCPServer
|
|
|
|
| 845 |
|
| 846 |
@classmethod
|
| 847 |
def from_fastapi(
|
| 848 |
+
cls, app: "Any", name: str | None = None, **settings: Any
|
| 849 |
) -> "FastMCPOpenAPI":
|
| 850 |
"""
|
| 851 |
Create a FastMCP server from a FastAPI application.
|
| 852 |
"""
|
| 853 |
+
|
| 854 |
from .openapi import FastMCPOpenAPI
|
| 855 |
|
| 856 |
client = httpx.AsyncClient(
|
uv.lock
CHANGED
|
@@ -254,12 +254,12 @@ wheels = [
|
|
| 254 |
|
| 255 |
[[package]]
|
| 256 |
name = "fastmcp"
|
| 257 |
-
version = "2.1.3.
|
| 258 |
source = { editable = "." }
|
| 259 |
dependencies = [
|
| 260 |
{ name = "dotenv" },
|
| 261 |
{ name = "exceptiongroup" },
|
| 262 |
-
{ name = "
|
| 263 |
{ name = "mcp" },
|
| 264 |
{ name = "openapi-pydantic" },
|
| 265 |
{ name = "rich" },
|
|
@@ -271,6 +271,7 @@ dependencies = [
|
|
| 271 |
dev = [
|
| 272 |
{ name = "copychat" },
|
| 273 |
{ name = "dirty-equals" },
|
|
|
|
| 274 |
{ name = "ipython" },
|
| 275 |
{ name = "pdbpp" },
|
| 276 |
{ name = "pre-commit" },
|
|
@@ -286,7 +287,7 @@ dev = [
|
|
| 286 |
requires-dist = [
|
| 287 |
{ name = "dotenv", specifier = ">=0.9.9" },
|
| 288 |
{ name = "exceptiongroup", specifier = ">=1.2.2" },
|
| 289 |
-
{ name = "
|
| 290 |
{ name = "mcp", specifier = ">=1.6.0,<2.0.0" },
|
| 291 |
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
|
| 292 |
{ name = "rich", specifier = ">=13.9.4" },
|
|
@@ -298,6 +299,7 @@ requires-dist = [
|
|
| 298 |
dev = [
|
| 299 |
{ name = "copychat", specifier = ">=0.5.2" },
|
| 300 |
{ name = "dirty-equals", specifier = ">=0.9.0" },
|
|
|
|
| 301 |
{ name = "ipython", specifier = ">=8.12.3" },
|
| 302 |
{ name = "pdbpp", specifier = ">=0.10.3" },
|
| 303 |
{ name = "pre-commit" },
|
|
|
|
| 254 |
|
| 255 |
[[package]]
|
| 256 |
name = "fastmcp"
|
| 257 |
+
version = "2.1.3.dev42+be2ccc6"
|
| 258 |
source = { editable = "." }
|
| 259 |
dependencies = [
|
| 260 |
{ name = "dotenv" },
|
| 261 |
{ name = "exceptiongroup" },
|
| 262 |
+
{ name = "httpx" },
|
| 263 |
{ name = "mcp" },
|
| 264 |
{ name = "openapi-pydantic" },
|
| 265 |
{ name = "rich" },
|
|
|
|
| 271 |
dev = [
|
| 272 |
{ name = "copychat" },
|
| 273 |
{ name = "dirty-equals" },
|
| 274 |
+
{ name = "fastapi" },
|
| 275 |
{ name = "ipython" },
|
| 276 |
{ name = "pdbpp" },
|
| 277 |
{ name = "pre-commit" },
|
|
|
|
| 287 |
requires-dist = [
|
| 288 |
{ name = "dotenv", specifier = ">=0.9.9" },
|
| 289 |
{ name = "exceptiongroup", specifier = ">=1.2.2" },
|
| 290 |
+
{ name = "httpx", specifier = ">=0.28.1" },
|
| 291 |
{ name = "mcp", specifier = ">=1.6.0,<2.0.0" },
|
| 292 |
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
|
| 293 |
{ name = "rich", specifier = ">=13.9.4" },
|
|
|
|
| 299 |
dev = [
|
| 300 |
{ name = "copychat", specifier = ">=0.5.2" },
|
| 301 |
{ name = "dirty-equals", specifier = ">=0.9.0" },
|
| 302 |
+
{ name = "fastapi", specifier = ">=0.115.12" },
|
| 303 |
{ name = "ipython", specifier = ">=8.12.3" },
|
| 304 |
{ name = "pdbpp", specifier = ">=0.10.3" },
|
| 305 |
{ name = "pre-commit" },
|