Chapter 7: Server Configuration and Lifecycle

Haiyue
37min

Chapter 7: Server Configuration and Lifecycle

Learning Objectives

  1. Master the initialization and configuration of the MCP Server
  2. Learn the connection establishment and handshake process
  3. Understand the lifecycle management of the Server
  4. Master graceful shutdown and resource cleanup
  5. Learn about Server monitoring and health checks

1. MCP Server Architecture Overview

1.1 Server Component Structure

interface ServerArchitecture {
  core: {
    server: Server;           // Core server instance
    transport: Transport;     // Transport layer
    protocol: Protocol;       // Protocol handler
  };
  handlers: {
    tools: ToolHandler[];     // Tool handlers
    resources: ResourceHandler[]; // Resource handlers
    prompts: PromptHandler[]; // Prompt handlers
  };
  lifecycle: {
    initializer: Initializer; // Initializer
    monitor: HealthMonitor;   // Health monitor
    cleanup: CleanupManager;  // Cleanup manager
  };
}

1.2 Server State Management

enum ServerState {
  INITIALIZING = "initializing",
  CONNECTING = "connecting", 
  CONNECTED = "connected",
  READY = "ready",
  SHUTTING_DOWN = "shutting_down",
  SHUTDOWN = "shutdown",
  ERROR = "error"
}

interface ServerStatus {
  state: ServerState;
  startTime: Date;
  lastActivity: Date;
  connectionCount: number;
  errors: Error[];
}

2. Server Initialization and Configuration

2.1 Configuration Management System

interface ServerConfig {
  server: {
    name: string;
    version: string;
    description?: string;
    timeout?: number;
    maxConnections?: number;
  };
  capabilities: {
    tools?: {
      listChanged?: boolean;
    };
    resources?: {
      subscribe?: boolean;
      listChanged?: boolean;
    };
    prompts?: {};
    logging?: {
      level?: "debug" | "info" | "warn" | "error";
    };
  };
  transport: {
    type: "stdio" | "sse" | "websocket";
    options?: Record<string, any>;
  };
  features?: {
    authentication?: boolean;
    compression?: boolean;
    encryption?: boolean;
  };
}

class ConfigManager {
  private config: ServerConfig;
  private validators: Map<string, (value: any) => boolean> = new Map();

  constructor(config: Partial<ServerConfig>) {
    this.config = this.mergeWithDefaults(config);
    this.setupValidators();
    this.validateConfig();
  }

  private mergeWithDefaults(config: Partial<ServerConfig>): ServerConfig {
    const defaults: ServerConfig = {
      server: {
        name: "mcp-server",
        version: "1.0.0",
        timeout: 30000,
        maxConnections: 10
      },
      capabilities: {
        tools: {},
        resources: {},
        prompts: {},
        logging: { level: "info" }
      },
      transport: {
        type: "stdio"
      }
    };

    return this.deepMerge(defaults, config);
  }

  private deepMerge(target: any, source: any): any {
    const result = { ...target };
    
    for (const key in source) {
      if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
        result[key] = this.deepMerge(target[key] || {}, source[key]);
      } else {
        result[key] = source[key];
      }
    }
    
    return result;
  }

  private setupValidators() {
    this.validators.set("server.timeout", (value) => 
      typeof value === "number" && value > 0 && value <= 300000);
    
    this.validators.set("server.maxConnections", (value) =>
      typeof value === "number" && value > 0 && value <= 1000);
    
    this.validators.set("capabilities.logging.level", (value) =>
      ["debug", "info", "warn", "error"].includes(value));
  }

  private validateConfig() {
    for (const [path, validator] of this.validators) {
      const value = this.getValueByPath(this.config, path);
      if (value !== undefined && !validator(value)) {
        throw new Error(`Invalid configuration value for ${path}: ${value}`);
      }
    }
  }

  private getValueByPath(obj: any, path: string): any {
    return path.split('.').reduce((current, key) => current?.[key], obj);
  }

  getConfig(): ServerConfig {
    return this.config;
  }

  updateConfig(updates: Partial<ServerConfig>) {
    this.config = this.deepMerge(this.config, updates);
    this.validateConfig();
  }
}

2.2 Server Initializer

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

class ServerInitializer {
  private configManager: ConfigManager;
  private server: Server | null = null;
  private transport: any = null;
  private status: ServerStatus;

