fastmcp/docs/patterns/openapi.mdx
2025-04-13 14:16:28 -04:00

172 lines
7.4 KiB
Text

---
title: OpenAPI Integration
sidebarTitle: OpenAPI
description: Automatically create FastMCP servers from existing OpenAPI specifications.
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.
## The Goal: API -> MCP Server
The core idea is to map OpenAPI paths and operations (like `GET /users/{id}` or `POST /orders`) to their corresponding MCP components:
- `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).
FastMCP automates this mapping process.
## Creating from OpenAPI Spec
Use the `FastMCP.from_openapi()` class method. You need:
1. The OpenAPI specification as a Python dictionary.
2. An `httpx.AsyncClient` configured to make requests to the actual API backend.
<CodeGroup>
```python server.py
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
"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."}},
},
"post": { # POST -> Tool
"summary": "Create a pet",
"operationId": "createPet",
"tags": ["pets"],
"requestBody": { # Request body -> Tool arguments
"required": True,
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PetInput"}}}
},
"responses": {"201": {"description": "Pet created."}},
},
},
"/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"}}},
}
}
}
```
</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(
openapi_spec=petstore_spec,
client=http_client,
name="PetStoreMCP",
route_maps=custom_maps # Pass custom mapping rules
)
return mcp_server
```
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.