mcpopenapiswagger
Swagger to MCP Server: The Pattern Explained
Auto-generate MCP server tool definitions from OpenAPI specs. Each endpoint becomes a callable tool for your LLM.
8 min
Any API with an OpenAPI (Swagger) specification can be automatically converted into an MCP server. Each endpoint becomes a callable tool, path parameters become tool arguments, and response schemas define the output format. This pattern lets you expose any REST API to LLMs without writing tool definitions by hand.
How the pattern works
- Parse the OpenAPI spec (YAML or JSON)
- Extract each endpoint as a tool definition
- Map path/query parameters to tool input schema
- Generate the MCP server with HTTP call handlers
- Register authentication from environment variables
Step 1: Parse the OpenAPI spec
Python
import yaml, json
def load_spec(path):
with open(path) as f:
if path.endswith(".yaml") or path.endswith(".yml"):
return yaml.safe_load(f)
return json.load(f)
spec = load_spec("openapi.yaml")
base_url = spec.get("servers", [{}])[0].get("url", "http://localhost:8000")
paths = spec.get("paths", {})Step 2: Generate tool definitions
Python
def spec_to_tools(spec):
tools = []
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ("get", "post", "put", "delete"):
continue
params = details.get("parameters", [])
properties = {}
required = []
for p in params:
properties[p["name"]] = {
"type": p.get("schema", {}).get("type", "string"),
"description": p.get("description", ""),
}
if p.get("required", False):
required.append(p["name"])
# Handle request body for POST/PUT
body = details.get("requestBody", {})
if body:
schema = body.get("content", {}).get(
"application/json", {}
).get("schema", {})
for prop_name, prop_def in schema.get("properties", {}).items():
properties[prop_name] = prop_def
required.extend(schema.get("required", []))
tools.append({
"name": details.get("operationId", f"{method}_{path}"),
"description": details.get("summary", path),
"method": method.upper(),
"path": path,
"input_schema": {
"type": "object",
"properties": properties,
"required": required,
},
})
return toolsStep 3: Build the MCP server
Python
from mcp.server import Server
from mcp.types import TextContent
import requests, os
app = Server("openapi-bridge")
def make_handler(tool_def, base_url):
async def handler(**kwargs):
path = tool_def["path"]
# Replace path parameters
for key, value in kwargs.items():
path = path.replace("{" + key + "}", str(value))
url = f"{base_url}{path}"
headers = {"Authorization": f"Bearer {os.environ.get('API_TOKEN', '')}"}
if tool_def["method"] == "GET":
resp = requests.get(url, headers=headers, params=kwargs)
else:
resp = requests.request(
tool_def["method"], url, headers=headers, json=kwargs,
)
return [TextContent(type="text", text=resp.text)]
return handler
# Register all tools from spec
spec = load_spec("openapi.yaml")
base_url = spec["servers"][0]["url"]
for tool_def in spec_to_tools(spec):
handler = make_handler(tool_def, base_url)
handler.__name__ = tool_def["name"]
handler.__doc__ = tool_def["description"]
app.tool()(handler)Real-world example: search API
The Scavio search API has an OpenAPI spec. Converting it to MCP gives your LLM access to web search, TikTok search, and YouTube search as callable tools -- or you can skip the conversion and use the hosted MCP endpoint directly.
JSON
{
"mcpServers": {
"scavio": {
"url": "https://mcp.scavio.dev/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}Limitations of auto-generation
- Complex authentication flows (OAuth2) need manual handling
- Paginated endpoints need wrapper logic for multi-page fetching
- Tool descriptions from Swagger summaries are often too terse for LLMs
- Large APIs generate too many tools -- LLMs struggle with 50+ tools
Best practices
- Filter to 5-15 most-used endpoints, not the entire API
- Rewrite tool descriptions for LLM consumption (what it does, when to use it)
- Group related endpoints into a single tool with an action parameter
- Add error handling that returns human-readable messages