UNPKG

n8n-mcp

Version:

Integration between n8n workflow automation and Model Context Protocol (MCP)

178 lines 6.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SSESessionManager = void 0; const logger_1 = require("./logger"); const uuid_1 = require("uuid"); class SSESessionManager { constructor() { this.clients = new Map(); this.CLEANUP_INTERVAL = 30000; this.SESSION_TIMEOUT = 300000; this.MAX_CLIENTS = 1000; this.cleanupInterval = setInterval(() => { this.cleanupInactiveSessions(); }, this.CLEANUP_INTERVAL); } registerClient(response) { if (this.clients.size >= this.MAX_CLIENTS) { logger_1.logger.error(`Maximum client limit reached: ${this.MAX_CLIENTS}`); throw new Error('Maximum concurrent connections exceeded'); } const clientId = (0, uuid_1.v4)(); const client = { id: clientId, response, lastActivity: Date.now(), isActive: true, }; this.clients.set(clientId, client); logger_1.logger.info(`SSE client registered: ${clientId} (total: ${this.clients.size})`); response.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache, no-transform', 'Connection': 'keep-alive', 'Content-Encoding': 'identity', 'X-Accel-Buffering': 'no', 'X-Content-Type-Options': 'nosniff', }); response.on('close', () => { this.removeClient(clientId); }); return clientId; } removeClient(clientId) { const client = this.clients.get(clientId); if (client) { client.isActive = false; this.clients.delete(clientId); logger_1.logger.info(`SSE client removed: ${clientId}`); } } sendToClient(clientId, message) { const client = this.clients.get(clientId); if (!client || !client.isActive) { logger_1.logger.warn(`Attempted to send to inactive client: ${clientId}`); return false; } try { const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data); let sseMessage = ''; if (message.id) { sseMessage += `id: ${message.id}\n`; } if (message.event) { sseMessage += `event: ${message.event}\n`; } const lines = data.split('\n'); for (const line of lines) { sseMessage += `data: ${line}\n`; } if (message.retry) { sseMessage += `retry: ${message.retry}\n`; } sseMessage += '\n'; client.response.write(sseMessage); client.lastActivity = Date.now(); logger_1.logger.debug(`SSE message sent to client ${clientId}`, { event: message.event, dataLength: sseMessage.length, preview: sseMessage.substring(0, 100) }); return true; } catch (error) { logger_1.logger.error(`Failed to send SSE message to client ${clientId}:`, error); this.removeClient(clientId); return false; } } sendMCPMessage(clientId, mcpMessage) { const message = { data: mcpMessage, }; if (mcpMessage.id) { message.id = String(mcpMessage.id); } return this.sendToClient(clientId, message); } broadcast(message) { const inactiveClients = []; for (const [clientId, client] of this.clients) { if (client.isActive) { const success = this.sendToClient(clientId, message); if (!success) { inactiveClients.push(clientId); } } } inactiveClients.forEach(clientId => this.removeClient(clientId)); } sendPing(clientId) { return this.sendToClient(clientId, { event: 'ping', data: { timestamp: Date.now() }, }); } pingAllClients() { for (const [clientId, client] of this.clients) { if (client.isActive) { this.sendPing(clientId); } } } cleanupInactiveSessions() { const now = Date.now(); const inactiveClients = []; for (const [clientId, client] of this.clients) { if (now - client.lastActivity > this.SESSION_TIMEOUT) { inactiveClients.push(clientId); } } if (inactiveClients.length > 0) { logger_1.logger.info(`Cleaning up ${inactiveClients.length} inactive SSE sessions`); inactiveClients.forEach(clientId => this.removeClient(clientId)); } } hasClient(clientId) { const client = this.clients.get(clientId); return client ? client.isActive : false; } getActiveClientCount() { return this.clients.size; } updateWorkflowContext(clientId, context) { const client = this.clients.get(clientId); if (!client || !client.isActive) { logger_1.logger.warn(`Attempted to update context for inactive client: ${clientId}`); return false; } client.workflowContext = { ...client.workflowContext, ...context }; logger_1.logger.info(`Updated workflow context for client ${clientId}:`, client.workflowContext); return true; } getWorkflowContext(clientId) { const client = this.clients.get(clientId); return client?.workflowContext; } shutdown() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } for (const [clientId, client] of this.clients) { try { client.response.end(); } catch (error) { } } this.clients.clear(); logger_1.logger.info('SSE session manager shut down'); } } exports.SSESessionManager = SSESessionManager; //# sourceMappingURL=sse-session-manager.js.map