UNPKG

n8n-nodes-mcp-session

Version:

MCP nodes for n8n with session management support

213 lines 7.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.McpSessionManager = void 0; const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js"); class McpSessionManager { constructor(sessionId) { this.client = null; this.transport = null; this.isConnected = false; this.connectionPromise = null; this.lastUsed = Date.now(); this.cleanupTimer = null; this.sessionId = sessionId; this.startCleanupTimer(); } static getInstance(sessionId) { if (!McpSessionManager.instances.has(sessionId)) { McpSessionManager.instances.set(sessionId, new McpSessionManager(sessionId)); } const instance = McpSessionManager.instances.get(sessionId); instance.lastUsed = Date.now(); return instance; } static generateSessionId(connectionConfig) { const configStr = JSON.stringify(connectionConfig); const timestamp = connectionConfig.timestamp || Date.now(); const combined = `${configStr}_${timestamp}`; let hash = 0; for (let i = 0; i < combined.length; i++) { const char = combined.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return `mcp_session_${Math.abs(hash).toString(36)}_${timestamp.toString(36)}`; } async connect(transport) { if (this.isConnected && this.client) { try { await this.validateConnection(); this.lastUsed = Date.now(); return this.client; } catch (error) { console.log(`Session ${this.sessionId} connection validation failed, reconnecting...`); this.isConnected = false; this.client = null; } } if (this.connectionPromise) { await this.connectionPromise; if (this.isConnected && this.client) { this.lastUsed = Date.now(); return this.client; } } this.connectionPromise = this.doConnect(transport); await this.connectionPromise; this.connectionPromise = null; if (!this.client) { throw new Error('Failed to establish MCP connection'); } this.lastUsed = Date.now(); return this.client; } async doConnect(transport) { try { await this.cleanup(); this.client = new index_js_1.Client({ name: 'n8n-mcp-client', version: '1.0.0', }, { capabilities: { prompts: {}, resources: {}, tools: {}, }, }); transport.onerror = (error) => { console.error(`MCP Transport error for session ${this.sessionId}:`, error.message); this.isConnected = false; this.client = null; this.transport = null; }; await this.client.connect(transport); this.transport = transport; this.isConnected = true; console.log(`MCP session ${this.sessionId} connected successfully`); } catch (error) { this.isConnected = false; this.client = null; this.transport = null; throw new Error(`Failed to connect MCP session ${this.sessionId}: ${error.message}`); } } async getClient() { if (this.isConnected && this.client) { try { await this.validateConnection(); this.lastUsed = Date.now(); return this.client; } catch (error) { console.log(`Session ${this.sessionId} validation failed during getClient`); this.isConnected = false; this.client = null; return null; } } return null; } getClientSync() { if (this.isConnected && this.client) { this.lastUsed = Date.now(); return this.client; } return null; } isSessionConnected() { return this.isConnected && this.client !== null; } async validateConnection() { if (!this.client || !this.transport) { throw new Error('No client or transport available'); } try { await this.client.listTools(); } catch (error) { throw new Error(`Connection validation failed: ${error.message}`); } } static createNewSession(connectionConfig) { const newConfig = { ...connectionConfig, timestamp: Date.now() }; return McpSessionManager.generateSessionId(newConfig); } async cleanup() { if (this.cleanupTimer) { clearTimeout(this.cleanupTimer); this.cleanupTimer = null; } if (this.transport) { try { await this.transport.close(); } catch (error) { console.error(`Error closing transport for session ${this.sessionId}:`, error); } this.transport = null; } this.client = null; this.isConnected = false; console.log(`MCP session ${this.sessionId} cleaned up`); } startCleanupTimer() { this.cleanupTimer = setTimeout(() => { this.checkAndCleanupExpiredSessions(); }, McpSessionManager.CLEANUP_INTERVAL); } checkAndCleanupExpiredSessions() { const now = Date.now(); const expiredSessions = []; for (const [sessionId, instance] of McpSessionManager.instances) { if (now - instance.lastUsed > McpSessionManager.SESSION_TIMEOUT) { expiredSessions.push(sessionId); } } for (const sessionId of expiredSessions) { const instance = McpSessionManager.instances.get(sessionId); if (instance) { instance.cleanup().catch(error => { console.error(`Error cleaning up expired session ${sessionId}:`, error); }); McpSessionManager.instances.delete(sessionId); console.log(`Expired MCP session ${sessionId} removed`); } } if (McpSessionManager.instances.size > 0) { this.startCleanupTimer(); } } static async cleanupAllSessions() { const cleanupPromises = []; for (const [, instance] of McpSessionManager.instances) { cleanupPromises.push(instance.cleanup()); } await Promise.all(cleanupPromises); McpSessionManager.instances.clear(); console.log('All MCP sessions cleaned up'); } static getSessionStats() { const now = Date.now(); const sessions = Array.from(McpSessionManager.instances.entries()).map(([sessionId, instance]) => ({ sessionId, isConnected: instance.isConnected, lastUsed: new Date(instance.lastUsed), age: now - instance.lastUsed, })); return { totalSessions: McpSessionManager.instances.size, connectedSessions: sessions.filter(s => s.isConnected).length, sessions, }; } } exports.McpSessionManager = McpSessionManager; McpSessionManager.instances = new Map(); McpSessionManager.SESSION_TIMEOUT = 30 * 60 * 1000; McpSessionManager.CLEANUP_INTERVAL = 5 * 60 * 1000; //# sourceMappingURL=McpSessionManager.js.map