  constructor(config: Partial<ServerConfig>) {
    this.configManager = new ConfigManager(config);
    this.status = {
      state: ServerState.INITIALIZING,
      startTime: new Date(),
      lastActivity: new Date(),
      connectionCount: 0,
      errors: []
    };
  }

  async initialize(): Promise<Server> {
    try {
      this.updateState(ServerState.INITIALIZING);
      
      const config = this.configManager.getConfig();
      
      // Create server instance
      this.server = new Server(
        {
          name: config.server.name,
          version: config.server.version,
          description: config.server.description
        },
        {
          capabilities: config.capabilities
        }
      );

      // Set up error handling
      this.setupErrorHandling();
      
      // Set up request interceptors
      this.setupRequestInterceptors();
      
      // Create transport layer
      this.transport = this.createTransport(config.transport);
      
      console.error(`Server initialized: ${config.server.name} v${config.server.version}`);
      
      return this.server;
      
    } catch (error) {
      this.handleError(error as Error);
      throw error;
    }
  }

  private createTransport(transportConfig: ServerConfig['transport']) {
    switch (transportConfig.type) {
      case "stdio":
        return new StdioServerTransport();
      // Implementation for other transport types
      default:
        throw new Error(`Unsupported transport type: ${transportConfig.type}`);
    }
  }

  private setupErrorHandling() {
    if (!this.server) return;

    this.server.onerror = (error) => {
      this.handleError(error);
    };

    // Global error handling
    process.on('uncaughtException', (error) => {
      console.error('Uncaught Exception:', error);
      this.handleError(error);
      this.shutdown();
    });

    process.on('unhandledRejection', (reason, promise) => {
      console.error('Unhandled Rejection at:', promise, 'reason:', reason);
      this.handleError(new Error(`Unhandled Rejection: ${reason}`));
    });
  }

  private setupRequestInterceptors() {
    if (!this.server) return;

    // Pre-request interception
    const originalSetRequestHandler = this.server.setRequestHandler.bind(this.server);
    this.server.setRequestHandler = (method: string, handler: any) => {
      const wrappedHandler = async (request: any) => {
        this.updateActivity();
        const startTime = Date.now();
        
        try {
          const result = await handler(request);
          const duration = Date.now() - startTime;
          
          console.error(`Request completed: ${method} (${duration}ms)`);
          return result;
          
        } catch (error) {
          const duration = Date.now() - startTime;
          console.error(`Request failed: ${method} (${duration}ms) - ${error.message}`);
          throw error;
        }
      };
      
      return originalSetRequestHandler(method, wrappedHandler);
    };
  }

  async connect(): Promise<void> {
    if (!this.server || !this.transport) {
      throw new Error("Server not initialized");
    }

    try {
      this.updateState(ServerState.CONNECTING);
      
      await this.server.connect(this.transport);
      
      this.updateState(ServerState.CONNECTED);
      this.status.connectionCount++;
      
      console.error("Server connected and ready");
      
      this.updateState(ServerState.READY);
      
    } catch (error) {
      this.handleError(error as Error);
      throw error;
    }
  }

  private updateState(newState: ServerState) {
    const oldState = this.status.state;
    this.status.state = newState;
    this.status.lastActivity = new Date();
    
    console.error(`State transition: ${oldState} -> ${newState}`);
  }

  private updateActivity() {
    this.status.lastActivity = new Date();
  }

  private handleError(error: Error) {
    this.status.errors.push(error);
    this.updateState(ServerState.ERROR);
    console.error("Server error:", error.message);
  }

  async shutdown(): Promise<void> {
    this.updateState(ServerState.SHUTTING_DOWN);
    
    // Perform cleanup operations
    if (this.server) {
      await this.server.close?.();
    }
    
    this.updateState(ServerState.SHUTDOWN);
    console.error("Server shutdown completed");
  }

  getStatus(): ServerStatus {
    return { ...this.status };
  }
}

3. Connection and Handshake Handling

3.1 Connection Manager

interface ClientConnection {
  id: string;
  connectedAt: Date;
  lastActivity: Date;
  requestCount: number;
  protocol: string;
  capabilities: any;
}

class ConnectionManager {
  private connections: Map<string, ClientConnection> = new Map();
  private maxConnections: number;
  private connectionTimeout: number;

  constructor(maxConnections: number = 10, connectionTimeout: number = 300000) {
    this.maxConnections = maxConnections;
    this.connectionTimeout = connectionTimeout;
    
    // Periodically clean up timed-out connections
    setInterval(() => this.cleanupTimeoutConnections(), 60000);
  }

