UNPKG

ai-debug-local-mcp

Version:

šŸŽÆ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

491 lines (465 loc) • 19.9 kB
/** * Circuit Breaker Handler * * Provides MCP tools for managing circuit breakers, monitoring tool health, * and performing recovery operations for AI-Debug tools. */ import { BaseToolHandler } from './base-handler.js'; import { CircuitBreakerManager } from '../utils/circuit-breaker-manager.js'; import { UserFriendlyLogger } from '../utils/user-friendly-logger.js'; export class CircuitBreakerHandler extends BaseToolHandler { circuitBreakerManager; logger; tools = [ { name: 'get_circuit_breaker_status', description: 'Get the current status of all circuit breakers and tool health metrics.', inputSchema: { type: 'object', properties: { toolName: { type: 'string', description: 'Optional: Get status for a specific tool only' }, includeEvents: { type: 'boolean', default: false, description: 'Include recent circuit breaker events in the response' } } } }, { name: 'reset_circuit_breaker', description: 'Manually reset a circuit breaker to restore tool availability.', inputSchema: { type: 'object', properties: { toolName: { type: 'string', description: 'Name of the tool whose circuit breaker should be reset' }, reason: { type: 'string', default: 'Manual reset via MCP', description: 'Reason for the reset (for logging purposes)' } }, required: ['toolName'] } }, { name: 'emergency_reset_all', description: 'Emergency reset of all circuit breakers. Use when multiple tools are failing.', inputSchema: { type: 'object', properties: { reason: { type: 'string', default: 'Emergency reset all circuits', description: 'Reason for the emergency reset' }, confirm: { type: 'boolean', default: false, description: 'Confirmation flag - must be true to proceed with reset' } }, required: ['confirm'] } }, { name: 'get_unhealthy_tools', description: 'Get a list of tools that are currently unhealthy or failing.', inputSchema: { type: 'object', properties: { includeRecommendations: { type: 'boolean', default: true, description: 'Include recommendations for fixing issues' } } } }, { name: 'configure_circuit_breaker', description: 'Configure circuit breaker parameters for a specific tool.', inputSchema: { type: 'object', properties: { toolName: { type: 'string', description: 'Name of the tool to configure' }, failureThreshold: { type: 'number', minimum: 1, maximum: 20, description: 'Number of failures before opening circuit (1-20)' }, recoveryTimeoutMs: { type: 'number', minimum: 5000, maximum: 300000, description: 'Recovery timeout in milliseconds (5s-5min)' }, successThreshold: { type: 'number', minimum: 1, maximum: 10, description: 'Number of successes needed to close circuit (1-10)' } }, required: ['toolName'] } }, { name: 'get_circuit_breaker_events', description: 'Get recent circuit breaker events for debugging and analysis.', inputSchema: { type: 'object', properties: { toolName: { type: 'string', description: 'Optional: Filter events for a specific tool' }, eventType: { type: 'string', enum: ['failure', 'success', 'open', 'close', 'half-open', 'reset'], description: 'Optional: Filter by event type' }, limit: { type: 'number', default: 50, minimum: 1, maximum: 200, description: 'Maximum number of events to return' } } } } ]; constructor() { super(); this.circuitBreakerManager = new CircuitBreakerManager(); this.logger = new UserFriendlyLogger('CircuitBreakerHandler'); } async handle(toolName, args) { switch (toolName) { case 'get_circuit_breaker_status': return this.getCircuitBreakerStatus(args); case 'reset_circuit_breaker': return this.resetCircuitBreaker(args); case 'emergency_reset_all': return this.emergencyResetAll(args); case 'get_unhealthy_tools': return this.getUnhealthyTools(args); case 'configure_circuit_breaker': return this.configureCircuitBreaker(args); case 'get_circuit_breaker_events': return this.getCircuitBreakerEvents(args); default: throw new Error(`Unknown circuit breaker tool: ${toolName}`); } } /** * Get current circuit breaker status */ async getCircuitBreakerStatus(args) { const { toolName, includeEvents = false } = args; if (toolName) { // Get status for specific tool const health = this.circuitBreakerManager.getToolHealth(toolName); if (!health) { return this.createTextResponse(`āš ļø **Tool Not Found**\n\nNo circuit breaker found for tool: ${toolName}`); } return this.createToolHealthResponse(health, includeEvents); } else { // Get status for all tools const allHealth = this.circuitBreakerManager.getAllToolHealth(); const statistics = this.circuitBreakerManager.getStatistics(); const healthyTools = Array.from(allHealth.values()).filter(h => h.healthScore >= 80 && h.state.status === 'closed'); const degradedTools = Array.from(allHealth.values()).filter(h => h.healthScore >= 50 && h.healthScore < 80); const failedTools = Array.from(allHealth.values()).filter(h => h.healthScore < 50 || h.state.status === 'open'); let response = `šŸ”§ **Circuit Breaker Status Overview** **System Health:** - 🟢 Healthy: ${statistics.healthyTools} tools - 🟔 Degraded: ${statistics.degradedTools} tools - šŸ”“ Failed: ${statistics.failedTools} tools - šŸ“Š Total Events: ${statistics.totalEvents} `; if (failedTools.length > 0) { response += `**šŸ”“ Failed Tools:**\n`; for (const tool of failedTools) { response += `- **${tool.toolName}**: ${tool.state.status} (${tool.healthScore}% health)\n`; } response += '\n'; } if (degradedTools.length > 0) { response += `**🟔 Degraded Tools:**\n`; for (const tool of degradedTools) { response += `- **${tool.toolName}**: ${tool.state.status} (${tool.healthScore}% health)\n`; } response += '\n'; } if (includeEvents && statistics.recentEvents.length > 0) { response += `**šŸ“‹ Recent Events:**\n`; for (const event of statistics.recentEvents.slice(-10)) { const time = new Date(event.timestamp).toLocaleTimeString(); response += `- ${time} | ${event.toolName} | ${event.event} | ${event.context}\n`; } } response += `\nšŸ’” **Tip:** Use \`reset_circuit_breaker\` to restore failed tools, or \`get_unhealthy_tools\` for detailed recommendations.`; return this.createTextResponse(response); } } /** * Reset a specific circuit breaker */ async resetCircuitBreaker(args) { const { toolName, reason = 'Manual reset via MCP' } = args; if (!toolName) { throw new Error('toolName is required'); } const success = this.circuitBreakerManager.resetCircuit(toolName, reason); if (success) { const health = this.circuitBreakerManager.getToolHealth(toolName); return this.createTextResponse(`āœ… **Circuit Breaker Reset Successful** **Tool:** ${toolName} **Status:** ${health?.state.status || 'unknown'} **Health Score:** ${health?.healthScore || 0}% **Reason:** ${reason} The tool is now available for use. Monitor its health after the next few executions.`); } else { return this.createTextResponse(`āŒ **Circuit Breaker Reset Failed** **Tool:** ${toolName} **Error:** Circuit breaker not found or already in healthy state Use \`get_circuit_breaker_status\` to see available tools.`); } } /** * Emergency reset of all circuit breakers */ async emergencyResetAll(args) { const { reason = 'Emergency reset all circuits', confirm = false } = args; if (!confirm) { return this.createTextResponse(`āš ļø **Emergency Reset Confirmation Required** This will reset ALL circuit breakers and restore all tools to active state. **Current Status:** ${this.formatSystemOverview()} To proceed, call this tool again with \`confirm: true\`. **Warning:** Only use emergency reset when multiple tools are failing and you've addressed the underlying issues.`); } const resetCount = this.circuitBreakerManager.resetAllCircuits(reason); const statistics = this.circuitBreakerManager.getStatistics(); return this.createTextResponse(`🚨 **Emergency Reset Completed** **Tools Reset:** ${resetCount} **Reason:** ${reason} **New Status:** ${statistics.healthyTools} healthy, ${statistics.degradedTools} degraded, ${statistics.failedTools} failed All circuit breakers have been reset. Monitor tool health closely after this operation. šŸ’” **Next Steps:** 1. Check \`get_circuit_breaker_status\` to verify reset 2. Test critical tools first 3. Monitor for recurring failures`); } /** * Get unhealthy tools with recommendations */ async getUnhealthyTools(args) { const { includeRecommendations = true } = args; const unhealthyTools = this.circuitBreakerManager.getUnhealthyTools(); if (unhealthyTools.length === 0) { return this.createTextResponse(`šŸŽ‰ **All Tools Healthy** No unhealthy tools detected. All circuit breakers are functioning normally. Use \`get_circuit_breaker_status\` to see detailed health metrics.`); } let response = `🚨 **Unhealthy Tools Report** Found ${unhealthyTools.length} tools with issues:\n\n`; for (const tool of unhealthyTools) { response += `**${tool.toolName}**\n`; response += `- Status: ${this.getStatusEmoji(tool.state.status)} ${tool.state.status}\n`; response += `- Health Score: ${tool.healthScore}%\n`; response += `- Failures: ${tool.state.failureCount} | Successes: ${tool.state.successCount}\n`; if (tool.state.lastFailureTime > 0) { const lastFailure = new Date(tool.state.lastFailureTime).toLocaleString(); response += `- Last Failure: ${lastFailure}\n`; } if (includeRecommendations && tool.recommendations.length > 0) { response += `- **Recommendations:**\n`; for (const rec of tool.recommendations) { response += ` - ${rec}\n`; } } response += '\n'; } response += `šŸ’” **Actions Available:** - \`reset_circuit_breaker\` for specific tools - \`emergency_reset_all\` if multiple tools need reset - \`configure_circuit_breaker\` to adjust sensitivity`; return this.createTextResponse(response); } /** * Configure circuit breaker parameters */ async configureCircuitBreaker(args) { const { toolName, failureThreshold, recoveryTimeoutMs, successThreshold } = args; if (!toolName) { throw new Error('toolName is required'); } const config = {}; if (failureThreshold !== undefined) config.failureThreshold = failureThreshold; if (recoveryTimeoutMs !== undefined) config.recoveryTimeoutMs = recoveryTimeoutMs; if (successThreshold !== undefined) config.successThreshold = successThreshold; if (Object.keys(config).length === 0) { return this.createTextResponse(`āŒ **No Configuration Changes** No configuration parameters provided. Available parameters: - \`failureThreshold\`: Number of failures before opening circuit - \`recoveryTimeoutMs\`: Recovery timeout in milliseconds - \`successThreshold\`: Successes needed to close circuit`); } this.circuitBreakerManager.updateConfig(toolName, config); const updatedHealth = this.circuitBreakerManager.getToolHealth(toolName); return this.createTextResponse(`āœ… **Circuit Breaker Configuration Updated** **Tool:** ${toolName} **Updated Parameters:** ${Object.keys(config).join(', ')} **Current Status:** ${updatedHealth?.state.status || 'unknown'} **Health Score:** ${updatedHealth?.healthScore || 0}% Configuration changes will take effect immediately.`); } /** * Get circuit breaker events */ async getCircuitBreakerEvents(args) { const { toolName, eventType, limit = 50 } = args; const statistics = this.circuitBreakerManager.getStatistics(); let events = statistics.recentEvents; // Apply filters if (toolName) { events = events.filter(e => e.toolName === toolName); } if (eventType) { events = events.filter(e => e.event === eventType); } // Limit results events = events.slice(-limit); if (events.length === 0) { return this.createTextResponse(`šŸ“‹ **No Events Found** No circuit breaker events match your criteria. **Filters Applied:** ${toolName ? `- Tool: ${toolName}\n` : ''}${eventType ? `- Event Type: ${eventType}\n` : ''}- Limit: ${limit} Try adjusting your filters or check \`get_circuit_breaker_status\` for current state.`); } let response = `šŸ“‹ **Circuit Breaker Events** Found ${events.length} events:\n\n`; for (const event of events.reverse()) { const time = new Date(event.timestamp).toLocaleString(); const emoji = this.getEventEmoji(event.event); response += `${emoji} **${time}** | ${event.toolName}\n`; response += ` Event: ${event.event} | Context: ${event.context}\n`; if (event.metadata) { if (event.metadata.error) { response += ` Error: ${event.metadata.error.substring(0, 100)}${event.metadata.error.length > 100 ? '...' : ''}\n`; } if (event.metadata.responseTime) { response += ` Response Time: ${event.metadata.responseTime}ms\n`; } } response += '\n'; } return this.createTextResponse(response); } // Helper methods createToolHealthResponse(health, includeEvents) { const { toolName, state, healthScore, availabilityRate, recommendations } = health; let response = `šŸ”§ **Circuit Breaker Status: ${toolName}** **Current State:** ${this.getStatusEmoji(state.status)} ${state.status} **Health Score:** ${healthScore}% ${this.getHealthEmoji(healthScore)} **Availability Rate:** ${availabilityRate.toFixed(1)}% **Statistics:** - Total Attempts: ${state.totalAttempts} - Failures: ${state.failureCount} - Successes: ${state.successCount} - Recent Failures: ${state.recentFailures.length} `; if (state.lastFailureTime > 0) { response += `**Last Failure:** ${new Date(state.lastFailureTime).toLocaleString()}\n`; } if (state.lastSuccessTime > 0) { response += `**Last Success:** ${new Date(state.lastSuccessTime).toLocaleString()}\n`; } if (state.status === 'open' && state.nextAttemptTime > 0) { response += `**Next Retry:** ${new Date(state.nextAttemptTime).toLocaleString()}\n`; } if (recommendations.length > 0) { response += `\n**Recommendations:**\n`; for (const rec of recommendations) { response += `- ${rec}\n`; } } if (includeEvents) { const statistics = this.circuitBreakerManager.getStatistics(); const toolEvents = statistics.recentEvents .filter(e => e.toolName === toolName) .slice(-5); if (toolEvents.length > 0) { response += `\n**Recent Events:**\n`; for (const event of toolEvents) { const time = new Date(event.timestamp).toLocaleTimeString(); response += `- ${time} | ${event.event} | ${event.context}\n`; } } } return this.createTextResponse(response); } formatSystemOverview() { const statistics = this.circuitBreakerManager.getStatistics(); return `- 🟢 Healthy: ${statistics.healthyTools} tools - 🟔 Degraded: ${statistics.degradedTools} tools - šŸ”“ Failed: ${statistics.failedTools} tools`; } getStatusEmoji(status) { switch (status) { case 'closed': return '🟢'; case 'open': return 'šŸ”“'; case 'half-open': return '🟔'; default: return '⚪'; } } getHealthEmoji(score) { if (score >= 80) return '🟢'; if (score >= 50) return '🟔'; return 'šŸ”“'; } getEventEmoji(event) { switch (event) { case 'success': return 'āœ…'; case 'failure': return 'āŒ'; case 'open': return 'šŸ”“'; case 'close': return '🟢'; case 'half-open': return '🟔'; case 'reset': return 'šŸ”„'; default: return 'šŸ“‹'; } } /** * Get the circuit breaker manager instance (for integration with other handlers) */ getCircuitBreakerManager() { return this.circuitBreakerManager; } /** * Cleanup resources */ destroy() { this.circuitBreakerManager.destroy(); } } //# sourceMappingURL=circuit-breaker-handler.js.map