fastmcp/docs/patterns/openapi.mdx
2025-04-30 12:33:42 -04:00

226 lines
6.8 KiB
Text

---
title: OpenAPI Integration
sidebarTitle: OpenAPI
description: Generate MCP servers from OpenAPI specs
icon: code-branch
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
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.
```python
import httpx
from fastmcp import FastMCP
# Create a client for your API
api_client = httpx.AsyncClient(base_url="https://api.example.com")
# Load your OpenAPI spec
spec = {...}
# Create an MCP server from your OpenAPI spec
mcp = FastMCP.from_openapi(openapi_spec=spec, client=api_client)
if __name__ == "__main__":
mcp.run()
```
## Configuration Options
### Timeout
You can set a timeout for all API requests:
```python
# Set a 5 second timeout for all requests
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=api_client,
timeout=5.0
)
```
This timeout is applied to all requests made by tools, resources, and resource templates.
## Route Mapping
By default, OpenAPI routes are mapped to MCP components based on these rules:
| 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 |
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
### Request Parameter Handling
FastMCP carefully handles different types of parameters in OpenAPI requests:
#### Query Parameters
By default, FastMCP will only include query parameters that have non-empty values. Parameters with `None` values or empty strings (`""`) are automatically filtered out of requests. This ensures that API servers don't receive unnecessary empty parameters that might cause issues.
For example, if you call a tool with these parameters:
```python
await client.call_tool("search_products", {
"category": "electronics", # Will be included
"min_price": 100, # Will be included
"max_price": None, # Will be excluded
"brand": "", # Will be excluded
})
```
The resulting HTTP request will only include `category=electronics&min_price=100`.
#### Path Parameters
For path parameters, which are typically required by REST APIs, FastMCP filters out `None` values and checks that all required path parameters are provided. If a required path parameter is missing or `None`, an error will be raised.
```python
# This will work
await client.call_tool("get_product", {"product_id": 123})
# This will raise ValueError: "Missing required path parameters: {'product_id'}"
await client.call_tool("get_product", {"product_id": None})
```
## Complete Example
```python [expandable]
import asyncio
import httpx
from fastmcp import FastMCP
# Sample OpenAPI spec for a Pet Store API
petstore_spec = {
"openapi": "3.0.0",
"info": {
"title": "Pet Store API",
"version": "1.0.0",
"description": "A sample API for managing pets",
},
"paths": {
"/pets": {
"get": {
"operationId": "listPets",
"summary": "List all pets",
"responses": {"200": {"description": "A list of pets"}},
},
"post": {
"operationId": "createPet",
"summary": "Create a new pet",
"responses": {"201": {"description": "Pet created successfully"}},
},
},
"/pets/{petId}": {
"get": {
"operationId": "getPet",
"summary": "Get a pet by ID",
"parameters": [
{
"name": "petId",
"in": "path",
"required": True,
"schema": {"type": "string"},
}
],
"responses": {
"200": {"description": "Pet details"},
"404": {"description": "Pet not found"},
},
}
},
},
}
async def check_mcp(mcp: FastMCP):
# List what components were created
tools = await mcp.get_tools()
resources = await mcp.get_resources()
templates = await mcp.get_resource_templates()
print(
f"{len(tools)} Tool(s): {', '.join([t.name for t in tools.values()])}"
) # Should include createPet
print(
f"{len(resources)} Resource(s): {', '.join([r.name for r in resources.values()])}"
) # Should include listPets
print(
f"{len(templates)} Resource Template(s): {', '.join([t.name for t in templates.values()])}"
) # Should include getPet
return mcp
if __name__ == "__main__":
# Client for the Pet Store API
client = httpx.AsyncClient(base_url="https://petstore.example.com/api")
# Create the MCP server
mcp = FastMCP.from_openapi(
openapi_spec=petstore_spec, client=client, name="PetStore"
)
asyncio.run(check_mcp(mcp))
# Start the MCP server
mcp.run()
```