n8n-mcp
Version:
Integration between n8n workflow automation and Model Context Protocol (MCP)
178 lines • 6.2 kB
JavaScript
"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