  addConnection(id: string, capabilities: any): ClientConnection {
    // Check connection limit
    if (this.connections.size >= this.maxConnections) {
      throw new Error("Maximum connections exceeded");
    }

    const connection: ClientConnection = {
      id,
      connectedAt: new Date(),
      lastActivity: new Date(),
      requestCount: 0,
      protocol: "mcp",
      capabilities
    };

    this.connections.set(id, connection);
    console.error(`Client connected: ${id} (${this.connections.size}/${this.maxConnections})`);
    
    return connection;
  }

  removeConnection(id: string): boolean {
    const removed = this.connections.delete(id);
    if (removed) {
      console.error(`Client disconnected: ${id} (${this.connections.size}/${this.maxConnections})`);
    }
    return removed;
  }

  updateActivity(id: string) {
    const connection = this.connections.get(id);
    if (connection) {
      connection.lastActivity = new Date();
      connection.requestCount++;
    }
  }

  private cleanupTimeoutConnections() {
    const now = Date.now();
    const timeoutConnections: string[] = [];

    for (const [id, connection] of this.connections) {
      if (now - connection.lastActivity.getTime() > this.connectionTimeout) {
        timeoutConnections.push(id);
      }
    }

    timeoutConnections.forEach(id => {
      this.removeConnection(id);
      console.error(`Connection timeout: ${id}`);
    });
  }

  getConnections(): ClientConnection[] {
    return Array.from(this.connections.values());
  }

  getConnectionCount(): number {
    return this.connections.size;
  }
}

3.2 Handshake Protocol Handling

interface HandshakeRequest {
  protocolVersion: string;
  capabilities: any;
  clientInfo: {
    name: string;
    version: string;
  };
}

interface HandshakeResponse {
  protocolVersion: string;
  capabilities: any;
  serverInfo: {
    name: string;
    version: string;
  };
  instructions?: string;
}

class HandshakeManager {
  private supportedVersions: string[] = ["2024-11-05"];
  private serverInfo: { name: string; version: string };
  private serverCapabilities: any;

  constructor(serverInfo: { name: string; version: string }, capabilities: any) {
    this.serverInfo = serverInfo;
    this.serverCapabilities = capabilities;
  }

  processHandshake(request: HandshakeRequest): HandshakeResponse {
    // Validate protocol version
    if (!this.supportedVersions.includes(request.protocolVersion)) {
      throw new Error(`Unsupported protocol version: ${request.protocolVersion}`);
    }

    // Validate client info
    this.validateClientInfo(request.clientInfo);

    // Negotiate capabilities
    const negotiatedCapabilities = this.negotiateCapabilities(
      request.capabilities,
      this.serverCapabilities
    );

    console.error(`Handshake completed with ${request.clientInfo.name} v${request.clientInfo.version}`);

    return {
      protocolVersion: request.protocolVersion,
      capabilities: negotiatedCapabilities,
      serverInfo: this.serverInfo,
      instructions: "Welcome to MCP Server"
    };
  }

  private validateClientInfo(clientInfo: { name: string; version: string }) {
    if (!clientInfo.name || !clientInfo.version) {
      throw new Error("Invalid client info: name and version are required");
    }

    // More validation logic can be added, like a client whitelist, etc.
  }

  private negotiateCapabilities(clientCapabilities: any, serverCapabilities: any): any {
    const negotiated: any = {};

    // Negotiate tool capabilities
    if (clientCapabilities.tools && serverCapabilities.tools) {
      negotiated.tools = {
        listChanged: Boolean(
          clientCapabilities.tools.listChanged && 
          serverCapabilities.tools.listChanged
        )
      };
    }

    // Negotiate resource capabilities
    if (clientCapabilities.resources && serverCapabilities.resources) {
      negotiated.resources = {
        subscribe: Boolean(
          clientCapabilities.resources.subscribe && 
          serverCapabilities.resources.subscribe
        ),
        listChanged: Boolean(
          clientCapabilities.resources.listChanged && 
          serverCapabilities.resources.listChanged
        )
      };
    }

    // Negotiate prompt capabilities
    if (clientCapabilities.prompts && serverCapabilities.prompts) {
      negotiated.prompts = {};
    }

    return negotiated;
  }
}

4. Lifecycle Event Management

4.1 Event System

import { EventEmitter } from "events";

