DEV Community

2x lazymac
2x lazymac

Posted on

Shipping 10 MCP-Compatible APIs in a Week: Architecture Walkthrough

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:

  1. Have clearly defined tool schemas (JSON Schema for all inputs/outputs)
  2. Return structured data, not prose
  3. Handle errors with machine-readable error codes
  4. Support idempotency (same input → same output, or explicit side-effect documentation)
  5. 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     │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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);
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

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' }
    }
  }
}));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Total deploy time: 4 minutes.

Build Your Own MCP API | lazymac API Store

Top comments (0)