Chapter 7: Server Configuration and Lifecycle
Haiyue
18min
Chapter 7: Server Configuration and Lifecycle
Learning Objectives
- Master MCP Server initialization and configuration
- Learn connection establishment and handshake process
- Understand Server lifecycle management
- Master graceful shutdown and resource cleanup
- Learn Server monitoring and health checks
7.1 MCP Server Initialization and Configuration
7.1.1 Basic Configuration Structure
MCP Server configuration is the foundation of the entire service operation, including service metadata, capability declarations, runtime parameters, etc.
// src/config/ServerConfig.ts
export interface ServerConfig {
// Service basic information
server: {
name: string;
version: string;
description?: string;
author?: string;
};
// Service capability configuration
capabilities: {
tools?: ToolsCapability;
resources?: ResourcesCapability;
prompts?: PromptsCapability;
logging?: LoggingCapability;
experimental?: Record<string, unknown>;
};
// Runtime configuration
runtime: {
maxConnections?: number;
timeout?: number;
keepAlive?: boolean;
enableMetrics?: boolean;
};
// Security configuration
security: {
allowedOrigins?: string[];
rateLimiting?: RateLimitConfig;
authentication?: AuthConfig;
};
}
export interface ToolsCapability {
listChanged?: boolean;
}
export interface ResourcesCapability {
subscribe?: boolean;
listChanged?: boolean;
}
export interface PromptsCapability {
listChanged?: boolean;
}
export interface LoggingCapability {
level?: 'error' | 'warn' | 'info' | 'debug';
}
7.1.2 Configuration Loading and Validation
// src/config/ConfigLoader.ts
import { z } from 'zod';
import fs from 'fs/promises';
const ServerConfigSchema = z.object({
server: z.object({
name: z.string().min(1),
version: z.string().min(1),
description: z.string().optional(),
author: z.string().optional(),
}),
capabilities: z.object({
tools: z.object({
listChanged: z.boolean().optional(),
}).optional(),
resources: z.object({
subscribe: z.boolean().optional(),
listChanged: z.boolean().optional(),
}).optional(),
prompts: z.object({
listChanged: z.boolean().optional(),
}).optional(),
}),
runtime: z.object({
maxConnections: z.number().positive().optional().default(100),
timeout: z.number().positive().optional().default(30000),
keepAlive: z.boolean().optional().default(true),
enableMetrics: z.boolean().optional().default(false),
}).optional().default({}),
});
export class ConfigLoader {
static async loadFromFile(configPath: string): Promise<ServerConfig> {
try {
const configData = await fs.readFile(configPath, 'utf-8');
const rawConfig = JSON.parse(configData);
// Validate configuration structure
const validatedConfig = ServerConfigSchema.parse(rawConfig);
console.log(`Configuration file loaded successfully: ${configPath}`);
return validatedConfig;
} catch (error) {
if (error instanceof z.ZodError) {
console.error('Configuration validation failed:', error.errors);
throw new Error(`Configuration file validation failed: ${error.errors.map(e => e.message).join(', ')}`);
}
console.error(`Configuration file loading failed: ${configPath}`, error);
throw error;
}
}
static mergeConfigs(baseConfig: ServerConfig, overrideConfig: Partial<ServerConfig>): ServerConfig {
return {
server: { ...baseConfig.server, ...overrideConfig.server },
capabilities: { ...baseConfig.capabilities, ...overrideConfig.capabilities },
runtime: { ...baseConfig.runtime, ...overrideConfig.runtime },
security: { ...baseConfig.security, ...overrideConfig.security },
};
}
}
7.2 Connection Establishment and Handshake Process
7.2.1 Connection Manager
// src/connection/ConnectionManager.ts
import { EventEmitter } from 'events';
import { Transport } from '@modelcontextprotocol/sdk/types.js';
export interface Connection {
id: string;
transport: Transport;
clientInfo?: ClientInfo;
connectedAt: Date;
lastActivity: Date;
state: ConnectionState;
capabilities?: ClientCapabilities;
}
export enum ConnectionState {
CONNECTING = 'connecting',
HANDSHAKING = 'handshaking',
CONNECTED = 'connected',
DISCONNECTING = 'disconnecting',
DISCONNECTED = 'disconnected',
}
export class ConnectionManager extends EventEmitter {
private connections = new Map<string, Connection>();
private maxConnections: number;
constructor(maxConnections: number = 100) {
super();
this.maxConnections = maxConnections;
}
async addConnection(transport: Transport): Promise<string> {
if (this.connections.size >= this.maxConnections) {
throw new Error('Maximum connections exceeded');
}
const connectionId = this.generateConnectionId();
const connection: Connection = {
id: connectionId,
transport,
connectedAt: new Date(),
lastActivity: new Date(),
state: ConnectionState.CONNECTING,
};
this.connections.set(connectionId, connection);
this.setupConnectionHandlers(connection);
console.log(`New connection established: ${connectionId}`);
this.emit('connectionAdded', connection);
return connectionId;
}
private setupConnectionHandlers(connection: Connection): void {
const { transport } = connection;
// Listen for messages
transport.onmessage = (message) => {
connection.lastActivity = new Date();
this.emit('message', connection, message);
};
// Listen for connection close
transport.onclose = () => {
this.removeConnection(connection.id);
};
// Listen for errors
transport.onerror = (error) => {
console.error(`Connection error ${connection.id}:`, error);
this.emit('connectionError', connection, error);
};
}
private generateConnectionId(): string {
return `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
7.3 Server Lifecycle Management
7.3.1 Lifecycle State Machine
// src/lifecycle/LifecycleManager.ts
export enum ServerState {
INITIAL = 'initial',
STARTING = 'starting',
RUNNING = 'running',
STOPPING = 'stopping',
STOPPED = 'stopped',
ERROR = 'error',
}
export interface LifecycleHook {
name: string;
execute(): Promise<void>;
}
export class LifecycleManager extends EventEmitter {
private state: ServerState = ServerState.INITIAL;
private startupHooks: LifecycleHook[] = [];
private shutdownHooks: LifecycleHook[] = [];
private shutdownTimeout = 30000; // 30 seconds
async start(): Promise<void> {
if (this.state !== ServerState.INITIAL) {
throw new Error(`Cannot start server from state: ${this.state}`);
}
this.setState(ServerState.STARTING);
try {
console.log('Server starting...');
// Execute startup hooks
for (const hook of this.startupHooks) {
console.log(`Executing startup hook: ${hook.name}`);
await hook.execute();
}
this.setState(ServerState.RUNNING);
console.log('Server started successfully');
} catch (error) {
this.setState(ServerState.ERROR);
console.error('Server startup failed:', error);
throw error;
}
}
async stop(reason?: string): Promise<void> {
if (this.state !== ServerState.RUNNING) {
console.log(`Server already stopped or stopping: ${this.state}`);
return;
}
this.setState(ServerState.STOPPING);
console.log(`Server stopping... ${reason ? `Reason: ${reason}` : ''}`);
try {
// Set shutdown timeout
const timeoutPromise = new Promise<void>((_, reject) => {
setTimeout(() => {
reject(new Error('Shutdown timeout exceeded'));
}, this.shutdownTimeout);
});
// Execute shutdown process
const shutdownPromise = this.executeShutdownHooks();
await Promise.race([shutdownPromise, timeoutPromise]);
this.setState(ServerState.STOPPED);
console.log('Server gracefully stopped');
} catch (error) {
console.error('Error during server shutdown:', error);
this.setState(ServerState.ERROR);
throw error;
}
}
private async executeShutdownHooks(): Promise<void> {
// Execute shutdown hooks in reverse order
const reversedHooks = [...this.shutdownHooks].reverse();
for (const hook of reversedHooks) {
try {
console.log(`Executing shutdown hook: ${hook.name}`);
await hook.execute();
} catch (error) {
console.error(`Shutdown hook execution failed: ${hook.name}`, error);
// Continue executing other hooks to ensure resources are cleaned up as much as possible
}
}
}
addStartupHook(hook: LifecycleHook): void {
this.startupHooks.push(hook);
}
addShutdownHook(hook: LifecycleHook): void {
this.shutdownHooks.push(hook);
}
private setState(newState: ServerState): void {
const previousState = this.state;
this.state = newState;
console.log(`State change: ${previousState} -> ${newState}`);
this.emit('stateChange', { from: previousState, to: newState });
}
getState(): ServerState {
return this.state;
}
isRunning(): boolean {
return this.state === ServerState.RUNNING;
}
}
7.3.2 Health Check System
// src/health/HealthChecker.ts
export interface HealthCheck {
name: string;
check(): Promise<HealthCheckResult>;
timeout?: number;
}
export interface HealthCheckResult {
status: 'healthy' | 'unhealthy' | 'degraded';
message?: string;
details?: Record<string, any>;
timestamp: Date;
}
export class HealthChecker {
private checks = new Map<string, HealthCheck>();
private lastResults = new Map<string, HealthCheckResult>();
addCheck(check: HealthCheck): void {
this.checks.set(check.name, check);
}
async runCheck(name: string): Promise<HealthCheckResult> {
const check = this.checks.get(name);
if (!check) {
throw new Error(`Health check not found: ${name}`);
}
const timeout = check.timeout || 5000;
try {
const result = await Promise.race([
check.check(),
this.createTimeoutPromise(timeout),
]);
this.lastResults.set(name, result);
return result;
} catch (error) {
const result: HealthCheckResult = {
status: 'unhealthy',
message: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date(),
};
this.lastResults.set(name, result);
return result;
}
}
getOverallHealth(): { status: 'healthy' | 'unhealthy' | 'degraded'; checks: Record<string, HealthCheckResult> } {
const checks: Record<string, HealthCheckResult> = {};
let hasUnhealthy = false;
let hasDegraded = false;
for (const [name, result] of this.lastResults) {
checks[name] = result;
if (result.status === 'unhealthy') {
hasUnhealthy = true;
} else if (result.status === 'degraded') {
hasDegraded = true;
}
}
let status: 'healthy' | 'unhealthy' | 'degraded';
if (hasUnhealthy) {
status = 'unhealthy';
} else if (hasDegraded) {
status = 'degraded';
} else {
status = 'healthy';
}
return { status, checks };
}
private createTimeoutPromise(timeout: number): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Health check timeout after ${timeout}ms`));
}, timeout);
});
}
}
7.4 Graceful Shutdown and Resource Cleanup
7.4.1 Resource Manager
// src/resources/ResourceManager.ts
export interface ManagedResource {
name: string;
close(): Promise<void>;
isHealthy?(): Promise<boolean>;
}
export class ResourceManager {
private resources = new Map<string, ManagedResource>();
private cleanupOrder: string[] = [];
register(resource: ManagedResource): void {
this.resources.set(resource.name, resource);
this.cleanupOrder.push(resource.name);
console.log(`Resource registered: ${resource.name}`);
}
async closeAll(): Promise<void> {
console.log('Starting resource cleanup...');
// Clean up resources in reverse registration order
const reverseOrder = [...this.cleanupOrder].reverse();
for (const name of reverseOrder) {
const resource = this.resources.get(name);
if (!resource) continue;
try {
console.log(`Cleaning up resource: ${name}`);
await resource.close();
console.log(`Resource cleanup completed: ${name}`);
} catch (error) {
console.error(`Resource cleanup failed: ${name}`, error);
// Continue cleaning up other resources
}
}
this.resources.clear();
this.cleanupOrder.length = 0;
console.log('All resources cleaned up');
}
}
7.5 Practical Example
7.5.1 Complete MCP Server Implementation
// src/MCPServer.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ConfigLoader, ServerConfig } from './config/ConfigLoader.js';
import { ConnectionManager } from './connection/ConnectionManager.js';
import { LifecycleManager, ServerState } from './lifecycle/LifecycleManager.js';
import { HealthChecker } from './health/HealthChecker.js';
import { ResourceManager } from './resources/ResourceManager.js';
export class MCPServer {
private config: ServerConfig;
private server: Server;
private connectionManager: ConnectionManager;
private lifecycleManager: LifecycleManager;
private healthChecker: HealthChecker;
private resourceManager: ResourceManager;
private transport: StdioServerTransport;
constructor(config: ServerConfig) {
this.config = config;
this.initializeComponents();
this.setupLifecycleHooks();
this.setupHealthChecks();
}
private initializeComponents(): void {
// Initialize SDK Server
this.server = new Server(
{
name: this.config.server.name,
version: this.config.server.version,
},
{
capabilities: this.config.capabilities,
}
);
// Initialize managers
this.connectionManager = new ConnectionManager(this.config.runtime?.maxConnections);
this.lifecycleManager = new LifecycleManager();
this.healthChecker = new HealthChecker();
this.resourceManager = new ResourceManager();
// Initialize transport layer
this.transport = new StdioServerTransport();
}
private setupLifecycleHooks(): void {
// Startup hooks
this.lifecycleManager.addStartupHook({
name: 'server-initialization',
execute: async () => {
console.log('Initializing MCP Server...');
await this.server.connect(this.transport);
},
});
// Shutdown hooks
this.lifecycleManager.addShutdownHook({
name: 'resources',
execute: async () => {
console.log('Cleaning up resources...');
await this.resourceManager.closeAll();
},
});
this.lifecycleManager.addShutdownHook({
name: 'server',
execute: async () => {
console.log('Closing MCP Server...');
await this.server.close();
},
});
}
private setupHealthChecks(): void {
this.healthChecker.addCheck({
name: 'server-state',
check: async () => {
const isRunning = this.lifecycleManager.isRunning();
return {
status: isRunning ? 'healthy' : 'unhealthy',
message: `Server state: ${this.lifecycleManager.getState()}`,
details: { state: this.lifecycleManager.getState() },
timestamp: new Date(),
};
},
});
}
async start(): Promise<void> {
try {
await this.lifecycleManager.start();
console.log(`MCP Server '${this.config.server.name}' started successfully!`);
console.log(`Version: ${this.config.server.version}`);
console.log(`Capabilities: ${Object.keys(this.config.capabilities).join(', ')}`);
} catch (error) {
console.error('Server startup failed:', error);
throw error;
}
}
async stop(reason?: string): Promise<void> {
await this.lifecycleManager.stop(reason);
}
getState(): ServerState {
return this.lifecycleManager.getState();
}
async getHealth(): Promise<any> {
return this.healthChecker.getOverallHealth();
}
// Static factory method
static async fromConfigFile(configPath: string): Promise<MCPServer> {
const config = await ConfigLoader.loadFromFile(configPath);
return new MCPServer(config);
}
}
Chapter Summary
Chapter 7 delves into MCP Server configuration and lifecycle management:
Core Knowledge Points
- Server Configuration System: Established complete configuration loading, validation, and merging mechanisms
- Connection Management: Implemented connection establishment, handshake, and state management
- Lifecycle Management: Built complete startup, running, and shutdown state machine
- Health Checks: Established comprehensive health monitoring system
- Resource Management: Implemented graceful resource cleanup mechanisms
Practical Points
- Use configuration files and environment variables for flexible configuration
- Implement complete handshake protocol to ensure connection establishment
- Establish hook system to manage lifecycle events
- Add health checks to ensure service reliability
- Implement graceful shutdown to avoid resource leaks
Through this chapter, we mastered the complete lifecycle management capabilities for building production-grade MCP Servers, laying a solid foundation for subsequent error handling, security control, and other advanced features.