Tutorial

How to Build an MCP Server from a Swagger Spec

Auto-generate an MCP tool server from any Swagger or OpenAPI spec. Give Claude Code native access to any REST API in minutes.

Model Context Protocol (MCP) lets AI agents call external tools natively. Instead of hand-coding each tool, you can auto-generate an MCP server from any Swagger or OpenAPI specification. This tutorial takes the Scavio API OpenAPI spec and produces a working MCP server that Claude Code can use directly. The same approach works for any API with a Swagger spec -- Stripe, Twilio, GitHub, or your internal services.

Prerequisites

  • Node.js 18+ or Python 3.9+
  • A Swagger/OpenAPI JSON or YAML spec
  • Basic understanding of MCP
  • Claude Code installed (for testing)

Walkthrough

Step 1: Parse the OpenAPI spec and extract endpoints

Load the Swagger spec and extract the endpoint definitions that will become MCP tools. Each endpoint becomes one tool with typed parameters.

Python
import json, yaml
from pathlib import Path

def parse_openapi(spec_path: str) -> list:
    """Parse OpenAPI spec into MCP tool definitions."""
    raw = Path(spec_path).read_text()
    spec = yaml.safe_load(raw) if spec_path.endswith('.yaml') else json.loads(raw)
    tools = []
    base_url = spec.get('servers', [{}])[0].get('url', '')
    for path, methods in spec.get('paths', {}).items():
        for method, details in methods.items():
            if method not in ('get', 'post', 'put', 'delete'):
                continue
            params = []
            for p in details.get('parameters', []):
                params.append({
                    'name': p['name'],
                    'type': p.get('schema', {}).get('type', 'string'),
                    'required': p.get('required', False),
                    'description': p.get('description', '')
                })
            # Extract request body schema
            body = details.get('requestBody', {}).get('content', {}).get('application/json', {})
            body_schema = body.get('schema', {}).get('properties', {})
            for name, prop in body_schema.items():
                params.append({
                    'name': name, 'type': prop.get('type', 'string'),
                    'required': name in body.get('schema', {}).get('required', []),
                    'description': prop.get('description', '')
                })
            tool_name = details.get('operationId', f'{method}_{path}'.replace('/', '_'))
            tools.append({
                'name': tool_name, 'description': details.get('summary', ''),
                'method': method.upper(), 'path': path,
                'base_url': base_url, 'parameters': params
            })
    print(f'Parsed {len(tools)} tools from spec')
    for t in tools[:5]:
        print(f'  {t["name"]}: {t["method"]} {t["path"]}')
    return tools

Step 2: Generate the MCP server code

Transform the parsed endpoints into a working MCP server. Each endpoint becomes a callable tool with input validation.

Python
def generate_mcp_server(tools: list, output_path: str = 'mcp_server.py'):
    """Generate a Python MCP server from parsed tool definitions."""
    lines = [
        'import json, os, sys, requests',
        'from typing import Any',
        '',
        'def handle_tool_call(name: str, arguments: dict) -> dict:',
        '    """Route tool calls to the correct API endpoint."""',
        '    tools_map = {',
    ]
    for tool in tools:
        lines.append(f'        "{tool["name"]}": {{"method": "{tool["method"]}", "path": "{tool["path"]}", "base_url": "{tool["base_url"]}"}},')  
    lines.extend([
        '    }',
        '    if name not in tools_map:',
        '        return {"error": f"Unknown tool: {name}"}',  
        '    spec = tools_map[name]',
        '    url = spec["base_url"] + spec["path"]',
        '    headers = {"Content-Type": "application/json",',
        '               "x-api-key": os.environ.get("API_KEY", "")}',
        '    if spec["method"] == "GET":',
        '        resp = requests.get(url, headers=headers, params=arguments)',
        '    else:',
        '        resp = requests.post(url, headers=headers, json=arguments)',
        '    return resp.json()',
        '',
    ])
    # Write MCP stdio loop
    lines.extend([
        'def main():',
        '    for line in sys.stdin:',
        '        msg = json.loads(line.strip())',
        '        if msg.get("method") == "tools/call":',
        '            result = handle_tool_call(msg["params"]["name"], msg["params"].get("arguments", {}))',
        '            print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"content": [{"type": "text", "text": json.dumps(result)}]}}))',
        '            sys.stdout.flush()',
        '',
        'if __name__ == "__main__":',
        '    main()',
    ])
    with open(output_path, 'w') as f:
        f.write('\n'.join(lines))
    print(f'Generated MCP server: {output_path}')
    print(f'Tools: {len(tools)}')

