Skip to content

MCP Server Architecture

!!! info "TL;DR" The MCP server uses HTTP + SSE transport with a tool registry pattern. Deployed on Vercel as Next.js route handlers. Health check at /api/mcp/health.

Transport Layer

The MCP server implements the Model Context Protocol over HTTP with Server-Sent Events (SSE) for streaming responses. This approach is Vercel-compatible and works without persistent WebSocket connections.

Endpoints

Endpoint Method Purpose
/api/mcp/tools POST Execute tool calls
/api/mcp/stream GET SSE stream for long-running operations
/api/mcp/subscribe POST Subscribe to realtime events
/api/mcp/health GET Health check and server info

Request Format

Tool calls use JSON-RPC 2.0:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_survey",
    "arguments": {
      "title": "Q1 Product Feedback",
      "questions": [
        {
          "type": "multiple_choice",
          "text": "What feature would you like next?",
          "options": ["Dark mode", "API access", "Mobile app"]
        }
      ]
    }
  }
}

Response Format

Successful responses:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "survey_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "draft",
    "url": "https://canviq.app/surveys/550e8400-e29b-41d4-a716-446655440000"
  }
}

Error responses:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid request",
    "data": {
      "validation_errors": ["questions must be an array"]
    }
  }
}

Tool Registry Pattern

The server uses a plugin-style registry for tools. Each tool is a class that implements the McpTool interface:

interface McpTool {
  name: string
  description: string
  requiredScopes: string[]
  schema: z.ZodSchema
  execute(params: unknown, context: ExecutionContext): Promise<unknown>
}

Tools register themselves at startup:

// lib/mcp/tools/index.ts
export const toolRegistry = new Map<string, McpTool>([
  ['create_survey', new CreateSurveyTool()],
  ['update_survey', new UpdateSurveyTool()],
  ['list_surveys', new ListSurveysTool()],
  // ... more tools
])

This pattern makes adding new tools straightforward. Drop a file in lib/mcp/tools/, export the class, and it's automatically available.

Execution Context

Every tool execution receives a context object:

interface ExecutionContext {
  agentId: string
  requestId: string
  scopes: string[]
  rateLimit: RateLimitInfo
  metadata: Record<string, unknown>
}

This context flows through the execution pipeline:

  1. Authentication — Verify API key, load agent identity
  2. Authorization — Check scopes against tool's required scopes
  3. Rate limiting — Decrement quota in Upstash Redis
  4. Validation — Parse params with tool's Zod schema
  5. Execution — Call tool's execute() method
  6. Audit logging — Record action in agent_audit_logs
  7. Response — Return result or error

Streaming Responses

Long-running operations use SSE streaming. The client calls /api/mcp/tools with stream: true, then opens an SSE connection to /api/mcp/stream/:requestId.

Server sends events as the operation progresses:

event: progress
data: {"step": "creating_survey", "percent": 30}

event: progress
data: {"step": "generating_questions", "percent": 60}

event: result
data: {"survey_id": "...", "url": "..."}

Health Check

The /api/mcp/health endpoint returns server status:

{
  "status": "healthy",
  "version": "1.0.0",
  "transport": "http+sse",
  "tools": 12,
  "uptime": 86400,
  "rate_limit_backend": "upstash-redis"
}

Use this for monitoring and integration tests.

Deployment

The MCP server deploys as part of the main Next.js app on Vercel. No separate service required. Environment variables:

  • UPSTASH_REDIS_REST_URL — Rate limiting backend
  • UPSTASH_REDIS_REST_TOKEN — Redis auth
  • ANTHROPIC_API_KEY — Claude for NL parsing and sentiment

Edge runtime is not used. MCP tools often call Supabase which requires Node.js runtime.

What's Next