enum LifecycleEvent {
  INITIALIZING = "initializing",
  INITIALIZED = "initialized",
  CONNECTING = "connecting", 
  CONNECTED = "connected",
  CLIENT_CONNECTED = "client_connected",
  CLIENT_DISCONNECTED = "client_disconnected",
  REQUEST_RECEIVED = "request_received",
  REQUEST_COMPLETED = "request_completed",
  ERROR_OCCURRED = "error_occurred",
  SHUTTING_DOWN = "shutting_down",
  SHUTDOWN = "shutdown"
}

interface LifecycleEventData {
  timestamp: Date;
  event: LifecycleEvent;
  data?: any;
  error?: Error;
}

class LifecycleManager extends EventEmitter {
  private eventHistory: LifecycleEventData[] = [];
  private maxHistorySize: number = 1000;

  constructor() {
    super();
    this.setupEventLogging();
  }

  private setupEventLogging() {
    // Listen to all events and log them
    this.onAny((event: LifecycleEvent, data?: any) => {
      const eventData: LifecycleEventData = {
        timestamp: new Date(),
        event,
        data
      };

      this.eventHistory.push(eventData);
      
      // Limit history size
      if (this.eventHistory.length > this.maxHistorySize) {
        this.eventHistory.shift();
      }

      console.error(`Lifecycle event: ${event}`, data ? `[${JSON.stringify(data)}]` : '');
    });
  }

  emitLifecycleEvent(event: LifecycleEvent, data?: any) {
    this.emit(event, data);
  }

  emitError(event: LifecycleEvent, error: Error, data?: any) {
    const eventData: LifecycleEventData = {
      timestamp: new Date(),
      event,
      error,
      data
    };

    this.eventHistory.push(eventData);
    this.emit(event, { error, data });
  }

  getEventHistory(limit?: number): LifecycleEventData[] {
    if (limit) {
      return this.eventHistory.slice(-limit);
    }
    return [...this.eventHistory];
  }

  getEventsByType(eventType: LifecycleEvent): LifecycleEventData[] {
    return this.eventHistory.filter(event => event.event === eventType);
  }

  // Extend EventEmitter to support wildcard listening
  private onAny(callback: (event: LifecycleEvent, data?: any) => void) {
    const originalEmit = this.emit.bind(this);
    
    this.emit = function(event: string | symbol, ...args: any[]) {
      callback(event as LifecycleEvent, args[0]);
      return originalEmit(event, ...args);
    };
  }
}

4.2 Lifecycle Hook System

type LifecycleHook = (context: any) => Promise<void> | void;

interface HookRegistry {
  beforeInit: LifecycleHook[];
  afterInit: LifecycleHook[];
  beforeConnect: LifecycleHook[];
  afterConnect: LifecycleHook[];
  beforeShutdown: LifecycleHook[];
  afterShutdown: LifecycleHook[];
}

class LifecycleHookManager {
  private hooks: HookRegistry = {
    beforeInit: [],
    afterInit: [],
    beforeConnect: [],
    afterConnect: [],
    beforeShutdown: [],
    afterShutdown: []
  };

  registerHook(phase: keyof HookRegistry, hook: LifecycleHook) {
    this.hooks[phase].push(hook);
  }

  async executeHooks(phase: keyof HookRegistry, context: any = {}) {
    const phaseHooks = this.hooks[phase];
    
    console.error(`Executing ${phaseHooks.length} hooks for phase: ${phase}`);
    
    for (const hook of phaseHooks) {
      try {
        await hook(context);
      } catch (error) {
        console.error(`Hook execution failed in phase ${phase}:`, error);
        throw error;
      }
    }
  }

  clearHooks(phase?: keyof HookRegistry) {
    if (phase) {
      this.hooks[phase] = [];
    } else {
      for (const key in this.hooks) {
        this.hooks[key as keyof HookRegistry] = [];
      }
    }
  }
}

5. Health Monitoring and Supervision

5.1 Health Check System

interface HealthMetrics {
  uptime: number;
  memoryUsage: NodeJS.MemoryUsage;
  cpuUsage: NodeJS.CpuUsage;
  connectionCount: number;
  requestCount: number;
  errorCount: number;
  averageResponseTime: number;
}

interface HealthStatus {
  status: "healthy" | "warning" | "critical";
  timestamp: Date;
  metrics: HealthMetrics;
  issues: string[];
}

class HealthMonitor {
  private startTime: Date;
  private requestMetrics: number[] = [];
  private errorCount: number = 0;
  private requestCount: number = 0;
  private connectionManager: ConnectionManager;

