Last week I shipped 10 MCP-compatible APIs. Not as separate projects — as one coordinated release from a single architecture. Here's the full walkthrough of how I structured it.
The Core Insight
MCP (Model Context Protocol) compatibility isn't a feature you bolt on — it's a constraint that shapes your entire API design. Start with MCP requirements and everything else falls into place.
What MCP Compatibility Requires
An MCP-compatible API must:
- Have clearly defined tool schemas (JSON Schema for all inputs/outputs)
- Return structured data, not prose
- Handle errors with machine-readable error codes
- Support idempotency (same input → same output, or explicit side-effect documentation)
- Response times under 30 seconds (MCP timeout default)
The Architecture
┌─────────────────────────────────────┐
│ Cloudflare Workers │
│ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ /schema │ │ /invoke │ │
│ │ Returns │ │ Executes │ │
│ │ JSON │ │ tool with │ │
│ │ Schema │ │ validation │ │
│ └──────────┘ └──────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Shared: auth, rate limit, │ │
│ │ error handling, logging │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
Every API shares the same middleware. I wrote it once.
The Shared Middleware
// middleware.js — used by all 10 APIs
export async function withMCPMiddleware(handler, schema) {
return async (request, env) => {
// 1. Auth
const key = request.headers.get('Authorization')?.replace('Bearer ', '');
if (!await validateKey(key, env)) {
return error(401, 'UNAUTHORIZED', 'Invalid API key');
}
// 2. Parse and validate input
const body = await request.json().catch(() => null);
const validation = validateSchema(body, schema.input);
if (!validation.valid) {
return error(400, 'INVALID_INPUT', validation.errors);
}
// 3. Rate limiting
const limited = await checkRateLimit(key, env);
if (limited) {
return error(429, 'RATE_LIMITED', 'Slow down');
}
// 4. Execute handler
try {
const result = await handler(body, env);
return Response.json({ success: true, data: result });
} catch (err) {
return error(500, 'INTERNAL_ERROR', err.message);
}
};
}
Tool Schema Generation
Each API self-documents via a /schema endpoint:
// auto-generated from handler definition
app.get('/schema', () => Response.json({
name: 'ai_cost_calculator',
description: 'Calculate cost for AI model API calls',
input_schema: {
type: 'object',
properties: {
model: { type: 'string', enum: ['gpt-4', 'claude-3-sonnet', 'gemini-pro'] },
input_tokens: { type: 'integer', minimum: 1 },
output_tokens: { type: 'integer', minimum: 0 }
},
required: ['model', 'input_tokens']
},
output_schema: {
type: 'object',
properties: {
input_cost: { type: 'number' },
output_cost: { type: 'number' },
total_cost: { type: 'number' },
currency: { type: 'string' }
}
}
}));
Deployment Pipeline
One command deploys all 10:
for api in ai-spend tech-stack llm-pricing k-privacy k-nlp \
k-company k-exchange ai-ratelimit ai-status k-address; do
cd ~/apis/$api && npx wrangler deploy --env production
done
Total deploy time: 4 minutes.
Top comments (0)