Grigoriy Dobryakov

Howto · breakdown

Breakdown 04 GitHub · ★8 / 4 forks

Async MCP server with job queue: design-system extractor for Cursor

Separate submit from result: async job queue with poll-by-ID, and 404-after-completion as a deliberate stateless trade-off.

CTO Head of AI Architect Tech Lead

Problem

A naive MCP server does its work synchronously: the agent calls a tool and waits for the response. Fine for fast operations. For long ones — browser automation site analysis, ML inference, multi-page crawling — it blocks: timeouts, no horizontal scaling, a single heavy request holds the connection.

Methodology

Separate submit from result: async job queue with poll-by-ID.

  1. 1. Submit returns a job ID immediately, without blocking. Status is polled with a separate call.
  2. 2. Playwright in a container does the actual site analysis (design tokens: colors, typography, spacing, components) — isolated, with node_modules from the host explicitly excluded from the image.
  3. 3. Redis under the queue — task persistence and concurrency (max 5 simultaneous jobs).
  4. 4. MCP server on port 3001, multi-protocol: HTTP / HTTPS / SSH tunnel — flexibility for different deployment scenarios and Cursor integration.
# docker-compose.yml (production services)
services:
  redis:
    image: redis:7-alpine
    container_name: design-system-redis
    volumes:
      - redis-data:/data          # queue persistence across restarts
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

  api:
    build: .
    container_name: design-system-api
    ports:
      - "${PORT:-3000}:3000"
      - "${MCP_PORT:-3001}:3001"  # REST API + MCP on separate ports
    environment:
      - MAX_CONCURRENT_ANALYSES=${MAX_CONCURRENT_ANALYSES:-5}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - MCP_PROTOCOL=${MCP_PROTOCOL:-http}
    depends_on:
      redis:
        condition: service_healthy

volumes:
  redis-data:

Job lifecycle — and why 404 is a contract, not a bug:

POST /analyze        → 202 {"job_id": "abc123"}
GET  /job/abc123     → 200 {"status": "running"}
GET  /job/abc123     → 200 {"status": "done", "result": {...}}
GET  /job/abc123     → 404                  # status deleted immediately after delivery

The client must retrieve the result on the first done response. A second request is already 404: the server doesn't accumulate state. This is a deliberate stateless trade-off — the consumer is designed around it, not against it.

Artifact

github.com/dobryakov/design-system-mcp (TypeScript, Docker/Compose, Jest, Playwright E2E). ★8 / 4 forks — the only repo in the series with community validation.

Series signature

Where it breaks

For whom and why

The "async submit + poll + deliberate stateless trade-off" pattern is architectural, not tutorial-level. ★8 provides independent confirmation that the technical audience finds this interesting. The strongest externally validated entry in the series.

Building MCP-based tooling for your engineering team?

Async patterns, stateless design trade-offs, and production-grade MCP server architecture — with a working artifact, not a tutorial.

Email me

Other breakdowns

An engineering breakdown series: real task → methodology → working artifact → honest breakdown of where it fails.

Back to series →