  constructor(connectionManager: ConnectionManager) {
    this.startTime = new Date();
    this.connectionManager = connectionManager;
    
    // Periodically clean up old metric data
    setInterval(() => this.cleanupOldMetrics(), 60000);
  }

  recordRequest(responseTime: number) {
    this.requestCount++;
    this.requestMetrics.push(responseTime);
    
    // Only keep the most recent 1000 request metrics
    if (this.requestMetrics.length > 1000) {
      this.requestMetrics.shift();
    }
  }

  recordError() {
    this.errorCount++;
  }

  getHealthStatus(): HealthStatus {
    const now = new Date();
    const uptime = now.getTime() - this.startTime.getTime();
    
    const metrics: HealthMetrics = {
      uptime,
      memoryUsage: process.memoryUsage(),
      cpuUsage: process.cpuUsage(),
      connectionCount: this.connectionManager.getConnectionCount(),
      requestCount: this.requestCount,
      errorCount: this.errorCount,
      averageResponseTime: this.calculateAverageResponseTime()
    };

    const { status, issues } = this.analyzeHealth(metrics);

    return {
      status,
      timestamp: now,
      metrics,
      issues
    };
  }

  private calculateAverageResponseTime(): number {
    if (this.requestMetrics.length === 0) return 0;
    
    const sum = this.requestMetrics.reduce((acc, time) => acc + time, 0);
    return sum / this.requestMetrics.length;
  }

  private analyzeHealth(metrics: HealthMetrics): { status: HealthStatus['status'], issues: string[] } {
    const issues: string[] = [];
    let status: HealthStatus['status'] = "healthy";

    // Check memory usage
    const memoryUsagePercent = (metrics.memoryUsage.heapUsed / metrics.memoryUsage.heapTotal) * 100;
    if (memoryUsagePercent > 90) {
      issues.push(`High memory usage: ${memoryUsagePercent.toFixed(1)}%`);
      status = "critical";
    } else if (memoryUsagePercent > 75) {
      issues.push(`Elevated memory usage: ${memoryUsagePercent.toFixed(1)}%`);
      if (status === "healthy") status = "warning";
    }

    // Check response time
    if (metrics.averageResponseTime > 5000) {
      issues.push(`High response time: ${metrics.averageResponseTime.toFixed(0)}ms`);
      status = "critical";
    } else if (metrics.averageResponseTime > 2000) {
      issues.push(`Elevated response time: ${metrics.averageResponseTime.toFixed(0)}ms`);
      if (status === "healthy") status = "warning";
    }

    // Check error rate
    const errorRate = metrics.requestCount > 0 ? (metrics.errorCount / metrics.requestCount) * 100 : 0;
    if (errorRate > 10) {
      issues.push(`High error rate: ${errorRate.toFixed(1)}%`);
      status = "critical";
    } else if (errorRate > 5) {
      issues.push(`Elevated error rate: ${errorRate.toFixed(1)}%`);
      if (status === "healthy") status = "warning";
    }

    return { status, issues };
  }

  private cleanupOldMetrics() {
    // Clean up request metrics older than 1 hour
    const oneHourAgo = Date.now() - 3600000;
    // Simplified here, should actually log timestamps
    if (this.requestMetrics.length > 1000) {
      this.requestMetrics.splice(0, this.requestMetrics.length - 1000);
    }
  }
}

5.2 Performance Monitoring

interface PerformanceMetrics {
  requestLatency: {
    p50: number;
    p95: number;
    p99: number;
  };
  throughput: {
    requestsPerSecond: number;
    bytesPerSecond: number;
  };
  resources: {
    memoryMB: number;
    cpuPercent: number;
  };
}

class PerformanceMonitor {
  private metrics: {
    requestTimes: { timestamp: number; duration: number }[];
    requestSizes: { timestamp: number; bytes: number }[];
  };

  constructor() {
    this.metrics = {
      requestTimes: [],
      requestSizes: []
    };

    // Periodically clean up old data
    setInterval(() => this.cleanupOldData(), 60000);
  }

  recordRequestMetrics(duration: number, requestSize: number, responseSize: number) {
    const timestamp = Date.now();
    
    this.metrics.requestTimes.push({ timestamp, duration });
    this.metrics.requestSizes.push({ timestamp, bytes: requestSize + responseSize });
  }

