n8n-nodes-mcp-session
Version:
MCP nodes for n8n with session management support
213 lines • 7.68 kB
JavaScript
"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