206 lines
8.5 KiB
Markdown
206 lines
8.5 KiB
Markdown
# 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/`:
|
|
|
|
```typescript
|
|
// 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.
|