  getPerformanceMetrics(): PerformanceMetrics {
    const now = Date.now();
    const oneMinuteAgo = now - 60000;
    
    // Get data from the last minute
    const recentTimes = this.metrics.requestTimes.filter(m => m.timestamp > oneMinuteAgo);
    const recentSizes = this.metrics.requestSizes.filter(m => m.timestamp > oneMinuteAgo);

    // Calculate latency percentiles
    const latencies = recentTimes.map(m => m.duration).sort((a, b) => a - b);
    const latencyPercentiles = {
      p50: this.calculatePercentile(latencies, 50),
      p95: this.calculatePercentile(latencies, 95),
      p99: this.calculatePercentile(latencies, 99)
    };

    // Calculate throughput
    const requestsPerSecond = recentTimes.length / 60;
    const totalBytes = recentSizes.reduce((sum, m) => sum + m.bytes, 0);
    const bytesPerSecond = totalBytes / 60;

    // Get resource usage
    const memoryUsage = process.memoryUsage();
    const cpuUsage = process.cpuUsage();

    return {
      requestLatency: latencyPercentiles,
      throughput: {
        requestsPerSecond,
        bytesPerSecond
      },
      resources: {
        memoryMB: memoryUsage.heapUsed / 1024 / 1024,
        cpuPercent: (cpuUsage.user + cpuUsage.system) / 1000000 * 100
      }
    };
  }

  private calculatePercentile(values: number[], percentile: number): number {
    if (values.length === 0) return 0;
    
    const index = Math.ceil((percentile / 100) * values.length) - 1;
    return values[Math.max(0, index)];
  }

  private cleanupOldData() {
    const oneHourAgo = Date.now() - 3600000;
    
    this.metrics.requestTimes = this.metrics.requestTimes.filter(m => m.timestamp > oneHourAgo);
    this.metrics.requestSizes = this.metrics.requestSizes.filter(m => m.timestamp > oneHourAgo);
  }
}

6. Graceful Shutdown and Resource Cleanup

6.1 Shutdown Manager

interface ShutdownHandler {
  name: string;
  priority: number;  // 0-100, 100 is the highest priority
  timeout: number;   // Timeout in ms
  handler: () => Promise<void>;
}

class ShutdownManager {
  private handlers: ShutdownHandler[] = [];
  private isShuttingDown: boolean = false;
  private shutdownTimeout: number = 30000; // 30 seconds

  constructor() {
    this.setupSignalHandlers();
  }

  private setupSignalHandlers() {
    const gracefulShutdown = () => {
      if (!this.isShuttingDown) {
        console.error("Received shutdown signal, initiating graceful shutdown...");
        this.shutdown().catch(console.error);
      }
    };

    process.on('SIGINT', gracefulShutdown);
    process.on('SIGTERM', gracefulShutdown);
    process.on('SIGUSR2', gracefulShutdown); // nodemon restart
  }

  registerShutdownHandler(handler: ShutdownHandler) {
    this.handlers.push(handler);
    // Sort by priority, higher priority executed first
    this.handlers.sort((a, b) => b.priority - a.priority);
  }

  async shutdown(): Promise<void> {
    if (this.isShuttingDown) {
      console.error("Shutdown already in progress");
      return;
    }

    this.isShuttingDown = true;
    console.error("Starting graceful shutdown...");

    const shutdownStart = Date.now();
    const overallTimeout = setTimeout(() => {
      console.error("Shutdown timeout reached, forcing exit");
      process.exit(1);
    }, this.shutdownTimeout);

    try {
      for (const handler of this.handlers) {
        console.error(`Executing shutdown handler: ${handler.name}`);
        
        const handlerTimeout = setTimeout(() => {
          console.error(`Shutdown handler timeout: ${handler.name}`);
        }, handler.timeout);

        try {
          await handler.handler();
          clearTimeout(handlerTimeout);
          console.error(`Shutdown handler completed: ${handler.name}`);
        } catch (error) {
          clearTimeout(handlerTimeout);
          console.error(`Shutdown handler failed: ${handler.name}`, error);
        }
      }

      clearTimeout(overallTimeout);
      const shutdownDuration = Date.now() - shutdownStart;
      console.error(`Graceful shutdown completed in ${shutdownDuration}ms`);
      
      process.exit(0);
      
    } catch (error) {
      clearTimeout(overallTimeout);
      console.error("Shutdown failed:", error);
      process.exit(1);
    }
  }
}

6.2 Resource Cleanup Manager

interface Resource {
  id: string;
  type: string;
  cleanup: () => Promise<void>;
  created: Date;
}

