Chapter 7: Server Configuration and Lifecycle
Haiyue
37min
Chapter 7: Server Configuration and Lifecycle
Learning Objectives
- Master the initialization and configuration of the MCP Server
- Learn the connection establishment and handshake process
- Understand the lifecycle management of the Server
- Master graceful shutdown and resource cleanup
- 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
- Environment Separation - Use different configurations for development, testing, and production environments.
- Sensitive Information - Use environment variables to store sensitive configurations.
- Configuration Validation - Validate all configuration items at startup.
- Hot Reloading - Support hot reloading for some configurations.
- Default Values - Provide reasonable default values for all configurations.
8.2 Monitoring and Operations
- Health Checks - Implement a comprehensive health check mechanism.
- Logging - Log key events and error messages.
- Performance Monitoring - Continuously monitor server performance metrics.
- Alerting Mechanism - Alert in a timely manner when problems occur.
- Fault Recovery - Implement automatic fault detection and recovery.
8.3 Resource Management
- Memory Management - Prevent memory leaks and excessive usage.
- Connection Pooling - Manage network connection resources reasonably.
- File Handles - Close file and resource handles promptly.
- Periodic Cleanup - Periodically clean up expired and unused resources.
- 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.