UNPKG

@the_cfdude/productboard-mcp

Version:

Model Context Protocol server for Productboard REST API with dynamic tool loading

150 lines (149 loc) 5.55 kB
/** * Connection manager for handling multiple concurrent MCP connections */ class ConnectionManager { connections = new Map(); requestQueue = new Map(); activeRequests = new Map(); maxConcurrentRequests = 10; connectionTimeout = 300000; // 5 minutes /** * Register a new connection */ registerConnection(connectionId) { this.connections.set(connectionId, { id: connectionId, createdAt: new Date(), lastUsed: new Date(), requestCount: 0, isActive: true, }); // Initialize request tracking this.activeRequests.set(connectionId, 0); this.requestQueue.set(connectionId, []); // Connection registered successfully } /** * Handle incoming request with queuing and concurrency control */ async handleRequest(connectionId, requestHandler) { // Register connection if not exists if (!this.connections.has(connectionId)) { this.registerConnection(connectionId); } const connection = this.connections.get(connectionId); connection.lastUsed = new Date(); connection.requestCount++; // Check if we can process immediately const currentActive = this.activeRequests.get(connectionId) || 0; if (currentActive >= this.maxConcurrentRequests) { // Queue the request return new Promise((resolve, reject) => { const queue = this.requestQueue.get(connectionId) || []; queue.push({ resolve, reject, request: requestHandler }); this.requestQueue.set(connectionId, queue); }); } return this.executeRequest(connectionId, requestHandler); } /** * Execute a request with proper tracking */ async executeRequest(connectionId, requestHandler) { // Increment active request count const currentActive = this.activeRequests.get(connectionId) || 0; this.activeRequests.set(connectionId, currentActive + 1); try { const result = await requestHandler(); return result; } finally { // Decrement active request count const newActive = (this.activeRequests.get(connectionId) || 1) - 1; this.activeRequests.set(connectionId, newActive); // Process next queued request if any this.processQueue(connectionId); } } /** * Process queued requests for a connection */ processQueue(connectionId) { const queue = this.requestQueue.get(connectionId) || []; const currentActive = this.activeRequests.get(connectionId) || 0; if (queue.length > 0 && currentActive < this.maxConcurrentRequests) { const { resolve, reject, request } = queue.shift(); this.requestQueue.set(connectionId, queue); this.executeRequest(connectionId, request) .then((result) => resolve(result)) .catch((error) => reject(error)); } } /** * Close and cleanup a connection */ closeConnection(connectionId) { const connection = this.connections.get(connectionId); if (connection) { connection.isActive = false; // Reject any queued requests const queue = this.requestQueue.get(connectionId) || []; queue.forEach(({ reject }) => { reject(new Error('Connection closed')); }); // Cleanup this.connections.delete(connectionId); this.activeRequests.delete(connectionId); this.requestQueue.delete(connectionId); // Connection closed and cleaned up } } /** * Get connection statistics */ getStats() { const activeConnections = Array.from(this.connections.values()).filter(conn => conn.isActive).length; const totalRequests = Array.from(this.connections.values()).reduce((sum, conn) => sum + conn.requestCount, 0); const queuedRequests = Array.from(this.requestQueue.values()).reduce((sum, queue) => sum + queue.length, 0); return { totalConnections: this.connections.size, activeConnections, totalRequests, queuedRequests, }; } /** * Cleanup stale connections */ cleanupStaleConnections() { const now = new Date(); const staleConnections = []; this.connections.forEach((connection, id) => { const timeSinceLastUse = now.getTime() - connection.lastUsed.getTime(); if (timeSinceLastUse > this.connectionTimeout) { staleConnections.push(id); } }); staleConnections.forEach(id => this.closeConnection(id)); if (staleConnections.length > 0) { // Stale connections cleaned up successfully } } } // Singleton instance export const connectionManager = new ConnectionManager(); // Cleanup interval - store reference for testing let cleanupInterval = null; // Only set interval if not in test environment if (process.env.NODE_ENV !== 'test') { cleanupInterval = setInterval(() => { connectionManager.cleanupStaleConnections(); }, 60000); // Every minute } // Export function to clear interval for testing export const clearCleanupInterval = () => { if (cleanupInterval) { clearInterval(cleanupInterval); cleanupInterval = null; } };