class ResourceCleanupManager {
  private resources: Map<string, Resource> = new Map();
  private cleanupInProgress: boolean = false;

  registerResource(resource: Resource) {
    this.resources.set(resource.id, resource);
    console.error(`Resource registered: ${resource.type}:${resource.id}`);
  }

  unregisterResource(resourceId: string): boolean {
    const resource = this.resources.get(resourceId);
    if (resource) {
      this.resources.delete(resourceId);
      console.error(`Resource unregistered: ${resource.type}:${resourceId}`);
      return true;
    }
    return false;
  }

  async cleanupAll(): Promise<void> {
    if (this.cleanupInProgress) {
      console.error("Cleanup already in progress");
      return;
    }

    this.cleanupInProgress = true;
    console.error(`Starting cleanup of ${this.resources.size} resources`);

    const cleanupPromises = Array.from(this.resources.values()).map(async (resource) => {
      try {
        console.error(`Cleaning up resource: ${resource.type}:${resource.id}`);
        await resource.cleanup();
        console.error(`Resource cleaned up: ${resource.type}:${resource.id}`);
      } catch (error) {
        console.error(`Resource cleanup failed: ${resource.type}:${resource.id}`, error);
      }
    });

    await Promise.allSettled(cleanupPromises);
    
    this.resources.clear();
    this.cleanupInProgress = false;
    console.error("Resource cleanup completed");
  }

  async cleanupByType(type: string): Promise<void> {
    const resourcesOfType = Array.from(this.resources.values())
      .filter(r => r.type === type);

    console.error(`Cleaning up ${resourcesOfType.length} resources of type: ${type}`);

    for (const resource of resourcesOfType) {
      try {
        await resource.cleanup();
        this.resources.delete(resource.id);
        console.error(`Resource cleaned up: ${resource.type}:${resource.id}`);
      } catch (error) {
        console.error(`Resource cleanup failed: ${resource.type}:${resource.id}`, error);
      }
    }
  }

  getResourceCount(): number {
    return this.resources.size;
  }

  getResourcesByType(type: string): Resource[] {
    return Array.from(this.resources.values()).filter(r => r.type === type);
  }
}

7. Complete Server Implementation

7.1 ManagedMCPServer

class ManagedMCPServer {
  private initializer: ServerInitializer;
  private lifecycleManager: LifecycleManager;
  private hookManager: LifecycleHookManager;
  private connectionManager: ConnectionManager;
  private handshakeManager: HandshakeManager;
  private healthMonitor: HealthMonitor;
  private performanceMonitor: PerformanceMonitor;
  private shutdownManager: ShutdownManager;
  private resourceCleanup: ResourceCleanupManager;
  private server: Server | null = null;

  constructor(config: Partial<ServerConfig>) {
    // Initialize all managers
    this.lifecycleManager = new LifecycleManager();
    this.hookManager = new LifecycleHookManager();
    this.connectionManager = new ConnectionManager();
    this.shutdownManager = new ShutdownManager();
    this.resourceCleanup = new ResourceCleanupManager();
    this.initializer = new ServerInitializer(config);
    
    const serverConfig = this.initializer.getConfigManager().getConfig();
    this.handshakeManager = new HandshakeManager(
      { name: serverConfig.server.name, version: serverConfig.server.version },
      serverConfig.capabilities
    );
    
    this.healthMonitor = new HealthMonitor(this.connectionManager);
    this.performanceMonitor = new PerformanceMonitor();

    this.setupLifecycleHooks();
    this.setupShutdownHandlers();
  }

  private setupLifecycleHooks() {
    // Register built-in lifecycle hooks
    this.hookManager.registerHook('beforeInit', async () => {
      this.lifecycleManager.emitLifecycleEvent(LifecycleEvent.INITIALIZING);
    });

    this.hookManager.registerHook('afterInit', async () => {
      this.lifecycleManager.emitLifecycleEvent(LifecycleEvent.INITIALIZED);
    });

    this.hookManager.registerHook('beforeConnect', async () => {
      this.lifecycleManager.emitLifecycleEvent(LifecycleEvent.CONNECTING);
    });

    this.hookManager.registerHook('afterConnect', async () => {
      this.lifecycleManager.emitLifecycleEvent(LifecycleEvent.CONNECTED);
    });

    this.hookManager.registerHook('beforeShutdown', async () => {
      this.lifecycleManager.emitLifecycleEvent(LifecycleEvent.SHUTTING_DOWN);
    });

    this.hookManager.registerHook('afterShutdown', async () => {
      this.lifecycleManager.emitLifecycleEvent(LifecycleEvent.SHUTDOWN);
    });
  }

