Advanced MCP Server Patterns: Professional Development Practices
This comprehensive guide explores advanced architectural patterns and professional development practices for building production-ready Model Context Protocol (MCP) servers. Learn how to implement robust error handling, efficient resource management, and scalable architectures that meet enterprise requirements.
Table of Contents
- Advanced Architecture Patterns
- Error Handling and Recovery
- Resource Management and Performance
- Security Implementation Patterns
- State Management and Persistence
- Monitoring and Observability
- Testing Strategies
- Production Deployment Patterns
Advanced Architecture Patterns
Server Factory Pattern
Implement a factory pattern for creating MCP servers with consistent configuration and middleware:
interface MCPServerConfig {
name: string;
version: string;
capabilities: ServerCapabilities;
middleware?: Middleware[];
}
class MCPServerFactory {
static create(config: MCPServerConfig): Server {
const server = new Server(config);
// Apply common middleware
config.middleware?.forEach(middleware => {
server.use(middleware);
});
return server;
}
}
Plugin Architecture Pattern
Design extensible servers using a plugin system:
interface MCPPlugin {
name: string;
version: string;
register(server: Server): void;
unregister?(server: Server): void;
}
class PluginManager {
private plugins: Map<string, MCPPlugin> = new Map();
register(plugin: MCPPlugin, server: Server): void {
this.plugins.set(plugin.name, plugin);
plugin.register(server);
}
unregister(pluginName: string, server: Server): void {
const plugin = this.plugins.get(pluginName);
if (plugin?.unregister) {
plugin.unregister(server);
}
this.plugins.delete(pluginName);
}
}
Resource Pool Pattern
Manage expensive resources efficiently:
class ResourcePool<T> {
private available: T[] = [];
private inUse: Set<T> = new Set();
private factory: () => Promise<T>;
private maxSize: number;
constructor(factory: () => Promise<T>, maxSize: number = 10) {
this.factory = factory;
this.maxSize = maxSize;
}
async acquire(): Promise<T> {
if (this.available.length > 0) {
const resource = this.available.pop()!;
this.inUse.add(resource);
return resource;
}
if (this.inUse.size < this.maxSize) {
const resource = await this.factory();
this.inUse.add(resource);
return resource;
}
throw new Error('Resource pool exhausted');
}
release(resource: T): void {
this.inUse.delete(resource);
this.available.push(resource);
}
}
Error Handling and Recovery
Structured Error Response Pattern
Implement consistent error handling across your MCP server:
enum MCPErrorType {
VALIDATION = 'validation',
AUTHORIZATION = 'authorization',
RESOURCE_NOT_FOUND = 'resource_not_found',
INTERNAL_SERVER_ERROR = 'internal_server_error',
RATE_LIMIT_EXCEEDED = 'rate_limit_exceeded'
}
interface MCPError {
type: MCPErrorType;
message: string;
code: number;
details?: Record<string, any>;
timestamp: string;
requestId?: string;
}
class MCPErrorHandler {
static createError(
type: MCPErrorType,
message: string,
details?: Record<string, any>
): MCPError {
return {
type,
message,
code: this.getErrorCode(type),
details,
timestamp: new Date().toISOString(),
requestId: this.generateRequestId()
};
}
static handle(error: unknown): MCPError {
if (error instanceof ValidationError) {
return this.createError(
MCPErrorType.VALIDATION,
error.message,
{ field: error.field }
);
}
// Handle other error types...
return this.createError(
MCPErrorType.INTERNAL_SERVER_ERROR,
'An unexpected error occurred'
);
}
}
Circuit Breaker Pattern
Implement circuit breakers for external service calls:
enum CircuitState {
CLOSED,
OPEN,
HALF_OPEN
}
class CircuitBreaker {
private state: CircuitState = CircuitState.CLOSED;
private failureCount: number = 0;
private lastFailureTime: number = 0;
private successCount: number = 0;
constructor(
private failureThreshold: number = 5,
private recoveryTimeout: number = 60000,
private successThreshold: number = 3
) {}
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === CircuitState.OPEN) {
if (Date.now() - this.lastFailureTime < this.recoveryTimeout) {
throw new Error('Circuit breaker is open');
}
this.state = CircuitState.HALF_OPEN;
this.successCount = 0;
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
if (this.state === CircuitState.HALF_OPEN) {
this.successCount++;
if (this.successCount >= this.successThreshold) {
this.state = CircuitState.CLOSED;
this.failureCount = 0;
}
}
}
private onFailure(): void {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.state = CircuitState.OPEN;
}
}
}
Resource Management and Performance
Request Deduplication Pattern
Prevent duplicate expensive operations:
class RequestDeduplicator {
private pendingRequests: Map<string, Promise<any>> = new Map();
async execute<T>(
key: string,
operation: () => Promise<T>,
ttl: number = 5000
): Promise<T> {
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key) as Promise<T>;
}
const promise = operation();
this.pendingRequests.set(key, promise);
// Clean up after completion or timeout
setTimeout(() => {
this.pendingRequests.delete(key);
}, ttl);
try {
const result = await promise;
return result;
} catch (error) {
this.pendingRequests.delete(key);
throw error;
}
}
}
Caching Strategy Pattern
Implement multi-level caching:
interface CacheProvider {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, ttl?: number): Promise<void>;
delete(key: string): Promise<void>;
}
class MultiLevelCache {
constructor(
private l1Cache: CacheProvider, // Memory cache
private l2Cache: CacheProvider // Redis/external cache
) {}
async get<T>(key: string): Promise<T | null> {
// Try L1 cache first
let value = await this.l1Cache.get<T>(key);
if (value) return value;
// Try L2 cache
value = await this.l2Cache.get<T>(key);
if (value) {
// Promote to L1 cache
await this.l1Cache.set(key, value, 300); // 5 minutes
return value;
}
return null;
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
await Promise.all([
this.l1Cache.set(key, value, ttl),
this.l2Cache.set(key, value, ttl)
]);
}
}
Rate Limiting Pattern
Implement sophisticated rate limiting:
interface RateLimiter {
checkLimit(identifier: string): Promise<boolean>;
getRemainingRequests(identifier: string): Promise<number>;
}
class SlidingWindowRateLimiter implements RateLimiter {
private windows: Map<string, number[]> = new Map();
constructor(
private maxRequests: number,
private windowSizeMs: number
) {}
async checkLimit(identifier: string): Promise<boolean> {
const now = Date.now();
const windowStart = now - this.windowSizeMs;
let requests = this.windows.get(identifier) || [];
// Remove expired requests
requests = requests.filter(timestamp => timestamp > windowStart);
if (requests.length >= this.maxRequests) {
return false;
}
// Add current request
requests.push(now);
this.windows.set(identifier, requests);
return true;
}
async getRemainingRequests(identifier: string): Promise<number> {
const now = Date.now();
const windowStart = now - this.windowSizeMs;
const requests = this.windows.get(identifier) || [];
const validRequests = requests.filter(timestamp => timestamp > windowStart);
return Math.max(0, this.maxRequests - validRequests.length);
}
}
Security Implementation Patterns
Request Validation Pattern
Implement comprehensive input validation:
import { z } from 'zod';
const ResourceRequestSchema = z.object({
uri: z.string().url().refine(uri => {
// Only allow specific protocols
return uri.startsWith('file://') || uri.startsWith('https://');
}),
parameters: z.record(z.string()).optional(),
metadata: z.object({
requestId: z.string().uuid(),
timestamp: z.string().datetime()
})
});
class RequestValidator {
static validate<T>(schema: z.ZodSchema<T>, data: unknown): T {
try {
return schema.parse(data);
} catch (error) {
if (error instanceof z.ZodError) {
throw new ValidationError('Invalid request format', error.errors);
}
throw error;
}
}
}
Authorization Middleware Pattern
Implement role-based access control:
enum Permission {
READ_RESOURCES = 'read:resources',
WRITE_RESOURCES = 'write:resources',
EXECUTE_TOOLS = 'execute:tools',
ADMIN_ACCESS = 'admin:access'
}
interface AuthContext {
userId: string;
roles: string[];
permissions: Permission[];
sessionId: string;
}
class AuthorizationMiddleware {
constructor(private requiredPermissions: Permission[]) {}
async authorize(context: AuthContext, resource?: string): Promise<boolean> {
// Check if user has required permissions
const hasPermissions = this.requiredPermissions.every(
permission => context.permissions.includes(permission)
);
if (!hasPermissions) {
return false;
}
// Resource-specific authorization
if (resource) {
return this.checkResourceAccess(context, resource);
}
return true;
}
private async checkResourceAccess(
context: AuthContext,
resource: string
): Promise<boolean> {
// Implement resource-specific access control
// This could involve checking ownership, group membership, etc.
return true;
}
}
State Management and Persistence
Event Sourcing Pattern
Implement event sourcing for audit trails and state reconstruction:
interface Event {
id: string;
type: string;
aggregateId: string;
data: Record<string, any>;
timestamp: Date;
version: number;
}
class EventStore {
private events: Map<string, Event[]> = new Map();
async appendEvent(aggregateId: string, event: Omit<Event, 'id' | 'timestamp'>): Promise<void> {
const events = this.events.get(aggregateId) || [];
const newEvent: Event = {
...event,
id: this.generateId(),
timestamp: new Date(),
version: events.length + 1
};
events.push(newEvent);
this.events.set(aggregateId, events);
}
async getEvents(aggregateId: string, fromVersion?: number): Promise<Event[]> {
const events = this.events.get(aggregateId) || [];
if (fromVersion) {
return events.filter(event => event.version >= fromVersion);
}
return events;
}
private generateId(): string {
return Math.random().toString(36).substring(2, 15);
}
}
Snapshot Pattern
Implement snapshots for performance optimization:
interface Snapshot<T> {
aggregateId: string;
data: T;
version: number;
timestamp: Date;
}
class SnapshotStore<T> {
private snapshots: Map<string, Snapshot<T>> = new Map();
async saveSnapshot(aggregateId: string, data: T, version: number): Promise<void> {
const snapshot: Snapshot<T> = {
aggregateId,
data,
version,
timestamp: new Date()
};
this.snapshots.set(aggregateId, snapshot);
}
async getSnapshot(aggregateId: string): Promise<Snapshot<T> | null> {
return this.snapshots.get(aggregateId) || null;
}
async reconstructState(aggregateId: string): Promise<T | null> {
const snapshot = await this.getSnapshot(aggregateId);
if (!snapshot) {
return null;
}
// Apply events since snapshot
const eventStore = new EventStore();
const recentEvents = await eventStore.getEvents(aggregateId, snapshot.version + 1);
let state = snapshot.data;
for (const event of recentEvents) {
state = this.applyEvent(state, event);
}
return state;
}
private applyEvent(state: T, event: Event): T {
// Implement event application logic
return state;
}
}
Monitoring and Observability
Metrics Collection Pattern
Implement comprehensive metrics collection:
enum MetricType {
COUNTER = 'counter',
GAUGE = 'gauge',
HISTOGRAM = 'histogram'
}
interface Metric {
name: string;
type: MetricType;
value: number;
labels: Record<string, string>;
timestamp: Date;
}
class MetricsCollector {
private metrics: Metric[] = [];
counter(name: string, labels: Record<string, string> = {}): void {
this.recordMetric(name, MetricType.COUNTER, 1, labels);
}
gauge(name: string, value: number, labels: Record<string, string> = {}): void {
this.recordMetric(name, MetricType.GAUGE, value, labels);
}
histogram(name: string, value: number, labels: Record<string, string> = {}): void {
this.recordMetric(name, MetricType.HISTOGRAM, value, labels);
}
private recordMetric(
name: string,
type: MetricType,
value: number,
labels: Record<string, string>
): void {
this.metrics.push({
name,
type,
value,
labels,
timestamp: new Date()
});
}
getMetrics(): Metric[] {
return [...this.metrics];
}
clearMetrics(): void {
this.metrics = [];
}
}
Distributed Tracing Pattern
Implement distributed tracing for complex operations:
interface Span {
traceId: string;
spanId: string;
parentSpanId?: string;
operationName: string;
startTime: Date;
endTime?: Date;
tags: Record<string, any>;
logs: Array<{ timestamp: Date; message: string; level: string }>;
}
class DistributedTracer {
private activeSpans: Map<string, Span> = new Map();
startSpan(operationName: string, parentSpanId?: string): string {
const span: Span = {
traceId: parentSpanId ? this.getTraceId(parentSpanId) : this.generateId(),
spanId: this.generateId(),
parentSpanId,
operationName,
startTime: new Date(),
tags: {},
logs: []
};
this.activeSpans.set(span.spanId, span);
return span.spanId;
}
finishSpan(spanId: string): void {
const span = this.activeSpans.get(spanId);
if (span) {
span.endTime = new Date();
}
}
addTag(spanId: string, key: string, value: any): void {
const span = this.activeSpans.get(spanId);
if (span) {
span.tags[key] = value;
}
}
log(spanId: string, message: string, level: string = 'info'): void {
const span = this.activeSpans.get(spanId);
if (span) {
span.logs.push({
timestamp: new Date(),
message,
level
});
}
}
private getTraceId(spanId: string): string {
const span = this.activeSpans.get(spanId);
return span ? span.traceId : this.generateId();
}
private generateId(): string {
return Math.random().toString(36).substring(2, 15);
}
}
Testing Strategies
Test Double Pattern for MCP
Create test doubles for MCP components:
class MockMCPClient implements MCPClient {
private responses: Map<string, any> = new Map();
private callLog: Array<{ method: string; params: any }> = [];
setResponse(method: string, response: any): void {
this.responses.set(method, response);
}
async callTool(name: string, arguments_: Record<string, any>): Promise<any> {
this.callLog.push({ method: 'callTool', params: { name, arguments_ } });
return this.responses.get('callTool') || { success: true };
}
async getResource(uri: string): Promise<any> {
this.callLog.push({ method: 'getResource', params: { uri } });
return this.responses.get('getResource') || { content: 'mock content' };
}
getCallLog(): Array<{ method: string; params: any }> {
return [...this.callLog];
}
clearCallLog(): void {
this.callLog = [];
}
}
Integration Test Pattern
Implement comprehensive integration testing:
class MCPIntegrationTest {
private server: Server;
private client: MCPClient;
async setup(): Promise<void> {
// Start server
this.server = new Server({
name: 'test-server',
version: '1.0.0'
});
await this.server.start();
// Connect client
this.client = new MCPClient({
serverUrl: 'http://localhost:3000'
});
await this.client.connect();
}
async teardown(): Promise<void> {
await this.client?.disconnect();
await this.server?.stop();
}
async testResourceAccess(): Promise<void> {
const resource = await this.client.getResource('test://resource/1');
expect(resource).toBeDefined();
expect(resource.content).toContain('expected content');
}
async testToolExecution(): Promise<void> {
const result = await this.client.callTool('test-tool', {
param1: 'value1'
});
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
}
}
Production Deployment Patterns
Health Check Pattern
Implement comprehensive health checks:
enum HealthStatus {
HEALTHY = 'healthy',
DEGRADED = 'degraded',
UNHEALTHY = 'unhealthy'
}
interface HealthCheck {
name: string;
status: HealthStatus;
message?: string;
responseTime: number;
timestamp: Date;
}
class HealthCheckManager {
private checks: Map<string, () => Promise<HealthCheck>> = new Map();
registerCheck(name: string, check: () => Promise<HealthCheck>): void {
this.checks.set(name, check);
}
async runAllChecks(): Promise<HealthCheck[]> {
const results: HealthCheck[] = [];
for (const [name, check] of this.checks) {
try {
const result = await check();
results.push(result);
} catch (error) {
results.push({
name,
status: HealthStatus.UNHEALTHY,
message: error instanceof Error ? error.message : 'Unknown error',
responseTime: 0,
timestamp: new Date()
});
}
}
return results;
}
async getOverallHealth(): Promise<HealthStatus> {
const checks = await this.runAllChecks();
if (checks.some(check => check.status === HealthStatus.UNHEALTHY)) {
return HealthStatus.UNHEALTHY;
}
if (checks.some(check => check.status === HealthStatus.DEGRADED)) {
return HealthStatus.DEGRADED;
}
return HealthStatus.HEALTHY;
}
}
Graceful Shutdown Pattern
Implement graceful shutdown handling:
class GracefulShutdownManager {
private shutdownHandlers: Array<() => Promise<void>> = [];
private isShuttingDown: boolean = false;
constructor() {
this.setupSignalHandlers();
}
addShutdownHandler(handler: () => Promise<void>): void {
this.shutdownHandlers.push(handler);
}
private setupSignalHandlers(): void {
process.on('SIGTERM', () => this.shutdown('SIGTERM'));
process.on('SIGINT', () => this.shutdown('SIGINT'));
process.on('SIGQUIT', () => this.shutdown('SIGQUIT'));
}
private async shutdown(signal: string): Promise<void> {
if (this.isShuttingDown) {
return;
}
this.isShuttingDown = true;
console.log(`Received ${signal}, starting graceful shutdown...`);
try {
await Promise.all(
this.shutdownHandlers.map(handler =>
Promise.race([
handler(),
this.timeout(30000) // 30 second timeout
])
)
);
console.log('Graceful shutdown completed');
process.exit(0);
} catch (error) {
console.error('Error during shutdown:', error);
process.exit(1);
}
}
private timeout(ms: number): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('Shutdown timeout')), ms);
});
}
}
Next Steps
These advanced patterns provide the foundation for building enterprise-grade MCP servers. Consider these areas for further exploration:
- Microservices Architecture: Split MCP functionality across multiple services
- Event-Driven Architecture: Implement async communication patterns
- Multi-Tenant Support: Add tenant isolation and resource management
- Advanced Security: Implement OAuth 2.0, JWT tokens, and encryption
- Performance Optimization: Add connection pooling and query optimization
- Monitoring Integration: Connect with Prometheus, Grafana, and ELK stack
Remember that implementing these patterns should be driven by actual requirements rather than premature optimization. Start with simpler implementations and evolve toward these patterns as your system grows and requirements become clearer.