Skip to main content
Glama

MCP Firebird

session-manager.ts8.88 kB
/** * Session Manager for MCP Firebird Server * Provides robust session management with automatic cleanup, configurable timeouts, * and comprehensive error handling for both SSE and Streamable HTTP transports */ import { EventEmitter } from 'events'; import { createLogger } from './logger.js'; const logger = createLogger('session-manager'); export interface SessionData { id: string; type: 'sse' | 'streamable-http'; transport: any; // Transport instance server?: any; // Server instance (for stateful modes) createdAt: Date; lastActivity: Date; metadata?: Record<string, any>; } export interface SessionManagerConfig { sessionTimeoutMs?: number; cleanupIntervalMs?: number; maxSessions?: number; enableMetrics?: boolean; } export interface SessionMetrics { totalSessions: number; activeSessions: number; expiredSessions: number; cleanupRuns: number; averageSessionDuration: number; sessionsByType: Record<string, number>; } /** * Centralized session manager for MCP transports */ export class SessionManager extends EventEmitter { private sessions: Map<string, SessionData> = new Map(); private cleanupInterval: NodeJS.Timeout | null = null; private metrics: SessionMetrics; private config: Required<SessionManagerConfig>; constructor(config: SessionManagerConfig = {}) { super(); this.config = { sessionTimeoutMs: config.sessionTimeoutMs || 1800000, // 30 minutes cleanupIntervalMs: config.cleanupIntervalMs || 60000, // 1 minute maxSessions: config.maxSessions || 1000, enableMetrics: config.enableMetrics ?? true }; this.metrics = { totalSessions: 0, activeSessions: 0, expiredSessions: 0, cleanupRuns: 0, averageSessionDuration: 0, sessionsByType: {} }; this.startCleanupTimer(); logger.info('Session manager initialized', { config: this.config }); } /** * Creates a new session */ createSession( id: string, type: 'sse' | 'streamable-http', transport: any, server?: any, metadata?: Record<string, any> ): SessionData { // Check session limits if (this.sessions.size >= this.config.maxSessions) { const oldestSession = this.getOldestSession(); if (oldestSession) { logger.warn(`Session limit reached, removing oldest session: ${oldestSession.id}`); this.removeSession(oldestSession.id); } } const sessionData: SessionData = { id, type, transport, server, createdAt: new Date(), lastActivity: new Date(), metadata: metadata || {} }; this.sessions.set(id, sessionData); // Update metrics if (this.config.enableMetrics) { this.metrics.totalSessions++; this.metrics.activeSessions++; this.metrics.sessionsByType[type] = (this.metrics.sessionsByType[type] || 0) + 1; } logger.info(`Session created: ${id} (type: ${type})`); this.emit('sessionCreated', sessionData); return sessionData; } /** * Gets a session by ID */ getSession(id: string): SessionData | undefined { const session = this.sessions.get(id); if (session) { session.lastActivity = new Date(); } return session; } /** * Updates session activity timestamp */ updateActivity(id: string): boolean { const session = this.sessions.get(id); if (session) { session.lastActivity = new Date(); return true; } return false; } /** * Removes a session */ removeSession(id: string): boolean { const session = this.sessions.get(id); if (!session) { return false; } try { // Close transport if (session.transport && typeof session.transport.close === 'function') { session.transport.close(); } // Close server if present if (session.server && typeof session.server.close === 'function') { session.server.close(); } } catch (error) { logger.warn(`Error closing resources for session ${id}:`, { error }); } this.sessions.delete(id); // Update metrics if (this.config.enableMetrics) { this.metrics.activeSessions--; const duration = new Date().getTime() - session.createdAt.getTime(); this.updateAverageSessionDuration(duration); } logger.info(`Session removed: ${id}`); this.emit('sessionRemoved', session); return true; } /** * Gets all active sessions */ getAllSessions(): SessionData[] { return Array.from(this.sessions.values()); } /** * Gets sessions by type */ getSessionsByType(type: 'sse' | 'streamable-http'): SessionData[] { return Array.from(this.sessions.values()).filter(session => session.type === type); } /** * Gets expired sessions */ getExpiredSessions(): SessionData[] { const now = new Date(); return Array.from(this.sessions.values()).filter(session => now.getTime() - session.lastActivity.getTime() > this.config.sessionTimeoutMs ); } /** * Performs cleanup of expired sessions */ cleanup(): number { const expiredSessions = this.getExpiredSessions(); let cleanedCount = 0; for (const session of expiredSessions) { if (this.removeSession(session.id)) { cleanedCount++; } } if (this.config.enableMetrics) { this.metrics.expiredSessions += cleanedCount; this.metrics.cleanupRuns++; } if (cleanedCount > 0) { logger.info(`Cleaned up ${cleanedCount} expired sessions`); this.emit('cleanup', { cleanedCount, totalSessions: this.sessions.size }); } return cleanedCount; } /** * Gets current metrics */ getMetrics(): SessionMetrics { return { ...this.metrics }; } /** * Resets metrics */ resetMetrics(): void { this.metrics = { totalSessions: 0, activeSessions: this.sessions.size, expiredSessions: 0, cleanupRuns: 0, averageSessionDuration: 0, sessionsByType: {} }; logger.info('Metrics reset'); } /** * Graceful shutdown - closes all sessions */ async shutdown(): Promise<void> { logger.info('Starting session manager shutdown...'); if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } const sessionIds = Array.from(this.sessions.keys()); const promises = sessionIds.map(id => new Promise<void>(resolve => { try { this.removeSession(id); } catch (error) { logger.warn(`Error removing session ${id} during shutdown:`, { error }); } resolve(); }) ); await Promise.all(promises); logger.info(`Session manager shutdown completed. Closed ${sessionIds.length} sessions.`); this.emit('shutdown'); } /** * Starts the cleanup timer */ private startCleanupTimer(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } this.cleanupInterval = setInterval(() => { this.cleanup(); }, this.config.cleanupIntervalMs); } /** * Gets the oldest session (for eviction when limit is reached) */ private getOldestSession(): SessionData | null { let oldest: SessionData | null = null; for (const session of this.sessions.values()) { if (!oldest || session.createdAt < oldest.createdAt) { oldest = session; } } return oldest; } /** * Updates the average session duration metric */ private updateAverageSessionDuration(newDuration: number): void { const totalSessions = this.metrics.totalSessions; if (totalSessions > 0) { this.metrics.averageSessionDuration = (this.metrics.averageSessionDuration * (totalSessions - 1) + newDuration) / totalSessions; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PuroDelphi/mcpFirebird'

If you have feedback or need assistance with the MCP directory API, please join our Discord server