Generating an MCP server from an OpenAPI spec converts any documented REST API into a set of typed tools that AI agents can call directly, eliminating manual tool definition. Instead of writing MCP tool handlers by hand for each endpoint, you parse the OpenAPI JSON/YAML, extract operations, parameters, and response schemas, and generate a standards-compliant MCP server that Claude Code, Cursor, or any MCP client can consume. This tutorial builds a generator in Python that takes an OpenAPI 3.x spec and outputs a working MCP server.
Prerequisites
- Python 3.10+
- fastmcp library installed (pip install fastmcp)
- An OpenAPI 3.x spec (JSON or YAML)
- Scavio API key for testing with a real spec
Walkthrough
Step 1: Parse the OpenAPI spec
Load and parse an OpenAPI 3.x spec file, extracting all operations with their paths, methods, parameters, and request bodies.
import json, yaml
from pathlib import Path
def parse_openapi(spec_path):
content = Path(spec_path).read_text()
spec = yaml.safe_load(content) if spec_path.endswith(('.yml', '.yaml')) else json.loads(content)
operations = []
for path, methods in spec.get('paths', {}).items():
for method, details in methods.items():
if method in ('get', 'post', 'put', 'patch', 'delete'):
op = {
'operation_id': details.get('operationId', f'{method}_{path}'.replace('/', '_')),
'summary': details.get('summary', ''),
'method': method.upper(),
'path': path,
'parameters': details.get('parameters', []),
'request_body': details.get('requestBody', {}),
}
operations.append(op)
print(f'Parsed {len(operations)} operations from {spec_path}')
return spec, operations
spec, ops = parse_openapi('openapi.json')Step 2: Generate MCP tool definitions
Convert each OpenAPI operation into an MCP tool with typed parameters and descriptions.
def openapi_type_to_python(schema):
type_map = {'string': 'str', 'integer': 'int', 'number': 'float',
'boolean': 'bool', 'array': 'list', 'object': 'dict'}
return type_map.get(schema.get('type', 'string'), 'str')
def generate_tool_code(op, base_url):
params = []
for p in op['parameters']:
ptype = openapi_type_to_python(p.get('schema', {}))
default = f' = None' if not p.get('required') else ''
params.append(f"{p['name']}: {ptype}{default}")
body_params = []
if op['request_body']:
content = op['request_body'].get('content', {})
json_schema = content.get('application/json', {}).get('schema', {})
for prop, schema in json_schema.get('properties', {}).items():
ptype = openapi_type_to_python(schema)
required = prop in json_schema.get('required', [])
default = '' if required else ' = None'
body_params.append(f"{prop}: {ptype}{default}")
all_params = ', '.join(params + body_params)
name = op['operation_id'].replace('-', '_').replace('.', '_')
return name, all_params, op['summary']Step 3: Build the MCP server file
Generate a complete MCP server Python file that registers all tools and handles HTTP requests to the underlying API.
def generate_mcp_server(spec, operations, base_url, output_path='generated_mcp.py'):
lines = [
'import httpx',
'from fastmcp import FastMCP',
'',
f'mcp = FastMCP("Generated API")',
f'BASE_URL = "{base_url}"',
'',
]
for op in operations:
name, params, summary = generate_tool_code(op, base_url)
lines.append(f'@mcp.tool(description="{summary}")')
lines.append(f'async def {name}({params}) -> dict:')
lines.append(f' async with httpx.AsyncClient() as client:')
if op['method'] == 'GET':
lines.append(f' resp = await client.get(f"{{BASE_URL}}{op["path"]}")')
else:
lines.append(f' resp = await client.{op["method"].lower()}(f"{{BASE_URL}}{op["path"]}", json=locals())')
lines.append(f' return resp.json()')
lines.append('')
Path(output_path).write_text('\n'.join(lines))
print(f'Generated MCP server with {len(operations)} tools at {output_path}')
generate_mcp_server(spec, ops, 'https://api.example.com')Step 4: Test with the Scavio OpenAPI spec
Fetch the Scavio API spec using search and generate an MCP server from it as a working example.
import requests, os
# Use Scavio search to find OpenAPI specs for testing
H = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=H, json={'query': 'petstore openapi 3.0 json spec', 'country_code': 'us'}).json()
print('Found specs to test with:')
for r in data.get('organic_results', [])[:3]:
print(f" {r.get('title', '')}: {r.get('link', '')}")
# Example: generate from the classic Petstore spec
# spec, ops = parse_openapi('petstore.json')
# generate_mcp_server(spec, ops, 'https://petstore3.swagger.io/api/v3')Python Example
import json, yaml, httpx, os, requests
from pathlib import Path
def parse_openapi(spec_path):
content = Path(spec_path).read_text()
spec = yaml.safe_load(content) if spec_path.endswith(('.yml', '.yaml')) else json.loads(content)
ops = []
for path, methods in spec.get('paths', {}).items():
for method, details in methods.items():
if method in ('get', 'post', 'put', 'patch', 'delete'):
ops.append({
'id': details.get('operationId', f'{method}_{path}'.replace('/', '_')),
'summary': details.get('summary', ''),
'method': method.upper(), 'path': path,
'parameters': details.get('parameters', []),
'body': details.get('requestBody', {}),
})
return spec, ops
def generate_server(spec, ops, base_url, out='generated_mcp.py'):
type_map = {'string': 'str', 'integer': 'int', 'number': 'float', 'boolean': 'bool'}
lines = ['import httpx', 'from fastmcp import FastMCP', '',
'mcp = FastMCP("Generated API")', f'BASE = "{base_url}"', '']
for op in ops:
name = op['id'].replace('-', '_').replace('.', '_')
params = []
for p in op['parameters']:
pt = type_map.get(p.get('schema', {}).get('type', 'string'), 'str')
params.append(f"{p['name']}: {pt}")
sig = ', '.join(params)
lines.extend([
f'@mcp.tool(description="{op["summary"]}")',
f'async def {name}({sig}) -> dict:',
f' async with httpx.AsyncClient() as c:',
f' r = await c.request("{op["method"]}", f"{{BASE}}{op["path"]}")',
f' return r.json()', ''
])
Path(out).write_text('\n'.join(lines))
print(f'Generated {len(ops)} MCP tools to {out}')
# Example usage
# spec, ops = parse_openapi('openapi.json')
# generate_server(spec, ops, 'https://api.example.com')
print('MCP generator ready. Pass any OpenAPI 3.x spec.')JavaScript Example
const fs = require('fs');
function parseOpenAPI(specPath) {
const spec = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
const ops = [];
for (const [path, methods] of Object.entries(spec.paths || {})) {
for (const [method, details] of Object.entries(methods)) {
if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
ops.push({
id: details.operationId || \`\${method}_\${path.replace(/\//g, '_')}\`,
summary: details.summary || '',
method: method.toUpperCase(), path,
parameters: details.parameters || [],
});
}
}
}
return {spec, ops};
}
function generateServer(spec, ops, baseUrl, out = 'generated_mcp.mjs') {
let code = \`import {FastMCP} from 'fastmcp';\nconst mcp = new FastMCP('Generated API');\nconst BASE = '\${baseUrl}';\n\n\`;
for (const op of ops) {
const name = op.id.replace(/[-. ]/g, '_');
const params = op.parameters.map(p => \`\${p.name}: {type: 'string'}\`).join(', ');
code += \`mcp.addTool({name: '\${name}', description: '\${op.summary}',\n parameters: {\${params}},\n execute: async (args) => {\n const r = await fetch(\\\`\\\${BASE}\${op.path}\\\`, {method: '\${op.method}'});\n return JSON.stringify(await r.json());\n }\n});\n\n\`;
}
fs.writeFileSync(out, code);
console.log(\`Generated \${ops.length} MCP tools to \${out}\`);
}
// const {spec, ops} = parseOpenAPI('openapi.json');
// generateServer(spec, ops, 'https://api.example.com');
console.log('MCP generator ready');Expected Output
Parsed 12 operations from openapi.json
Generated MCP server with 12 tools at generated_mcp.py
Tools: get_users, post_users, get_users_by_id, put_users_by_id, ...