/**
* Session Manager
*
* Manages HTTP transport sessions for MCP server.
* Handles session lifecycle, transport storage, and cleanup.
*/
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { randomUUID } from 'crypto';
import { ILogger } from '../logging/logger.js';
/**
* Session manager for MCP transports
*/
export class SessionManager {
private readonly transports: Map<string, StreamableHTTPServerTransport> =
new Map();
private readonly logger: ILogger;
constructor(logger: ILogger) {
this.logger = logger;
}
/**
* Create a new transport with unique session ID
*
* @returns New transport instance
*/
createTransport(): StreamableHTTPServerTransport {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
this.logger.debug('Transport created', {
sessionId: transport.sessionId,
});
return transport;
}
/**
* Store a transport by session ID
*
* @param sessionId - Unique session identifier
* @param transport - Transport instance to store
*/
store(sessionId: string, transport: StreamableHTTPServerTransport): void {
if (this.transports.has(sessionId)) {
this.logger.warn('Overwriting existing transport for session', {
sessionId,
});
}
this.transports.set(sessionId, transport);
this.logger.info('Transport stored', {
sessionId,
totalSessions: this.transports.size,
});
}
/**
* Retrieve a transport by session ID
*
* @param sessionId - Session identifier
* @returns Transport instance or undefined if not found
*/
get(sessionId: string): StreamableHTTPServerTransport | undefined {
return this.transports.get(sessionId);
}
/**
* Check if a session exists
*
* @param sessionId - Session identifier
* @returns true if session exists
*/
has(sessionId: string): boolean {
return this.transports.has(sessionId);
}
/**
* Remove a transport by session ID
*
* @param sessionId - Session identifier
* @returns true if transport was removed
*/
remove(sessionId: string): boolean {
const removed = this.transports.delete(sessionId);
if (removed) {
this.logger.info('Transport removed', {
sessionId,
remainingSessions: this.transports.size,
});
}
return removed;
}
/**
* Get all active transports
*
* @returns Array of all transport instances
*/
getAllTransports(): ReadonlyArray<StreamableHTTPServerTransport> {
return Array.from(this.transports.values());
}
/**
* Get count of active sessions
*
* @returns Number of active sessions
*/
getSessionCount(): number {
return this.transports.size;
}
/**
* Clear all transports
*/
clear(): void {
const count = this.transports.size;
this.transports.clear();
this.logger.info('All transports cleared', {
clearedCount: count,
});
}
/**
* Get all session IDs
*
* @returns Array of session IDs
*/
getSessionIds(): ReadonlyArray<string> {
return Array.from(this.transports.keys());
}
}
/**
* Factory function to create session manager
*/
export function createSessionManager(logger: ILogger): SessionManager {
return new SessionManager(logger);
}