| name | mcp-server-scaffold |
| description | Generate Model Context Protocol (MCP) tool servers from API descriptions, enabling AI assistants to connect to external services |
| license | MIT |
| metadata | {"version":"1.0.0","author":"Michael Lynn [mlynn.org](https://mlynn.org)","category":"ai-tooling","domain":"mcp-protocol","updated":"2026-03-01T00:00:00.000Z","python-tools":"openapi_parser.py, mcp_generator.py, server_tester.py","tech-stack":"python, typescript, mcp, openapi, json-rpc"} |
mcp-server-scaffold
Trigger
Use this skill when building MCP servers to connect AI assistants to APIs, databases, or custom tools.
Trigger phrases:
- "Create MCP server for [API]"
- "Generate MCP tool server"
- "Build MCP connector for [service]"
- "Scaffold MCP server from OpenAPI"
- "Connect Claude to [API]"
Overview
The Model Context Protocol (MCP) is an open standard for connecting AI assistants to tools and data sources. This skill generates production-ready MCP servers from API descriptions (OpenAPI/Swagger), custom tool specs, or database schemas.
What gets generated:
- MCP server implementation (TypeScript or Python)
- Tool definitions with JSON schemas
- Error handling and validation
- Authentication/authorization logic
- Testing utilities
- Docker deployment config
Not for: Building APIs themselves - this assumes you have an API and want to expose it via MCP.
How to Use
Quick Start
-
Parse API description:
python scripts/openapi_parser.py api-spec.yaml --output parsed.json
-
Generate MCP server:
python scripts/mcp_generator.py parsed.json \
--language typescript \
--output mcp-server/
-
Test the server:
python scripts/server_tester.py mcp-server/ --test-all
Python Tools
scripts/openapi_parser.py — Parse OpenAPI/Swagger specs and extract tool definitions
scripts/mcp_generator.py — Generate MCP server code (TypeScript or Python)
scripts/server_tester.py — Test MCP server tools and validate responses
Reference Docs
references/mcp-protocol-guide.md — MCP protocol overview and concepts
references/tool-design-patterns.md — Best practices for MCP tool design
Templates & Assets
assets/openapi-example.yaml — Sample OpenAPI spec
assets/tool-definition-template.json — Manual tool definition format
assets/docker-compose.yaml — Deployment configuration
Architecture Decisions
Why OpenAPI as Input
OpenAPI specs already describe:
- Endpoints and methods
- Request/response schemas
- Authentication requirements
- Error responses
Conversion: OpenAPI endpoint → MCP tool
/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
→ MCP Tool:
{
name: "get_user",
description: "Get user by ID",
inputSchema: {
type: "object",
properties: {
id: { type: "string" }
},
required: ["id"]
}
}
Tool Naming Convention
OpenAPI: GET /api/v1/users/{id}/profile
MCP Tool: get_user_profile
Rules:
- Method + path segments
- Snake_case
- Remove version prefixes
- Parameterized paths become tool inputs
Error Handling Strategy
MCP servers should return structured errors:
{
error: {
code: -32603,
message: "API error",
data: {
status: 404,
body: "User not found"
}
}
}
Categories:
-32700 Parse error (malformed request)
-32600 Invalid request (missing required params)
-32601 Method not found (unknown tool)
-32603 Internal error (API failure, network error)
Authentication Patterns
Pattern 1: API Key (most common)
const apiKey = process.env.API_KEY;
headers['Authorization'] = `Bearer ${apiKey}`;
Pattern 2: OAuth 2.0
const token = await refreshToken();
headers['Authorization'] = `Bearer ${token}`;
Pattern 3: Custom Auth
headers['X-Custom-Auth'] = computeSignature(request);
Best practice: Store credentials in environment variables, never hardcode.
Rate Limiting
MCP servers should handle rate limits gracefully:
async function callWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err) {
if (err.status === 429) {
const delay = Math.pow(2, i) * 1000;
await sleep(delay);
continue;
}
throw err;
}
}
}
Generated MCP Server Structure
TypeScript Server
mcp-server/
├── src/
│ ├── index.ts # Server entry point
│ ├── tools/ # Tool implementations
│ │ ├── get_user.ts
│ │ ├── create_user.ts
│ │ └── ...
│ ├── client.ts # API client wrapper
│ ├── types.ts # TypeScript types
│ └── validation.ts # Input validation
├── tests/
│ ├── tools.test.ts # Tool tests
│ └── integration.test.ts
├── package.json
├── tsconfig.json
├── .env.example
└── README.md
Python Server
mcp-server/
├── src/
│ ├── __init__.py
│ ├── server.py # Server entry point
│ ├── tools/ # Tool implementations
│ │ ├── get_user.py
│ │ ├── create_user.py
│ │ └── ...
│ ├── client.py # API client wrapper
│ └── validation.py # Input validation
├── tests/
│ ├── test_tools.py
│ └── test_integration.py
├── requirements.txt
├── .env.example
└── README.md
Python Tool Details
1. OpenAPI Parser
Purpose: Parse OpenAPI/Swagger specs and extract MCP tool definitions.
Usage:
python scripts/openapi_parser.py api-spec.yaml --output parsed.json
Output:
{
"info": {
"title": "User API",
"version": "1.0.0",
"baseUrl": "https://api.example.com"
},
"auth": {
"type": "bearer",
"headerName": "Authorization"
},
"tools": [
{
"name": "get_user",
"description": "Get user by ID",
"method": "GET",
"path": "/users/{id}",
"parameters": {
"path": ["id"],
"query": [],
"body": null
},
"inputSchema": { ... },
"responseSchema": { ... }
}
]
}
2. MCP Generator
Purpose: Generate MCP server code from parsed API spec.
Usage:
python scripts/mcp_generator.py parsed.json \
--language typescript \
--output mcp-server/ \
--with-docker
Options:
--language - typescript or python (default: typescript)
--output - Output directory
--with-docker - Generate Dockerfile and docker-compose.yaml
--auth-env-var - Name of env var for auth (default: API_KEY)
Generates:
- Server entry point
- Tool implementations (one file per tool)
- API client wrapper
- Type definitions
- Tests
- Deployment config
3. Server Tester
Purpose: Test MCP server tools and validate responses.
Usage:
python scripts/server_tester.py mcp-server/ \
--test-all \
--mock-api \
--output test-report.json
Tests:
- Tool schema validation
- Input validation (required fields, types)
- Error handling (missing params, invalid types)
- Response schema validation
- End-to-end with mock API
Output:
{
"summary": {
"total_tools": 5,
"passed": 4,
"failed": 1
},
"results": [
{
"tool": "get_user",
"tests": {
"schema_valid": true,
"input_validation": true,
"response_validation": true,
"error_handling": false
},
"errors": ["Missing 404 error handling"]
}
]
}
Workflow Example
Scenario: Create MCP server for GitHub API
Step 1: Get OpenAPI spec
curl -o github-api.yaml https://github.com/github/rest-api-description/releases/latest/download/api.github.com.yaml
Step 2: Parse spec
python scripts/openapi_parser.py github-api.yaml \
--filter "repos,issues" \
--output github-parsed.json
Output: 50 tools extracted (filtered to repos and issues endpoints)
Step 3: Generate MCP server
python scripts/mcp_generator.py github-parsed.json \
--language typescript \
--output github-mcp-server/ \
--auth-env-var GITHUB_TOKEN
Output: TypeScript MCP server with 50 tools
Step 4: Configure authentication
cd github-mcp-server/
cp .env.example .env
echo "GITHUB_TOKEN=ghp_your_token_here" >> .env
Step 5: Test tools
python scripts/server_tester.py github-mcp-server/ \
--test-all \
--output test-report.json
Output: 48/50 tools passing (2 require org access)
Step 6: Deploy
cd github-mcp-server/
npm install
npm run build
npm start
Step 7: Connect to Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"github": {
"command": "node",
"args": ["/path/to/github-mcp-server/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here"
}
}
}
}
Now Claude can call GitHub tools: "Create an issue in my repo", "List my repositories", etc.
Common Patterns
Pattern 1: Pagination Handling
Many APIs return paginated results. MCP tools should handle this:
async function listUsers(params: { limit?: number, cursor?: string }) {
const response = await api.get('/users', {
params: {
limit: params.limit || 100,
cursor: params.cursor
}
});
return {
users: response.data,
nextCursor: response.nextCursor,
hasMore: !!response.nextCursor
};
}
MCP Tool:
{
"name": "list_users",
"inputSchema": {
"type": "object",
"properties": {
"limit": { "type": "number", "default": 100 },
"cursor": { "type": "string" }
}
}
}
Pattern 2: Batch Operations
For efficiency, support batch operations:
async function batchGetUsers(params: { ids: string[] }) {
const chunks = chunkArray(params.ids, 50);
const results = [];
for (const chunk of chunks) {
const response = await api.post('/users/batch', { ids: chunk });
results.push(...response.data);
}
return results;
}
Pattern 3: Webhook Integration
Some MCP servers can register webhooks:
async function registerWebhook(params: { event: string, url: string }) {
return await api.post('/webhooks', {
event: params.event,
url: params.url,
secret: process.env.WEBHOOK_SECRET
});
}
Pattern 4: Caching
Cache frequently accessed data:
const cache = new Map();
async function getUser(params: { id: string }) {
const cacheKey = `user:${params.id}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const user = await api.get(`/users/${params.id}`);
cache.set(cacheKey, user, { ttl: 300 });
return user;
}
Tool Design Best Practices
1. Clear Descriptions
Bad:
{
"name": "get_data",
"description": "Gets data"
}
Good:
{
"name": "get_user_profile",
"description": "Retrieves detailed user profile including name, email, created date, and account settings. Requires user ID."
}
2. Explicit Input Schemas
Always specify required fields and types:
{
"inputSchema": {
"type": "object",
"properties": {
"userId": {
"type": "string",
"description": "Unique user identifier"
},
"includeSettings": {
"type": "boolean",
"description": "Whether to include account settings",
"default": false
}
},
"required": ["userId"]
}
}
3. Structured Errors
Return actionable error messages:
if (!params.userId) {
throw {
code: -32600,
message: "Missing required parameter: userId",
data: {
parameter: "userId",
type: "string",
description: "Unique user identifier"
}
};
}
4. Response Normalization
Normalize API responses to consistent structure:
async function getUser(params) {
try {
const response = await api.get(`/users/${params.id}`);
return {
success: true,
data: response.data,
metadata: {
requestId: response.headers['x-request-id'],
rateLimit: {
remaining: response.headers['x-ratelimit-remaining'],
reset: response.headers['x-ratelimit-reset']
}
}
};
} catch (err) {
return {
success: false,
error: {
message: err.message,
status: err.status,
code: err.code
}
};
}
}
Quality Checklist
Before deploying MCP server:
When to Use vs. Direct API Calls
| Use MCP Server | Use Direct API |
|---|
| AI assistant integration | Simple scripts |
| Multiple tools from one API | One-off requests |
| Reusable across projects | Project-specific code |
| Need structured error handling | Quick prototypes |
| Authentication abstraction needed | Full control required |
Deployment Options
Option 1: Local (Development)
npm start
Option 2: Docker (Production)
docker-compose up -d
Option 3: Cloud (Serverless)
export const handler = mcpServer.handler;
Monitoring
Track MCP server health:
const metrics = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
averageLatency: 0
};
function recordMetrics(tool: string, success: boolean, latency: number) {
metrics.totalRequests++;
if (success) metrics.successfulRequests++;
else metrics.failedRequests++;
metrics.averageLatency =
(metrics.averageLatency * (metrics.totalRequests - 1) + latency) / metrics.totalRequests;
console.log(`[${tool}] ${success ? 'OK' : 'FAIL'} ${latency}ms`);
}
References
Credits
Michael Lynn — mlynn.org · @mlynn · LinkedIn · GitHub
Next steps after generating server:
- Test tools with mock API
- Configure authentication
- Deploy and connect to Claude Desktop
- Monitor usage and errors
- Iterate on tool descriptions based on usage