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:
- Authentication — Verify API key, load agent identity
- Authorization — Check scopes against tool's required scopes
- Rate limiting — Decrement quota in Upstash Redis
- Validation — Parse params with tool's Zod schema
- Execution — Call tool's
execute()method - Audit logging — Record action in
agent_audit_logs - 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 backendUPSTASH_REDIS_REST_TOKEN— Redis authANTHROPIC_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¶
- Authentication — API keys, scopes, IAM tables
- Available Tools — Complete tool reference
- Rate Limits — Tiered rate limits and custom policies