UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

998 lines (997 loc) 45.6 kB
import logger from '../../logger.js'; import { sseNotifier } from '../sse-notifier/index.js'; import { websocketServer } from '../websocket-server/index.js'; import { httpAgentAPI } from '../http-agent-api/index.js'; import { PortAllocator } from '../../utils/port-allocator.js'; const DEFAULT_CONFIG = { sse: { enabled: true }, websocket: { enabled: true, port: 8080, path: '/agent-ws' }, http: { enabled: true, port: 3011, cors: true }, stdio: { enabled: true } }; const DEFAULT_PORT_RANGES = { websocket: { start: 8080, end: 8090, service: 'websocket' }, http: { start: 3011, end: 3030, service: 'http' }, sse: { start: 3000, end: 3010, service: 'sse' } }; function getPortRangesFromEnvironment() { logger.debug('Reading port ranges from environment variables with enhanced error handling'); const envVarErrors = []; const envVarWarnings = []; function safeParsePortRange(primaryVar, primaryValue, fallbackVar, fallbackValue, defaultRange, serviceName) { if (primaryValue) { try { const range = PortAllocator.parsePortRange(primaryValue, defaultRange); if (range.start === defaultRange.start && range.end === defaultRange.end && primaryValue !== `${defaultRange.start}-${defaultRange.end}` && primaryValue !== defaultRange.start.toString()) { envVarErrors.push({ variable: primaryVar, value: primaryValue, error: 'Invalid format, using default range' }); logger.warn({ variable: primaryVar, value: primaryValue, defaultUsed: `${defaultRange.start}-${defaultRange.end}`, service: serviceName }, `Invalid environment variable format for ${primaryVar}, using default`); } else { logger.debug({ variable: primaryVar, value: primaryValue, parsed: `${range.start}-${range.end}`, service: serviceName }, `Successfully parsed ${primaryVar}`); } return { range, source: primaryVar }; } catch (error) { envVarErrors.push({ variable: primaryVar, value: primaryValue, error: error instanceof Error ? error.message : 'Parse error' }); logger.error({ variable: primaryVar, value: primaryValue, error: error instanceof Error ? error.message : 'Unknown error', service: serviceName }, `Failed to parse ${primaryVar}, trying fallback`); } } if (fallbackValue) { try { const range = PortAllocator.parsePortRange(fallbackValue, defaultRange); if (range.start === defaultRange.start && range.end === defaultRange.end && fallbackValue !== `${defaultRange.start}-${defaultRange.end}` && fallbackValue !== defaultRange.start.toString()) { envVarErrors.push({ variable: fallbackVar, value: fallbackValue, error: 'Invalid format, using default range' }); logger.warn({ variable: fallbackVar, value: fallbackValue, defaultUsed: `${defaultRange.start}-${defaultRange.end}`, service: serviceName }, `Invalid environment variable format for ${fallbackVar}, using default`); } else { logger.debug({ variable: fallbackVar, value: fallbackValue, parsed: `${range.start}-${range.end}`, service: serviceName }, `Successfully parsed ${fallbackVar}`); } return { range, source: fallbackVar }; } catch (error) { envVarErrors.push({ variable: fallbackVar, value: fallbackValue, error: error instanceof Error ? error.message : 'Parse error' }); logger.error({ variable: fallbackVar, value: fallbackValue, error: error instanceof Error ? error.message : 'Unknown error', service: serviceName }, `Failed to parse ${fallbackVar}, using default`); } } logger.info({ service: serviceName, defaultRange: `${defaultRange.start}-${defaultRange.end}`, reason: 'No valid environment variables found' }, `Using default port range for ${serviceName} service`); return { range: defaultRange, source: 'default' }; } const websocketResult = safeParsePortRange('WEBSOCKET_PORT', process.env.WEBSOCKET_PORT, 'WEBSOCKET_PORT_RANGE', process.env.WEBSOCKET_PORT_RANGE, DEFAULT_PORT_RANGES.websocket, 'websocket'); const httpResult = safeParsePortRange('HTTP_AGENT_PORT', process.env.HTTP_AGENT_PORT, 'HTTP_AGENT_PORT_RANGE', process.env.HTTP_AGENT_PORT_RANGE, DEFAULT_PORT_RANGES.http, 'http'); const sseResult = safeParsePortRange('SSE_PORT', process.env.SSE_PORT, 'SSE_PORT_RANGE', process.env.SSE_PORT_RANGE, DEFAULT_PORT_RANGES.sse, 'sse'); logger.info({ websocket: { source: websocketResult.source, range: `${websocketResult.range.start}-${websocketResult.range.end}`, envVars: { WEBSOCKET_PORT: process.env.WEBSOCKET_PORT || 'not set', WEBSOCKET_PORT_RANGE: process.env.WEBSOCKET_PORT_RANGE || 'not set' } }, http: { source: httpResult.source, range: `${httpResult.range.start}-${httpResult.range.end}`, envVars: { HTTP_AGENT_PORT: process.env.HTTP_AGENT_PORT || 'not set', HTTP_AGENT_PORT_RANGE: process.env.HTTP_AGENT_PORT_RANGE || 'not set' } }, sse: { source: sseResult.source, range: `${sseResult.range.start}-${sseResult.range.end}`, envVars: { SSE_PORT: process.env.SSE_PORT || 'not set', SSE_PORT_RANGE: process.env.SSE_PORT_RANGE || 'not set' } }, errors: envVarErrors, warnings: envVarWarnings }, 'Port ranges configured from environment with enhanced error handling'); if (envVarErrors.length > 0) { logger.warn({ errorCount: envVarErrors.length, errors: envVarErrors, impact: 'Using default port ranges for affected services' }, 'Environment variable parsing errors detected'); } if (envVarWarnings.length > 0) { logger.info({ warningCount: envVarWarnings.length, warnings: envVarWarnings }, 'Environment variable parsing warnings'); } return { websocket: websocketResult.range, http: httpResult.range, sse: sseResult.range }; } function validatePortRanges(ranges) { const warnings = []; const overlaps = []; const services = Object.entries(ranges); for (let i = 0; i < services.length; i++) { for (let j = i + 1; j < services.length; j++) { const [service1Name, range1] = services[i]; const [service2Name, range2] = services[j]; const overlapStart = Math.max(range1.start, range2.start); const overlapEnd = Math.min(range1.end, range2.end); if (overlapStart <= overlapEnd) { const conflictRange = overlapStart === overlapEnd ? `${overlapStart}` : `${overlapStart}-${overlapEnd}`; overlaps.push({ service1: service1Name, service2: service2Name, conflictRange }); warnings.push(`Port range overlap detected: ${service1Name} (${range1.start}-${range1.end}) ` + `and ${service2Name} (${range2.start}-${range2.end}) conflict on ports ${conflictRange}`); } } } if (overlaps.length > 0) { logger.warn({ overlaps, warnings }, 'Port range validation found conflicts'); } else { logger.debug('Port range validation passed - no conflicts detected'); } return { valid: overlaps.length === 0, warnings, overlaps }; } class TransportManager { static instance; config; isStarted = false; startedServices = []; startupTimestamp; startupInProgress = false; startupPromise; static getInstance() { if (!TransportManager.instance) { TransportManager.instance = new TransportManager(); } return TransportManager.instance; } constructor() { this.config = { ...DEFAULT_CONFIG }; } configure(config) { this.config = { ...this.config, ...config, sse: { ...this.config.sse, ...config.sse }, websocket: { ...this.config.websocket, ...config.websocket }, http: { ...this.config.http, ...config.http }, stdio: { ...this.config.stdio, ...config.stdio } }; logger.info({ config: this.config }, 'Transport manager configured'); } reset() { this.config = { ...DEFAULT_CONFIG }; this.isStarted = false; this.startedServices = []; this.startupTimestamp = undefined; logger.debug('Transport manager reset to initial state'); } async startAll() { if (this.isStarted) { logger.warn('Transport manager already started'); return; } if (this.startupInProgress) { logger.warn('Transport manager startup already in progress, waiting...'); await this.waitForStartupCompletion(); return; } this.startupInProgress = true; this.startupPromise = (async () => { try { this.startupTimestamp = Date.now(); logger.info('Starting unified communication protocol transport services with dynamic port allocation...'); const portRanges = getPortRangesFromEnvironment(); const validation = validatePortRanges(portRanges); if (!validation.valid) { validation.warnings.forEach(warning => logger.warn(warning)); } const servicesToAllocate = []; if (this.config.websocket.enabled) { servicesToAllocate.push(portRanges.websocket); } if (this.config.http.enabled) { servicesToAllocate.push(portRanges.http); } if (this.config.sse.enabled && this.config.sse.portRange) { servicesToAllocate.push(portRanges.sse); } const allocationSummary = await PortAllocator.allocatePortsForServices(servicesToAllocate); for (const [serviceName, allocation] of allocationSummary.allocations) { if (allocation.success) { if (serviceName === 'websocket') { this.config.websocket.allocatedPort = allocation.port; } else if (serviceName === 'http') { this.config.http.allocatedPort = allocation.port; } else if (serviceName === 'sse') { this.config.sse.allocatedPort = allocation.port; } } } await this.startServicesWithAllocatedPorts(allocationSummary); this.isStarted = true; this.logStartupSummary(allocationSummary); } catch (error) { logger.error({ err: error }, 'Failed to start transport services'); await this.stopAll().catch(stopError => { logger.error({ err: stopError }, 'Failed to cleanup after startup failure'); }); throw error; } finally { this.startupInProgress = false; this.startupPromise = undefined; } })(); await this.startupPromise; } async startServicesWithAllocatedPorts(allocationSummary) { const serviceFailures = []; const serviceSuccesses = []; logger.info('Starting transport services with graceful degradation enabled'); if (this.config.stdio.enabled) { try { logger.info('stdio transport: Enabled (handled by MCP server)'); this.startedServices.push('stdio'); serviceSuccesses.push({ service: 'stdio', note: 'MCP server managed' }); } catch (error) { const failure = { service: 'stdio', reason: 'Startup failed', error }; serviceFailures.push(failure); logger.error({ err: error }, 'stdio transport: Failed to start'); } } if (this.config.sse.enabled) { try { logger.info('SSE transport: Enabled (integrated with MCP server)'); this.startedServices.push('sse'); serviceSuccesses.push({ service: 'sse', note: 'MCP server integrated' }); } catch (error) { const failure = { service: 'sse', reason: 'Startup failed', error }; serviceFailures.push(failure); logger.error({ err: error }, 'SSE transport: Failed to start'); } } if (this.config.websocket.enabled) { const allocation = allocationSummary.allocations.get('websocket'); if (allocation && allocation.success) { try { await websocketServer.start(allocation.port); logger.info({ port: allocation.port, path: this.config.websocket.path, attempted: allocation.attempted.length }, 'WebSocket transport: Started with allocated port'); this.startedServices.push('websocket'); serviceSuccesses.push({ service: 'websocket', port: allocation.port }); } catch (error) { logger.warn({ err: error, port: allocation.port, retryEnabled: true }, 'WebSocket transport: Initial startup failed, attempting retry with alternative ports'); const envPortRanges = getPortRangesFromEnvironment(); const retryRange = envPortRanges.websocket; const retryResult = await this.retryServiceStartup('websocket', retryRange); if (retryResult.success) { logger.info({ port: retryResult.port, attempts: retryResult.attempts, path: this.config.websocket.path }, 'WebSocket transport: Started successfully after retry'); this.startedServices.push('websocket'); serviceSuccesses.push({ service: 'websocket', port: retryResult.port }); } else { const failure = { service: 'websocket', reason: 'Service startup failed after retries', error }; serviceFailures.push(failure); logger.error({ attempts: retryResult.attempts, error: retryResult.error, gracefulDegradation: true }, 'WebSocket transport: Failed to start after retries, continuing with other transports'); } } } else { logger.warn({ allocation: allocation || 'none', retryEnabled: true }, 'WebSocket transport: Initial port allocation failed, attempting retry with alternative ports'); const envPortRanges = getPortRangesFromEnvironment(); const retryRange = envPortRanges.websocket; const retryResult = await this.retryServiceStartup('websocket', retryRange); if (retryResult.success) { logger.info({ port: retryResult.port, attempts: retryResult.attempts, path: this.config.websocket.path }, 'WebSocket transport: Started successfully after retry'); this.startedServices.push('websocket'); serviceSuccesses.push({ service: 'websocket', port: retryResult.port }); } else { const failure = { service: 'websocket', reason: 'Port allocation and retries failed' }; serviceFailures.push(failure); logger.warn({ attempts: retryResult.attempts, error: retryResult.error, gracefulDegradation: true }, 'WebSocket transport: Failed to allocate port after retries, continuing with other transports'); } } } if (this.config.http.enabled) { const allocation = allocationSummary.allocations.get('http'); if (allocation && allocation.success) { try { await httpAgentAPI.start(allocation.port); logger.info({ port: allocation.port, cors: this.config.http.cors, attempted: allocation.attempted.length }, 'HTTP transport: Started with allocated port'); this.startedServices.push('http'); serviceSuccesses.push({ service: 'http', port: allocation.port }); } catch (error) { logger.warn({ err: error, port: allocation.port, retryEnabled: true }, 'HTTP transport: Initial startup failed, attempting retry with alternative ports'); const envPortRanges = getPortRangesFromEnvironment(); const retryRange = envPortRanges.http; const retryResult = await this.retryServiceStartup('http', retryRange); if (retryResult.success) { logger.info({ port: retryResult.port, attempts: retryResult.attempts, cors: this.config.http.cors }, 'HTTP transport: Started successfully after retry'); this.startedServices.push('http'); serviceSuccesses.push({ service: 'http', port: retryResult.port }); } else { const failure = { service: 'http', reason: 'Service startup failed after retries', error }; serviceFailures.push(failure); logger.error({ attempts: retryResult.attempts, error: retryResult.error, gracefulDegradation: true }, 'HTTP transport: Failed to start after retries, continuing with other transports'); } } } else { logger.warn({ allocation: allocation || 'none', retryEnabled: true }, 'HTTP transport: Initial port allocation failed, attempting retry with alternative ports'); const envPortRanges = getPortRangesFromEnvironment(); const retryRange = envPortRanges.http; const retryResult = await this.retryServiceStartup('http', retryRange); if (retryResult.success) { logger.info({ port: retryResult.port, attempts: retryResult.attempts, cors: this.config.http.cors }, 'HTTP transport: Started successfully after retry'); this.startedServices.push('http'); serviceSuccesses.push({ service: 'http', port: retryResult.port }); } else { const failure = { service: 'http', reason: 'Port allocation and retries failed' }; serviceFailures.push(failure); logger.warn({ attempts: retryResult.attempts, error: retryResult.error, gracefulDegradation: true }, 'HTTP transport: Failed to allocate port after retries, continuing with other transports'); } } } this.logGracefulDegradationSummary(serviceSuccesses, serviceFailures); } logGracefulDegradationSummary(successes, failures) { const totalServices = successes.length + failures.length; const successRate = totalServices > 0 ? (successes.length / totalServices * 100).toFixed(1) : '0'; logger.info({ gracefulDegradation: { totalServices, successfulServices: successes.length, failedServices: failures.length, successRate: `${successRate}%`, availableTransports: successes.map(s => s.service), failedTransports: failures.map(f => f.service) }, serviceDetails: { successes: successes.map(s => ({ service: s.service, port: s.port || 'N/A', note: s.note || 'Network service' })), failures: failures.map(f => ({ service: f.service, reason: f.reason, hasError: !!f.error })) } }, 'Graceful degradation summary: Transport services startup completed'); if (failures.length > 0) { if (successes.length === 0) { logger.error('Critical: All transport services failed to start'); } else if (failures.some(f => f.service === 'websocket') && failures.some(f => f.service === 'http')) { logger.warn('Network transports (WebSocket + HTTP) failed, continuing with SSE + stdio only'); } else if (failures.some(f => f.service === 'websocket')) { logger.warn('WebSocket transport failed, continuing with HTTP + SSE + stdio'); } else if (failures.some(f => f.service === 'http')) { logger.warn('HTTP transport failed, continuing with WebSocket + SSE + stdio'); } } else { logger.info('All enabled transport services started successfully'); } } logStartupSummary(allocationSummary) { const successful = Array.from(allocationSummary.allocations.values()).filter(r => r.success); const attempted = allocationSummary.totalAttempted; const conflicts = []; const serviceDetails = {}; for (const [serviceName, allocation] of allocationSummary.allocations) { attempted.push(...allocation.attempted); serviceDetails[serviceName] = { status: allocation.success ? 'success' : 'failed', requested: allocation.attempted[0], allocated: allocation.success ? allocation.port : null, attempts: allocation.attempted.length, attemptedPorts: allocation.attempted, success: allocation.success, conflicts: allocation.success ? [] : allocation.attempted, reason: allocation.error }; if (allocation.success) { } else { conflicts.push(...allocation.attempted); } } const allocationStats = { totalServicesRequested: allocationSummary.allocations.size, successfulAllocations: successful.length, failedAllocations: allocationSummary.allocations.size - successful.length, successRate: (successful.length / allocationSummary.allocations.size * 100).toFixed(1), totalPortsAttempted: attempted.length, uniquePortsAttempted: [...new Set(attempted)].length, conflictedPorts: [...new Set(conflicts)], conflictCount: [...new Set(conflicts)].length }; const enhancedServiceStatus = { total: this.startedServices.length, started: this.startedServices, websocket: this.config.websocket.allocatedPort ? { port: this.config.websocket.allocatedPort, status: 'started', endpoint: `ws://localhost:${this.config.websocket.allocatedPort}${this.config.websocket.path}`, allocation: serviceDetails.websocket || null } : { status: 'failed', allocation: serviceDetails.websocket || null }, http: this.config.http.allocatedPort ? { port: this.config.http.allocatedPort, status: 'started', endpoint: `http://localhost:${this.config.http.allocatedPort}`, allocation: serviceDetails.http || null } : { status: 'failed', allocation: serviceDetails.http || null }, sse: { status: 'integrated', note: 'MCP server', port: this.config.sse.allocatedPort || 'N/A', allocation: serviceDetails.sse || null }, stdio: { status: 'enabled', note: 'MCP server', allocation: 'N/A (no network port required)' } }; logger.info({ summary: 'Transport services startup completed with dynamic port allocation', services: enhancedServiceStatus, portAllocation: { statistics: allocationStats, attempted: [...new Set(attempted)], successful, conflicts: [...new Set(conflicts)], serviceDetails }, performance: { startupTime: Date.now() - (this.startupTimestamp || Date.now()), servicesStarted: this.startedServices.length, portsAllocated: successful.length } }, 'Transport Manager: Startup Summary with Dynamic Port Allocation'); for (const [serviceName, details] of Object.entries(serviceDetails)) { if (details.success) { logger.info({ service: serviceName, requestedPort: details.requested, allocatedPort: details.allocated, attempts: details.attempts, status: 'success' }, `Port allocation successful: ${serviceName} service`); } else { logger.warn({ service: serviceName, requestedPort: details.requested, attemptedPorts: details.attemptedPorts, attempts: details.attempts, conflicts: details.conflicts, status: 'failed' }, `Port allocation failed: ${serviceName} service`); } } logger.info({ successRate: `${allocationStats.successRate}%`, successful: allocationStats.successfulAllocations, failed: allocationStats.failedAllocations, totalAttempts: allocationStats.totalPortsAttempted, conflicts: allocationStats.conflictCount }, 'Port Allocation Summary Statistics'); this.logDetailedServiceStatus(); } logDetailedServiceStatus() { logger.info('=== Transport Service Status Details ==='); if (this.config.websocket.enabled) { const wsStatus = { service: 'WebSocket', enabled: true, allocatedPort: this.config.websocket.allocatedPort, configuredPort: this.config.websocket.port, path: this.config.websocket.path, endpoint: this.config.websocket.allocatedPort ? `ws://localhost:${this.config.websocket.allocatedPort}${this.config.websocket.path}` : 'Not available', status: this.startedServices.includes('websocket') ? 'running' : 'failed', connections: this.startedServices.includes('websocket') ? (typeof websocketServer.getConnectionCount === 'function' ? websocketServer.getConnectionCount() : 0) : 0 }; logger.info(wsStatus, 'WebSocket Service Status'); } else { logger.info({ service: 'WebSocket', enabled: false }, 'WebSocket Service Status: Disabled'); } if (this.config.http.enabled) { const httpStatus = { service: 'HTTP Agent API', enabled: true, allocatedPort: this.config.http.allocatedPort, configuredPort: this.config.http.port, cors: this.config.http.cors, endpoint: this.config.http.allocatedPort ? `http://localhost:${this.config.http.allocatedPort}` : 'Not available', status: this.startedServices.includes('http') ? 'running' : 'failed' }; logger.info(httpStatus, 'HTTP Agent API Service Status'); } else { logger.info({ service: 'HTTP Agent API', enabled: false }, 'HTTP Agent API Service Status: Disabled'); } if (this.config.sse.enabled) { const sseStatus = { service: 'SSE (Server-Sent Events)', enabled: true, allocatedPort: this.config.sse.allocatedPort || 'Integrated with MCP server', endpoint: this.config.sse.allocatedPort ? `http://localhost:${this.config.sse.allocatedPort}/events` : 'Integrated with MCP server', status: this.startedServices.includes('sse') ? 'running' : 'integrated', connections: this.startedServices.includes('sse') ? (typeof sseNotifier.getConnectionCount === 'function' ? sseNotifier.getConnectionCount() : 'N/A') : 'N/A', note: 'Integrated with MCP server lifecycle' }; logger.info(sseStatus, 'SSE Service Status'); } else { logger.info({ service: 'SSE', enabled: false }, 'SSE Service Status: Disabled'); } if (this.config.stdio.enabled) { const stdioStatus = { service: 'Stdio (Standard Input/Output)', enabled: true, port: 'N/A (no network port required)', endpoint: 'stdio://mcp-server', status: this.startedServices.includes('stdio') ? 'running' : 'enabled', note: 'Handled by MCP server directly' }; logger.info(stdioStatus, 'Stdio Service Status'); } else { logger.info({ service: 'Stdio', enabled: false }, 'Stdio Service Status: Disabled'); } logger.info('=== End Transport Service Status Details ==='); } async retryServiceStartup(serviceName, originalRange, maxRetries = 3) { logger.info({ service: serviceName, originalRange: `${originalRange.start}-${originalRange.end}`, maxRetries, operation: 'service_retry_start' }, `Starting service retry for ${serviceName}`); for (let attempt = 1; attempt <= maxRetries; attempt++) { try { logger.debug({ service: serviceName, attempt, maxRetries, operation: 'service_retry_attempt' }, `Retry attempt ${attempt} for ${serviceName} service`); const retryRange = originalRange; const allocationResult = await PortAllocator.findAvailablePortInRange(retryRange); if (allocationResult.success) { if (serviceName === 'websocket') { await websocketServer.start(allocationResult.port); this.config.websocket.allocatedPort = allocationResult.port; } else if (serviceName === 'http') { await httpAgentAPI.start(allocationResult.port); this.config.http.allocatedPort = allocationResult.port; } logger.info({ service: serviceName, port: allocationResult.port, attempt, retryRange: `${retryRange.start}-${retryRange.end}`, operation: 'service_retry_success' }, `Service retry successful for ${serviceName} on attempt ${attempt}`); return { success: true, port: allocationResult.port, attempts: attempt }; } else { logger.warn({ service: serviceName, attempt, retryRange: `${retryRange.start}-${retryRange.end}`, operation: 'service_retry_port_failed' }, `Port allocation failed for ${serviceName} retry attempt ${attempt}`); } } catch (error) { logger.warn({ service: serviceName, attempt, error: error instanceof Error ? error.message : 'Unknown error', operation: 'service_retry_error' }, `Service startup failed for ${serviceName} retry attempt ${attempt}`); if (attempt === maxRetries) { return { success: false, attempts: attempt, error: error instanceof Error ? error.message : 'Unknown error' }; } } const backoffMs = Math.min(1000 * Math.pow(2, attempt - 1), 5000); logger.debug({ service: serviceName, attempt, backoffMs, operation: 'service_retry_backoff' }, `Waiting ${backoffMs}ms before next retry attempt`); await new Promise(resolve => setTimeout(resolve, backoffMs)); } return { success: false, attempts: maxRetries, error: `All ${maxRetries} retry attempts failed` }; } async stopAll() { if (!this.isStarted) { logger.warn('Transport manager not started'); return; } try { logger.info('Stopping unified communication protocol transport services...'); if (this.startedServices.includes('websocket')) { await websocketServer.stop(); logger.info('WebSocket transport: Stopped'); } if (this.startedServices.includes('http')) { await httpAgentAPI.stop(); logger.info('HTTP transport: Stopped'); } if (this.startedServices.includes('sse')) { logger.info('SSE transport: Stopped (handled by MCP server)'); } if (this.startedServices.includes('stdio')) { logger.info('stdio transport: Stopped (handled by MCP server)'); } this.isStarted = false; this.startedServices = []; logger.info('All transport services stopped successfully'); } catch (error) { logger.error({ err: error }, 'Failed to stop transport services'); throw error; } } async restart() { logger.info('Restarting transport services...'); await this.stopAll(); await this.startAll(); logger.info('Transport services restarted successfully'); } getStatus() { const serviceDetails = {}; const websocketRunning = this.startedServices.includes('websocket'); if (this.config.websocket.enabled) { serviceDetails.websocket = { port: this.config.websocket.allocatedPort || this.config.websocket.port, path: this.config.websocket.path, connections: websocketRunning && typeof websocketServer.getConnectionCount === 'function' ? websocketServer.getConnectionCount() : 0, connectedAgents: websocketRunning && typeof websocketServer.getConnectedAgents === 'function' ? websocketServer.getConnectedAgents() : [], running: websocketRunning }; } const httpRunning = this.startedServices.includes('http'); if (this.config.http.enabled) { serviceDetails.http = { port: this.config.http.allocatedPort || this.config.http.port, cors: this.config.http.cors, running: httpRunning }; } const sseRunning = this.startedServices.includes('sse'); if (this.config.sse.enabled) { serviceDetails.sse = { connections: sseRunning && typeof sseNotifier.getConnectionCount === 'function' ? sseNotifier.getConnectionCount() : 0, enabled: true, running: sseRunning }; } const stdioRunning = this.startedServices.includes('stdio'); if (this.config.stdio.enabled) { serviceDetails.stdio = { enabled: true, note: 'Handled by MCP server', running: stdioRunning }; } return { isStarted: this.isStarted, isConfigured: this.config.websocket.enabled || this.config.http.enabled || this.config.sse.enabled || this.config.stdio.enabled, startupInProgress: this.startupInProgress, startedServices: this.startedServices, config: this.config, serviceDetails, websocket: this.config.websocket.enabled ? { running: websocketRunning, port: this.config.websocket.allocatedPort || this.config.websocket.port, path: this.config.websocket.path, connections: websocketRunning && typeof websocketServer.getConnectionCount === 'function' ? websocketServer.getConnectionCount() : 0 } : undefined, http: this.config.http.enabled ? { running: httpRunning, port: this.config.http.allocatedPort || this.config.http.port, cors: this.config.http.cors } : undefined, sse: this.config.sse.enabled ? { running: sseRunning, connections: sseRunning && typeof sseNotifier.getConnectionCount === 'function' ? sseNotifier.getConnectionCount() : 0 } : undefined, stdio: this.config.stdio.enabled ? { running: stdioRunning } : undefined }; } isTransportRunning(transport) { return this.isStarted && this.startedServices.includes(transport); } getTransportConfig(transport) { return this.config[transport]; } setTransportEnabled(transport, enabled) { this.config[transport].enabled = enabled; logger.info({ transport, enabled }, 'Transport enabled status updated'); } getAllocatedPorts() { return { websocket: this.startedServices.includes('websocket') ? this.config.websocket.allocatedPort : undefined, http: this.startedServices.includes('http') ? this.config.http.allocatedPort : undefined, sse: this.startedServices.includes('sse') ? this.config.sse.allocatedPort : undefined, stdio: undefined }; } async waitForStartupCompletion() { if (this.startupPromise) { await this.startupPromise; } } getServicePort(serviceName) { switch (serviceName) { case 'websocket': return this.startedServices.includes('websocket') ? this.config.websocket.allocatedPort : undefined; case 'http': return this.startedServices.includes('http') ? this.config.http.allocatedPort : undefined; case 'sse': return this.startedServices.includes('sse') ? this.config.sse.allocatedPort : undefined; case 'stdio': return undefined; default: logger.warn({ serviceName }, 'Unknown service name for port query'); return undefined; } } getServiceEndpoints() { const endpoints = {}; if (this.startedServices.includes('websocket') && this.config.websocket.allocatedPort) { endpoints.websocket = `ws://localhost:${this.config.websocket.allocatedPort}${this.config.websocket.path}`; } if (this.startedServices.includes('http') && this.config.http.allocatedPort) { endpoints.http = `http://localhost:${this.config.http.allocatedPort}`; } if (this.startedServices.includes('sse') && this.config.sse.allocatedPort) { endpoints.sse = `http://localhost:${this.config.sse.allocatedPort}/events`; } endpoints.stdio = 'stdio://mcp-server'; return endpoints; } async getHealthStatus() { const health = {}; health.stdio = { status: this.config.stdio.enabled ? 'healthy' : 'disabled', details: { note: 'Handled by MCP server' } }; health.sse = { status: this.config.sse.enabled ? 'healthy' : 'disabled', details: { connections: this.isTransportRunning('sse') ? (typeof sseNotifier.getConnectionCount === 'function' ? sseNotifier.getConnectionCount() : 0) : 0 } }; if (this.config.websocket.enabled) { try { const connectionCount = typeof websocketServer.getConnectionCount === 'function' ? websocketServer.getConnectionCount() : 0; health.websocket = { status: this.isTransportRunning('websocket') ? 'healthy' : 'unhealthy', details: { port: this.config.websocket.port, connections: connectionCount, connectedAgents: websocketServer.getConnectedAgents().length } }; } catch (error) { health.websocket = { status: 'unhealthy', details: { error: error instanceof Error ? error.message : 'Unknown error' } }; } } else { health.websocket = { status: 'disabled' }; } if (this.config.http.enabled) { try { health.http = { status: this.isTransportRunning('http') ? 'healthy' : 'unhealthy', details: { port: this.config.http.port, cors: this.config.http.cors } }; } catch (error) { health.http = { status: 'unhealthy', details: { error: error instanceof Error ? error.message : 'Unknown error' } }; } } else { health.http = { status: 'disabled' }; } return health; } } export const transportManager = TransportManager.getInstance(); export { TransportManager };