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
JavaScript
/**
* 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