UNPKG

@pluggedin/pluggedin-mcp-proxy

Version:

Unified MCP proxy that aggregates all your MCP servers (STDIO, SSE, Streamable HTTP) into one powerful interface. Access any tool through a single connection, search across unified documents with built-in RAG, and receive notifications from any model. Tes

213 lines (212 loc) 6.73 kB
/** * Connection State Manager for Lazy Tool Loading * * Manages connection-scoped tool enablement state. * Tools are enabled per connection and cleared on disconnect. * This is NOT persistent storage - state is lost on server restart. */ import { debugLog, debugError } from './debug-log.js'; class ConnectionManager { connections = new Map(); cleanupInterval = null; CLEANUP_INTERVAL_MS = 60000; // 1 minute CONNECTION_TIMEOUT_MS = 3600000; // 1 hour constructor() { this.startCleanupInterval(); } /** * Initialize a new connection or get existing one */ initConnection(connectionId, profileUuid) { let state = this.connections.get(connectionId); if (!state) { state = { connectionId, profileUuid, enabledTools: new Set(), serverTools: new Map(), createdAt: new Date(), lastAccessed: new Date(), }; this.connections.set(connectionId, state); debugLog(`[ConnectionManager] Initialized connection: ${connectionId}`); } else { state.lastAccessed = new Date(); } return state; } /** * Get connection state */ getConnection(connectionId) { const state = this.connections.get(connectionId); if (state) { state.lastAccessed = new Date(); } return state; } /** * Enable tools for a connection */ enableTools(connectionId, toolNames, serverUuid) { const state = this.connections.get(connectionId); if (!state) { debugError(`[ConnectionManager] Connection not found: ${connectionId}`); return false; } // Add tools to enabled set toolNames.forEach(tool => { state.enabledTools.add(tool); // Track which server the tool belongs to if (serverUuid) { if (!state.serverTools.has(serverUuid)) { state.serverTools.set(serverUuid, new Set()); } state.serverTools.get(serverUuid).add(tool); } }); state.lastAccessed = new Date(); debugLog(`[ConnectionManager] Enabled ${toolNames.length} tools for connection ${connectionId}`); return true; } /** * Disable tools for a connection */ disableTools(connectionId, toolNames) { const state = this.connections.get(connectionId); if (!state) { return false; } toolNames.forEach(tool => { state.enabledTools.delete(tool); // Remove from server tracking state.serverTools.forEach((tools) => { tools.delete(tool); }); }); state.lastAccessed = new Date(); return true; } /** * Get enabled tools for a connection */ getEnabledTools(connectionId) { const state = this.connections.get(connectionId); if (!state) { return []; } state.lastAccessed = new Date(); return Array.from(state.enabledTools); } /** * Check if a tool is enabled for a connection */ isToolEnabled(connectionId, toolName) { const state = this.connections.get(connectionId); if (!state) { return false; } state.lastAccessed = new Date(); return state.enabledTools.has(toolName); } /** * Clear connection state */ clearConnection(connectionId) { if (this.connections.delete(connectionId)) { debugLog(`[ConnectionManager] Cleared connection: ${connectionId}`); } } /** * Get connection ID from request context * Handles different transport types */ getConnectionId(request) { // For STDIO transport, use process ID if (!request.transport || request.transport === 'stdio') { return `stdio_${process.pid}`; } // For HTTP/SSE transports, check headers if (request.headers) { // Check for session header const sessionId = request.headers['x-mcp-session-id']; if (sessionId) { return `http_${sessionId}`; } // Check for connection ID header const connectionId = request.headers['x-mcp-connection-id']; if (connectionId) { return connectionId; } } // For WebSocket, use socket ID if (request.socketId) { return `ws_${request.socketId}`; } // Fallback to timestamp-based ID return `unknown_${Date.now()}`; } /** * Start cleanup interval to remove stale connections */ startCleanupInterval() { this.cleanupInterval = setInterval(() => { this.cleanupStaleConnections(); }, this.CLEANUP_INTERVAL_MS); } /** * Clean up stale connections */ cleanupStaleConnections() { const now = Date.now(); const staleConnectionIds = []; this.connections.forEach((state, connectionId) => { const age = now - state.lastAccessed.getTime(); if (age > this.CONNECTION_TIMEOUT_MS) { staleConnectionIds.push(connectionId); } }); staleConnectionIds.forEach(id => { this.clearConnection(id); }); if (staleConnectionIds.length > 0) { debugLog(`[ConnectionManager] Cleaned up ${staleConnectionIds.length} stale connections`); } } /** * Shutdown the connection manager */ shutdown() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } this.connections.clear(); debugLog('[ConnectionManager] Shutdown complete'); } /** * Get statistics about connections */ getStats() { const now = Date.now(); let totalEnabledTools = 0; const connectionDetails = []; this.connections.forEach((state, connectionId) => { const toolCount = state.enabledTools.size; totalEnabledTools += toolCount; connectionDetails.push({ connectionId, toolCount, age: now - state.createdAt.getTime(), }); }); return { totalConnections: this.connections.size, totalEnabledTools, connectionDetails, }; } } // Export singleton instance export const connectionManager = new ConnectionManager();