  private setupShutdownHandlers() {
    // Register shutdown handlers
    this.shutdownManager.registerShutdownHandler({
      name: "connection-cleanup",
      priority: 90,
      timeout: 5000,
      handler: async () => {
        // Clean up all connections
        const connections = this.connectionManager.getConnections();
        for (const connection of connections) {
          this.connectionManager.removeConnection(connection.id);
        }
      }
    });

    this.shutdownManager.registerShutdownHandler({
      name: "resource-cleanup",
      priority: 80,
      timeout: 10000,
      handler: async () => {
        await this.resourceCleanup.cleanupAll();
      }
    });

    this.shutdownManager.registerShutdownHandler({
      name: "server-close",
      priority: 70,
      timeout: 5000,
      handler: async () => {
        if (this.server) {
          await this.server.close?.();
        }
      }
    });
  }

  async start(): Promise<void> {
    try {
      // Execute pre-initialization hooks
      await this.hookManager.executeHooks('beforeInit');

      // Initialize the server
      this.server = await this.initializer.initialize();

      // Execute post-initialization hooks
      await this.hookManager.executeHooks('afterInit');

      // Execute pre-connection hooks
      await this.hookManager.executeHooks('beforeConnect');

      // Establish connection
      await this.initializer.connect();

      // Execute post-connection hooks
      await this.hookManager.executeHooks('afterConnect');

      console.error("MCP Server started successfully");

    } catch (error) {
      this.lifecycleManager.emitError(LifecycleEvent.ERROR_OCCURRED, error as Error);
      throw error;
    }
  }

  async stop(): Promise<void> {
    await this.hookManager.executeHooks('beforeShutdown');
    await this.shutdownManager.shutdown();
    await this.hookManager.executeHooks('afterShutdown');
  }

  // Public API methods
  addLifecycleHook(phase: keyof HookRegistry, hook: LifecycleHook) {
    this.hookManager.registerHook(phase, hook);
  }

  getHealthStatus(): HealthStatus {
    return this.healthMonitor.getHealthStatus();
  }

  getPerformanceMetrics(): PerformanceMetrics {
    return this.performanceMonitor.getPerformanceMetrics();
  }

  getServerStatus(): ServerStatus {
    return this.initializer.getStatus();
  }

  getServer(): Server | null {
    return this.server;
  }
}

// Usage example
const server = new ManagedMCPServer({
  server: {
    name: "my-mcp-server",
    version: "1.0.0",
    timeout: 30000
  },
  capabilities: {
    tools: { listChanged: true },
    resources: { subscribe: true },
    prompts: {}
  }
});

// Add a custom lifecycle hook
server.addLifecycleHook('afterInit', async () => {
  console.error("Custom initialization completed");
});

// Start the server
server.start().catch(console.error);

8. Best Practices

8.1 Configuration Management

  1. Environment Separation - Use different configurations for development, testing, and production environments.
  2. Sensitive Information - Use environment variables to store sensitive configurations.
  3. Configuration Validation - Validate all configuration items at startup.
  4. Hot Reloading - Support hot reloading for some configurations.
  5. Default Values - Provide reasonable default values for all configurations.

8.2 Monitoring and Operations

  1. Health Checks - Implement a comprehensive health check mechanism.
  2. Logging - Log key events and error messages.
  3. Performance Monitoring - Continuously monitor server performance metrics.
  4. Alerting Mechanism - Alert in a timely manner when problems occur.
  5. Fault Recovery - Implement automatic fault detection and recovery.

8.3 Resource Management

  1. Memory Management - Prevent memory leaks and excessive usage.
  2. Connection Pooling - Manage network connection resources reasonably.
  3. File Handles - Close file and resource handles promptly.
  4. Periodic Cleanup - Periodically clean up expired and unused resources.
  5. Resource Limits - Set reasonable resource usage limits.

Summary

Through this chapter, we have learned:

  • The complete initialization and configuration process of the MCP Server.
  • Connection management and handshake protocol handling.
  • Lifecycle event management and hook system.
  • Health and performance monitoring mechanisms.
  • Graceful shutdown and resource cleanup strategies.

Good server configuration and lifecycle management are the foundation for building a stable and reliable MCP Server, laying a solid groundwork for subsequent feature development and operational management.