docs/integrations/openapi.mdx
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />FastMCP can automatically generate an MCP server from any OpenAPI specification, allowing AI models to interact with existing APIs through the MCP protocol. Instead of manually creating tools and resources, you provide an OpenAPI spec and FastMCP intelligently converts API endpoints into the appropriate MCP components.
<Note> Under the hood, OpenAPI integration uses OpenAPIProvider (v3.0.0+) to source tools from the specification. See [Providers](/servers/providers/overview) to understand how FastMCP sources components. </Note> <Tip> Generating MCP servers from OpenAPI is a great way to get started with FastMCP, but in practice LLMs achieve **significantly better performance** with well-designed and curated MCP servers than with auto-converted OpenAPI servers. This is especially true for complex APIs with many endpoints and parameters.We recommend using the FastAPI integration for bootstrapping and prototyping, not for mirroring your API to LLM clients. See the post Stop Converting Your REST APIs to MCP for more details. </Tip>
To convert an OpenAPI specification to an MCP server, use the FastMCP.from_openapi() class method:
import httpx
from fastmcp import FastMCP
# Create an HTTP client for your API
client = httpx.AsyncClient(base_url="https://api.example.com")
# Load your OpenAPI spec
openapi_spec = httpx.get("https://api.example.com/openapi.json").json()
# Create the MCP server
mcp = FastMCP.from_openapi(
openapi_spec=openapi_spec,
client=client,
name="My API Server"
)
if __name__ == "__main__":
mcp.run()
If your API requires authentication, configure it on the HTTP client:
import httpx
from fastmcp import FastMCP
# Bearer token authentication
api_client = httpx.AsyncClient(
base_url="https://api.example.com",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
# Create MCP server with authenticated client
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=api_client,
timeout=30.0 # 30 second timeout for all requests
)
By default, FastMCP converts every endpoint in your OpenAPI specification into an MCP Tool. This provides a simple, predictable starting point that ensures all your API's functionality is immediately available to the vast majority of LLM clients which only support MCP tools.
While this is a pragmatic default for maximum compatibility, you can easily customize this behavior. Internally, FastMCP uses an ordered list of RouteMap objects to determine how to map OpenAPI routes to various MCP component types.
Each RouteMap specifies a combination of methods, patterns, and tags, as well as a corresponding MCP component type. Each OpenAPI route is checked against each RouteMap in order, and the first one that matches every criteria is used to determine its converted MCP type. A special type, EXCLUDE, can be used to exclude routes from the MCP server entirely.
["GET", "POST"] or "*" for all)r"^/users/.*" or r".*" for all){}) means no tag filtering, so the route matches regardless of its tags.TOOL, RESOURCE, RESOURCE_TEMPLATE, or EXCLUDE)Here is FastMCP's default rule:
from fastmcp.server.providers.openapi import RouteMap, MCPType
DEFAULT_ROUTE_MAPPINGS = [
# All routes become tools
RouteMap(mcp_type=MCPType.TOOL),
]
When creating your FastMCP server, you can customize routing behavior by providing your own list of RouteMap objects. Your custom maps are processed before the default route maps, and routes will be assigned to the first matching custom map.
For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to Resource and ResourceTemplate components based on whether they had path parameters. (This was changed solely for client compatibility reasons.) You can restore this behavior by providing custom route maps:
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import RouteMap, MCPType
# Restore pre-2.8.0 semantic mapping
semantic_maps = [
# GET requests with path parameters become ResourceTemplates
RouteMap(methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE),
# All other GET requests become Resources
RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
]
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
route_maps=semantic_maps,
)
With these maps, GET requests are handled semantically, and all other methods (POST, PUT, etc.) will fall through to the default rule and become Tools.
Here is a more complete example that uses custom route maps to convert all GET endpoints under /analytics/ to tools while excluding all admin endpoints and all routes tagged "internal". All other routes will be handled by the default rules:
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
route_maps=[
# Analytics `GET` endpoints are tools
RouteMap(
methods=["GET"],
pattern=r"^/analytics/.*",
mcp_type=MCPType.TOOL,
),
# Exclude all admin endpoints
RouteMap(
pattern=r"^/admin/.*",
mcp_type=MCPType.EXCLUDE,
),
# Exclude all routes tagged "internal"
RouteMap(
tags={"internal"},
mcp_type=MCPType.EXCLUDE,
),
],
)
To exclude routes from the MCP server, use a route map to assign them to MCPType.EXCLUDE.
You can use this to remove sensitive or internal routes by targeting them specifically:
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
route_maps=[
RouteMap(pattern=r"^/admin/.*", mcp_type=MCPType.EXCLUDE),
RouteMap(tags={"internal"}, mcp_type=MCPType.EXCLUDE),
],
)
Or you can use a catch-all rule to exclude everything that your maps don't handle explicitly:
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
route_maps=[
# custom mapping logic goes here
# ... your specific route maps ...
# exclude all remaining routes
RouteMap(mcp_type=MCPType.EXCLUDE),
],
)
For advanced use cases that require more complex logic, you can provide a route_map_fn callable. After the route map logic is applied, this function is called on each matched route and its assigned MCP component type. It can optionally return a different component type to override the mapped assignment. If it returns None, the assigned type is used.
In addition to more precise targeting of methods, patterns, and tags, this function can access any additional OpenAPI metadata about the route.
<Tip> The `route_map_fn` is called on all routes, even those that matched `MCPType.EXCLUDE` in your custom maps. This gives you an opportunity to customize the mapping or even override an exclusion. </Tip>from fastmcp import FastMCP
from fastmcp.server.providers.openapi import RouteMap, MCPType
from fastmcp.utilities.openapi import HTTPRoute
def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None:
"""Advanced route type mapping."""
# Convert all admin routes to tools regardless of HTTP method
if "/admin/" in route.path:
return MCPType.TOOL
elif "internal" in route.tags:
return MCPType.EXCLUDE
# Convert user detail routes to templates even if they're POST
elif route.path.startswith("/users/") and route.method == "POST":
return MCPType.RESOURCE_TEMPLATE
# Use defaults for all other routes
return None
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
route_map_fn=custom_route_mapper,
)
FastMCP automatically generates names for MCP components based on the OpenAPI specification. By default, it uses the operationId from your OpenAPI spec, up to the first double underscore (__).
All component names are automatically:
For more control over component names, you can provide an mcp_names dictionary that maps operationId values to your desired names. The operationId must be exactly as it appears in the OpenAPI spec. The provided name will always be slugified and truncated.
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
mcp_names={
"list_users__with_pagination": "user_list",
"create_user__admin_required": "create_user",
"get_user_details__admin_required": "user_detail",
}
)
Any operationId not found in mcp_names will use the default strategy (operationId up to the first __).
FastMCP provides several ways to add tags to your MCP components, allowing you to categorize and organize them for better discoverability and filtering. Tags are combined from multiple sources to create the final set of tags on each component.
You can add custom tags to components created from specific routes using the mcp_tags parameter in RouteMap. These tags will be applied to all components created from routes that match that particular route map.
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
route_maps=[
# Add custom tags to all POST endpoints
RouteMap(
methods=["POST"],
pattern=r".*",
mcp_type=MCPType.TOOL,
mcp_tags={"write-operation", "api-mutation"}
),
# Add different tags to detail view endpoints
RouteMap(
methods=["GET"],
pattern=r".*\{.*\}.*",
mcp_type=MCPType.RESOURCE_TEMPLATE,
mcp_tags={"detail-view", "parameterized"}
),
# Add tags to list endpoints
RouteMap(
methods=["GET"],
pattern=r".*",
mcp_type=MCPType.RESOURCE,
mcp_tags={"list-data", "collection"}
),
],
)
You can add tags to all components by providing a tags parameter when creating your MCP server. These global tags will be applied to every component created from your OpenAPI specification.
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
tags={"api-v2", "production", "external"}
)
FastMCP automatically includes OpenAPI tags from your specification in the component's metadata. These tags are available to MCP clients through the meta.fastmcp.tags field, allowing clients to filter and organize components based on the original OpenAPI tagging:
This makes it easy for clients to understand and organize API endpoints based on their original OpenAPI categorization.
By default, FastMCP creates MCP components using a variety of metadata from the OpenAPI spec, such as incorporating the OpenAPI description into the MCP component description.
At times you may want to modify those MCP components in a variety of ways, such as adding LLM-specific instructions or tags. For fine-grained customization, you can provide a mcp_component_fn when creating the MCP server. After each MCP component has been created, this function is called on it and has the opportunity to modify it in-place.
from fastmcp.server.providers.openapi import (
OpenAPITool,
OpenAPIResource,
OpenAPIResourceTemplate,
)
from fastmcp.utilities.openapi import HTTPRoute
def customize_components(
route: HTTPRoute,
component: OpenAPITool | OpenAPIResource | OpenAPIResourceTemplate,
) -> None:
# Add custom tags to all components
component.tags.add("openapi")
# Customize based on component type
if isinstance(component, OpenAPITool):
component.description = f"🔧 {component.description} (via API)"
if isinstance(component, OpenAPIResource):
component.description = f"📊 {component.description}"
component.tags.add("data")
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
mcp_component_fn=customize_components,
)
FastMCP intelligently handles different types of parameters in OpenAPI requests:
By default, FastMCP only includes query parameters that have non-empty values. Parameters with None values or empty strings are automatically filtered out.
# When calling this tool...
await client.call_tool("search_products", {
"category": "electronics", # ✅ Included
"min_price": 100, # ✅ Included
"max_price": None, # ❌ Excluded
"brand": "", # ❌ Excluded
})
# The HTTP request will be: GET /products?category=electronics&min_price=100
Path parameters are typically required by REST APIs. FastMCP:
None values# ✅ This works
await client.call_tool("get_user", {"user_id": 123})
# ❌ This raises: "Missing required path parameters: {'user_id'}"
await client.call_tool("get_user", {"user_id": None})
FastMCP handles array parameters according to OpenAPI specifications:
explode parameter (default: True)# Query array with explode=true (default)
# ?tags=red&tags=blue&tags=green
# Query array with explode=false
# ?tags=red,blue,green
# Path array (always comma-separated)
# /items/red,blue,green
Header parameters are automatically converted to strings and included in the HTTP request.