Files
mcpctl/.taskmaster/tasks/task_003.md
2026-02-21 03:10:39 +00:00

8.5 KiB

Task ID: 3

Title: Implement mcpd Core Server Framework

Status: pending

Dependencies: 1, 2

Priority: high

Description: Build the mcpd daemon server with Express/Fastify, including middleware for authentication, logging, and error handling. Design for horizontal scalability.

Details:

Create mcpd server in src/mcpd/src/:

// server.ts
import Fastify from 'fastify';
import { PrismaClient } from '@prisma/client';

const app = Fastify({ logger: true });
const prisma = new PrismaClient();

// Middleware
app.register(require('@fastify/cors'));
app.register(require('@fastify/helmet'));
app.register(require('@fastify/rate-limit'), { max: 100, timeWindow: '1 minute' });

// Health check for load balancers
app.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));

// Auth middleware
app.addHook('preHandler', async (request, reply) => {
  if (request.url === '/health') return;
  const token = request.headers.authorization?.replace('Bearer ', '');
  if (!token) return reply.status(401).send({ error: 'Unauthorized' });
  // Validate token against session table
});

// Audit logging middleware
app.addHook('onResponse', async (request, reply) => {
  await prisma.auditLog.create({
    data: {
      action: request.method,
      resource: request.url,
      details: { statusCode: reply.statusCode },
      userId: request.user?.id
    }
  });
});

Design principles:

  • Stateless: All state in PostgreSQL, no in-memory session storage
  • Scalable: Can run multiple instances behind load balancer
  • Configurable via environment variables
  • Graceful shutdown handling

Test Strategy:

Unit test middleware functions. Integration test health endpoint. Load test with multiple concurrent requests. Verify statelessness by running two instances and alternating requests.

Subtasks

3.1. Set up mcpd package structure with clean architecture layers and TDD infrastructure

Status: pending
Dependencies: None

Create the src/mcpd directory structure following clean architecture principles with separate layers for routes, controllers, services, and repositories, along with Vitest test configuration.

Details:

Create src/mcpd/src/ directory structure with the following layers:

  • routes/ - HTTP route definitions (thin layer, delegates to controllers)
  • controllers/ - Request/response handling, input validation
  • services/ - Business logic, orchestrates repositories
  • repositories/ - Data access layer, Prisma abstraction
  • middleware/ - Auth, logging, error handling, rate limiting
  • config/ - Environment configuration with Zod validation
  • types/ - TypeScript interfaces for dependency injection
  • utils/ - Utility functions (graceful shutdown, health checks)

Create src/mcpd/tests/ with matching structure:

  • unit/ (routes, controllers, services, repositories, middleware)
  • integration/ (API endpoint tests)
  • fixtures/ (mock data, Prisma mock setup)

Set up vitest.config.ts extending root config with mcpd-specific settings. Create test-utils.ts with Prisma mock factory and Fastify test helpers. Install dependencies: fastify, @fastify/cors, @fastify/helmet, @fastify/rate-limit, zod, pino. DevDependencies: vitest, @vitest/coverage-v8, supertest.

3.2. Implement Fastify server core with health endpoint and database connectivity verification

Status: pending
Dependencies: 3.1

Create the core Fastify server with health check endpoint that verifies PostgreSQL database connectivity, environment configuration validation, and server lifecycle management.

Details:

Create src/mcpd/src/server.ts with Fastify instance factory function createServer(config: ServerConfig) for testability via dependency injection. Implement:

  • config/env.ts: Zod schema for environment variables (DATABASE_URL, PORT, NODE_ENV, LOG_LEVEL)
  • config/index.ts: loadConfig() function that validates env with Zod
  • utils/health.ts: checkDatabaseConnectivity(prisma) function
  • routes/health.ts: GET /health endpoint returning { status: 'ok' | 'degraded', timestamp: ISO8601, db: 'connected' | 'disconnected' }

Server requirements:

  • Fastify with pino logger enabled (configurable log level)
  • Health endpoint bypasses auth middleware
  • Health endpoint checks actual DB connectivity via prisma.$queryRaw
  • Server does NOT start if DATABASE_URL is missing (fail fast)
  • Export createServer() and startServer() separately for testing