Step 3: Register the MCP server in Claude Code

Add your generated MCP server to .mcp.json so Claude Code discovers it automatically on startup.

Python
import json
from pathlib import Path

def register_mcp(server_name: str, server_path: str):
    mcp_config_path = Path('.mcp.json')
    config = json.loads(mcp_config_path.read_text()) if mcp_config_path.exists() else {'mcpServers': {}}
    config['mcpServers'][server_name] = {
        'command': 'python3',
        'args': [server_path],
        'env': {
            'API_KEY': 'your-api-key-here'
        }
    }
    mcp_config_path.write_text(json.dumps(config, indent=2))
    print(f'Registered MCP server: {server_name}')
    print(f'Config: {mcp_config_path}')
    print(f'Restart Claude Code to load the new tools.')

# Register the generated server
register_mcp('my-api', 'mcp_server.py')

Python Example

Python
import json, yaml, requests, os, sys

def parse_spec(spec_url):
    resp = requests.get(spec_url)
    spec = resp.json()
    tools = []
    base = spec.get('servers', [{}])[0].get('url', '')
    for path, methods in spec.get('paths', {}).items():
        for method, details in methods.items():
            if method in ('get', 'post'):
                tools.append({'name': details.get('operationId', path),
                    'method': method.upper(), 'path': path, 'base_url': base})
    return tools

def call_tool(tool, args):
    url = tool['base_url'] + tool['path']
    headers = {'x-api-key': os.environ.get('API_KEY', ''), 'Content-Type': 'application/json'}
    if tool['method'] == 'GET':
        return requests.get(url, headers=headers, params=args).json()
    return requests.post(url, headers=headers, json=args).json()

tools = parse_spec('https://api.scavio.dev/api/v1/openapi.json')
print(f'Parsed {len(tools)} tools')

JavaScript Example

JavaScript
async function parseSpec(specUrl) {
  const resp = await fetch(specUrl);
  const spec = await resp.json();
  const tools = [];
  const base = spec.servers?.[0]?.url || '';
  for (const [path, methods] of Object.entries(spec.paths || {})) {
    for (const [method, details] of Object.entries(methods)) {
      if (['get', 'post'].includes(method)) {
        tools.push({ name: details.operationId || path, method: method.toUpperCase(), path, base });
      }
    }
  }
  return tools;
}

async function callTool(tool, args) {
  const url = tool.base + tool.path;
  const opts = { headers: { 'x-api-key': process.env.API_KEY, 'Content-Type': 'application/json' } };
  if (tool.method === 'GET') {
    return (await fetch(`${url}?${new URLSearchParams(args)}`, opts)).json();
  }
  return (await fetch(url, { ...opts, method: 'POST', body: JSON.stringify(args) })).json();
}

parseSpec('https://api.scavio.dev/api/v1/openapi.json').then(t => console.log(`${t.length} tools`));

Expected Output

JSON
Parsed 6 tools from spec
  search: POST /api/v1/search
  tiktok_search_videos: POST /api/v1/tiktok/search/videos
  tiktok_user_info: POST /api/v1/tiktok/user/info

Generated MCP server: mcp_server.py
Registered MCP server: my-api
Restart Claude Code to load the new tools.

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Node.js 18+ or Python 3.9+. A Swagger/OpenAPI JSON or YAML spec. Basic understanding of MCP. Claude Code installed (for testing). A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Auto-generate an MCP tool server from any Swagger or OpenAPI spec. Give Claude Code native access to any REST API in minutes.