Skip to main content
Glama
session-manager.ts4.72 kB
import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { SessionData } from "../auth/types.js"; import { getSessionConfig } from "../config/session-config.js"; interface Session { transport: StreamableHTTPServerTransport; sessionData: SessionData; createdAt: number; lastAccessedAt: number; } // Configuration - loaded from environment const sessionConfig = getSessionConfig(); const TRANSPORT_IDLE_TIMEOUT = sessionConfig.TRANSPORT_IDLE_TIMEOUT; const TRANSPORT_MAX_LIFETIME = sessionConfig.TRANSPORT_MAX_LIFETIME; export class SessionManager { private sessions = new Map<string, Session>(); /** * Check if a session is still valid based on TTL */ private isValid(session: Session): boolean { const now = Date.now(); const idleTime = now - session.lastAccessedAt; const lifetime = now - session.createdAt; return idleTime < TRANSPORT_IDLE_TIMEOUT && lifetime < TRANSPORT_MAX_LIFETIME; } /** * Get session data for a session ID */ getSessionData(sessionId: string): SessionData | undefined { const session = this.sessions.get(sessionId); if (!session) return undefined; // Update timestamp BEFORE validity check for atomicity const now = Date.now(); session.lastAccessedAt = now; if (!this.isValid(session)) { this.sessions.delete(sessionId); return undefined; } return session.sessionData; } /** * Get transport for a session ID */ getTransport(sessionId: string): StreamableHTTPServerTransport | undefined { const session = this.sessions.get(sessionId); if (!session) return undefined; // Update timestamp BEFORE validity check for atomicity const now = Date.now(); session.lastAccessedAt = now; if (!this.isValid(session)) { this.sessions.delete(sessionId); return undefined; } return session.transport; } /** * Get full session (for validation checks) */ getSession(sessionId: string): Session | undefined { const session = this.sessions.get(sessionId); if (!session) return undefined; // Update timestamp BEFORE validity check for atomicity const now = Date.now(); session.lastAccessedAt = now; if (!this.isValid(session)) { this.sessions.delete(sessionId); return undefined; } return session; } /** * Create a new session */ createSession( sessionId: string, transport: StreamableHTTPServerTransport, sessionData: SessionData ): void { // Defensive check for collision (extremely unlikely with UUIDs) if (this.sessions.has(sessionId)) { console.error(`[SessionManager] Warning: Session ID collision detected: ${sessionId}`); } this.sessions.set(sessionId, { transport, sessionData, createdAt: Date.now(), lastAccessedAt: Date.now() }); console.error(`[SessionManager] Created session ${sessionId} (total: ${this.sessions.size})`); } /** * Remove a session */ removeSession(sessionId: string): boolean { const removed = this.sessions.delete(sessionId); if (removed) { console.error(`[SessionManager] Removed session ${sessionId} (remaining: ${this.sessions.size})`); } return removed; } /** * Check if session exists and is valid */ hasSession(sessionId: string): boolean { const session = this.sessions.get(sessionId); return session !== undefined && this.isValid(session); } /** * Cleanup expired sessions */ cleanupExpired(): void { let cleaned = 0; for (const [sessionId, session] of this.sessions.entries()) { if (!this.isValid(session)) { try { session.transport.close(); } catch (err) { console.error(`[SessionManager] Error closing transport for ${sessionId}:`, err); } this.sessions.delete(sessionId); cleaned++; } } if (cleaned > 0) { console.error(`[SessionManager] Cleaned up ${cleaned} expired sessions (remaining: ${this.sessions.size})`); } } /** * Cleanup all sessions (for shutdown) */ cleanupAll(): void { console.error(`[SessionManager] Cleaning up all sessions (${this.sessions.size} total)`); for (const [sessionId, session] of this.sessions.entries()) { try { session.transport.close(); } catch (err) { console.error(`[SessionManager] Error closing transport for ${sessionId}:`, err); } } this.sessions.clear(); console.error('[SessionManager] All sessions cleaned up'); } /** * Get current session count */ getSessionCount(): number { return this.sessions.size; } }

Latest Blog Posts

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/robertn702/mcp-sunsama'

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