Write TDD tests FIRST in tests/unit/routes/health.test.ts and tests/unit/config/env.test.ts before implementing.

3.3. Implement authentication middleware with JWT validation and session management

Status: pending
Dependencies: 3.2

Create authentication preHandler hook that validates Bearer tokens against the Session table in PostgreSQL, with proper error responses and request decoration for downstream handlers.

Details:

Create src/mcpd/src/middleware/auth.ts with:

  • authMiddleware(prisma: PrismaClient) factory function (dependency injection)
  • Fastify preHandler hook implementation
  • Extract Bearer token from Authorization header
  • Validate token exists and format is correct
  • Query Session table: find by token, check expiresAt > now()
  • Query User by session.userId for request decoration
  • Decorate request with user: { id, email, name } via fastify.decorateRequest
  • Return 401 Unauthorized with { error: 'Unauthorized', code: 'TOKEN_REQUIRED' } for missing token
  • Return 401 with { error: 'Unauthorized', code: 'TOKEN_EXPIRED' } for expired session
  • Return 401 with { error: 'Unauthorized', code: 'TOKEN_INVALID' } for invalid token

Create types/fastify.d.ts with FastifyRequest augmentation for user property.

Write unit tests in tests/unit/middleware/auth.test.ts with mocked Prisma client before implementation.

3.4. Implement security middleware stack with CORS, Helmet, rate limiting, and input sanitization

Status: pending
Dependencies: 3.2

Configure and register security middleware including CORS policy, Helmet security headers, rate limiting, and create input sanitization utilities to prevent injection attacks.

Details:

Create src/mcpd/src/middleware/security.ts with:

  • registerSecurityPlugins(app: FastifyInstance, config: SecurityConfig) function
  • CORS configuration: configurable origins (default: same-origin for production, * for development), credentials support, allowed methods/headers
  • Helmet configuration: contentSecurityPolicy, hsts (enabled in production), noSniff, frameguard
  • Rate limiting: 100 requests per minute default, configurable via env, different limits for auth endpoints (stricter)

Create src/mcpd/src/utils/sanitize.ts:

  • sanitizeInput(input: unknown): sanitized value
  • stripHtmlTags(), escapeHtml() for XSS prevention
  • Validate JSON input doesn't exceed size limits

Create src/mcpd/src/middleware/validate.ts:

  • createValidationMiddleware(schema: ZodSchema) factory
  • Validates request.body against Zod schema
  • Returns 400 Bad Request with Zod errors formatted

Document security decisions in src/mcpd/SECURITY.md with rationale for each configuration choice.

3.5. Implement error handling, audit logging middleware, and graceful shutdown with comprehensive tests

Status: pending
Dependencies: 3.2, 3.3, 3.4

Create global error handler, audit logging onResponse hook that records all operations to database, and graceful shutdown handling with connection draining and proper signal handling.

Details:

Create src/mcpd/src/middleware/error-handler.ts:

  • Global Fastify error handler via setErrorHandler
  • Handle Zod validation errors -> 400 Bad Request
  • Handle Prisma errors (P2002 unique, P2025 not found) -> appropriate HTTP codes
  • Handle custom application errors with error codes
  • Log errors with pino, include stack trace in development only
  • Never expose internal errors to clients in production

Create src/mcpd/src/middleware/audit.ts:

  • auditMiddleware(prisma: PrismaClient, auditLogger: AuditLogger) factory
  • Fastify onResponse hook
  • Create AuditLog record with: userId (from request.user), action (HTTP method), resource (URL), details ({ statusCode, responseTime, ip })
  • Skip audit logging for /health endpoint
  • Async write - don't block response
  • Handle audit write failures gracefully (log warning, don't fail request)

Create src/mcpd/src/utils/shutdown.ts:

  • setupGracefulShutdown(app: FastifyInstance, prisma: PrismaClient) function
  • Handle SIGTERM, SIGINT signals
  • Stop accepting new connections
  • Wait for in-flight requests (configurable timeout, default 30s)
  • Disconnect Prisma client
  • Exit with appropriate code

Create services/audit-logger.ts interface that Task 14 will implement.