UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

835 lines 32.8 kB
import { randomUUID } from 'crypto'; import { createTool, createSuccessResult, createErrorResult } from '../../core/tool-framework.js'; /** * Get detailed error information */ const getErrorDetailsTool = createTool({ name: 'get_error_details', description: 'Get detailed information about a specific error including context, root cause analysis, and recovery suggestions.', category: 'error-analysis', inputSchema: { type: 'object', properties: { errorId: { type: 'string', description: 'Unique identifier for the error to analyze', minLength: 1 }, includeContext: { type: 'boolean', default: true, description: 'Include detailed error context and environment information' }, includeSuggestions: { type: 'boolean', default: true, description: 'Include recovery suggestions and next steps' } }, required: ['errorId'], additionalProperties: false }, async execute(input, context) { try { // Check if error exists const errorResult = await context.db.get(`SELECT * FROM error_logs WHERE id = ? AND project_id = ?`, [input.errorId, context.projectId || 'default']); if (!errorResult.success || !errorResult.data) { return createErrorResult({ code: 'RESOURCE_NOT_FOUND', message: 'Error not found', details: { errorId: input.errorId }, category: 'validation' }); } const error = errorResult.data; // Parse JSON fields const errorData = { errorId: error.id, type: error.error_type, severity: error.severity, message: error.message, timestamp: new Date(error.timestamp).toISOString(), tool: error.tool_name, context: JSON.parse(error.context || '{}'), stackTrace: error.stack_trace, rootCause: JSON.parse(error.root_cause || '{}'), suggestions: JSON.parse(error.suggestions || '[]'), relatedErrors: [] }; // Get related errors if needed if (input.includeContext) { const relatedResult = await context.db.query(`SELECT id, error_type, message, timestamp FROM error_logs WHERE project_id = ? AND tool_name = ? AND id != ? AND timestamp BETWEEN ? AND ? ORDER BY timestamp DESC LIMIT 5`, [ context.projectId || 'default', error.tool_name, input.errorId, error.timestamp - 3600000, // 1 hour before error.timestamp + 3600000 // 1 hour after ]); if (relatedResult.success && relatedResult.data) { errorData.relatedErrors = relatedResult.data.map((e) => ({ id: e.id, type: e.error_type, message: e.message, timestamp: new Date(e.timestamp).toISOString() })); } } return createSuccessResult({ errorDetails: errorData, message: 'Error details retrieved successfully' }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to get error details: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Analyze error patterns */ const analyzeErrorPatternsTool = createTool({ name: 'analyze_error_patterns', description: 'Analyze error patterns and trends to identify recurring issues and recommend systematic improvements.', category: 'error-analysis', inputSchema: { type: 'object', properties: { timeRange: { type: 'string', enum: ['1h', '24h', '7d', '30d'], default: '24h', description: 'Time range for error pattern analysis' }, errorTypes: { type: 'array', items: { type: 'string', enum: ['validation', 'network', 'permission', 'resource', 'logic', 'external'] }, description: 'Filter analysis by specific error types' }, minOccurrences: { type: 'number', minimum: 1, default: 2, description: 'Minimum occurrences required to identify a pattern' }, groupBy: { type: 'string', enum: ['type', 'tool', 'time', 'severity'], default: 'type', description: 'How to group errors for pattern analysis' } }, required: [], additionalProperties: false }, async execute(input, context) { try { const now = Date.now(); const timeRangeMs = { '1h': 3600000, '24h': 86400000, '7d': 604800000, '30d': 2592000000 }[input.timeRange || '24h']; const startTime = now - timeRangeMs; // Build query based on filters let query = ` SELECT error_type, tool_name, severity, COUNT(*) as count, COUNT(DISTINCT tool_name) as affected_tools_count, GROUP_CONCAT(DISTINCT tool_name) as affected_tools, AVG(CASE WHEN recovery_successful THEN 1 ELSE 0 END) as recovery_rate FROM error_logs WHERE project_id = ? AND timestamp >= ? `; const params = [context.projectId || 'default', startTime]; if (input.errorTypes && input.errorTypes.length > 0) { query += ` AND error_type IN (${input.errorTypes.map(() => '?').join(',')})`; params.push(...input.errorTypes); } const groupByColumn = { 'type': 'error_type', 'tool': 'tool_name', 'severity': 'severity', 'time': "strftime('%Y-%m-%d %H:00:00', timestamp/1000, 'unixepoch')" }[input.groupBy || 'type']; query += ` GROUP BY ${groupByColumn}`; query += ` HAVING COUNT(*) >= ?`; params.push(input.minOccurrences || 2); query += ` ORDER BY count DESC`; const patternsResult = await context.db.query(query, params); if (!patternsResult.success) { throw new Error('Failed to analyze error patterns'); } // Get total error count const totalResult = await context.db.get(`SELECT COUNT(*) as total FROM error_logs WHERE project_id = ? AND timestamp >= ?`, [context.projectId || 'default', startTime]); const totalErrors = totalResult.data?.total || 0; const patterns = (patternsResult.data || []).map((p) => ({ title: p.error_type || p.tool_name || p.severity, count: p.count, frequency: p.count > 10 ? 'Frequent' : p.count > 5 ? 'Regular' : 'Occasional', severity: p.severity || 'medium', description: `${p.count} occurrences in ${input.timeRange}`, affectedTools: p.affected_tools ? p.affected_tools.split(',') : [], recoveryRate: p.recovery_rate || 0 })); // Generate recommendations based on patterns const recommendations = []; for (const pattern of patterns) { if (pattern.recoveryRate < 0.5) { recommendations.push({ title: `Improve recovery for ${pattern.title} errors`, priority: 'high', impact: 'High', action: 'Implement better error recovery mechanisms' }); } if (pattern.count > totalErrors * 0.3) { recommendations.push({ title: `Address frequent ${pattern.title} errors`, priority: 'critical', impact: 'Critical', action: 'This error type represents over 30% of all errors' }); } } return createSuccessResult({ totalErrors, patternsFound: patterns, recommendations, insights: patterns.length > 0 ? [ `Found ${patterns.length} error patterns in the last ${input.timeRange}`, `Most common error type: ${patterns[0]?.title || 'None'}` ] : ['No significant error patterns detected'], timeRange: input.timeRange, groupBy: input.groupBy }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to analyze error patterns: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Get error timeline */ const getErrorTimelineTool = createTool({ name: 'get_error_timeline', description: 'Get chronological timeline of errors with context about system state and user actions.', category: 'error-analysis', readOnly: true, inputSchema: { type: 'object', properties: { timeRange: { type: 'string', enum: ['1h', '6h', '24h', '7d'], default: '24h', description: 'Time range for error timeline' }, toolName: { type: 'string', description: 'Filter timeline to specific tool (optional)' }, severity: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'Minimum severity level to include' }, includeContext: { type: 'boolean', default: true, description: 'Include system context and user actions around each error' } }, required: [], additionalProperties: false }, async execute(input, context) { try { const now = Date.now(); const timeRangeMs = { '1h': 3600000, '6h': 21600000, '24h': 86400000, '7d': 604800000 }[input.timeRange || '24h']; const startTime = now - timeRangeMs; let query = ` SELECT id, timestamp, tool_name, error_type, severity, message, context, recovery_method, recovery_successful FROM error_logs WHERE project_id = ? AND timestamp >= ? `; const params = [context.projectId || 'default', startTime]; if (input.toolName) { query += ` AND tool_name = ?`; params.push(input.toolName); } if (input.severity) { const severityOrder = ['low', 'medium', 'high', 'critical']; const minIndex = severityOrder.indexOf(input.severity); const validSeverities = severityOrder.slice(minIndex); query += ` AND severity IN (${validSeverities.map(() => '?').join(',')})`; params.push(...validSeverities); } query += ` ORDER BY timestamp DESC LIMIT 100`; const timelineResult = await context.db.query(query, params); if (!timelineResult.success) { throw new Error('Failed to get error timeline'); } const timeline = (timelineResult.data || []).map((entry) => ({ timestamp: new Date(entry.timestamp).toISOString(), tool: entry.tool_name, type: entry.error_type, severity: entry.severity, message: entry.message, context: input.includeContext ? JSON.parse(entry.context || '{}') : undefined, recovery: entry.recovery_successful ? (entry.recovery_method || 'Automatic recovery') : 'No recovery attempted' })); return createSuccessResult({ timeline, count: timeline.length, timeRange: input.timeRange, filters: { toolName: input.toolName || null, severity: input.severity || null } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to get error timeline: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Generate error report */ const generateErrorReportTool = createTool({ name: 'generate_error_report', description: 'Generate comprehensive error analysis report for debugging, monitoring, or team review.', category: 'error-analysis', inputSchema: { type: 'object', properties: { reportType: { type: 'string', enum: ['summary', 'detailed', 'debugging', 'trends'], default: 'summary', description: 'Type of error report to generate' }, timeRange: { type: 'string', enum: ['24h', '7d', '30d'], default: '24h', description: 'Time range for report data' }, includeRecommendations: { type: 'boolean', default: true, description: 'Include improvement recommendations' }, outputFormat: { type: 'string', enum: ['markdown', 'json', 'csv'], default: 'markdown', description: 'Format for the generated report' }, saveToFile: { type: 'boolean', default: false, description: 'Save report to file in .atlas/reports/' } }, required: [], additionalProperties: false }, async execute(input, context) { try { const now = Date.now(); const timeRangeMs = { '24h': 86400000, '7d': 604800000, '30d': 2592000000 }[input.timeRange || '24h']; const startTime = now - timeRangeMs; // Get summary statistics const summaryResult = await context.db.get(`SELECT COUNT(*) as total_errors, COUNT(DISTINCT tool_name) as affected_tools, COUNT(DISTINCT error_type) as error_types, SUM(CASE WHEN severity = 'critical' THEN 1 ELSE 0 END) as critical_errors, SUM(CASE WHEN severity = 'high' THEN 1 ELSE 0 END) as high_errors, SUM(CASE WHEN recovery_successful = 1 THEN 1 ELSE 0 END) as recovered_errors FROM error_logs WHERE project_id = ? AND timestamp >= ?`, [context.projectId || 'default', startTime]); const summary = summaryResult.data || {}; // Get top errors by type const topErrorsResult = await context.db.query(`SELECT error_type, COUNT(*) as count, AVG(CASE WHEN recovery_successful THEN 1 ELSE 0 END) as recovery_rate FROM error_logs WHERE project_id = ? AND timestamp >= ? GROUP BY error_type ORDER BY count DESC LIMIT 5`, [context.projectId || 'default', startTime]); let content; if (input.outputFormat === 'markdown') { content = generateMarkdownReport(summary, topErrorsResult.data || [], input.reportType || 'summary', input.timeRange || '24h', input.includeRecommendations || true); } else if (input.outputFormat === 'json') { content = { summary, topErrors: topErrorsResult.data || [], timeRange: input.timeRange, generatedAt: new Date().toISOString() }; } else { // CSV format const headers = ['Type', 'Count', 'Recovery Rate']; const rows = (topErrorsResult.data || []).map((e) => [e.error_type, e.count, `${Math.round(e.recovery_rate * 100)}%`]); content = [headers, ...rows].map(row => row.join(',')).join('\n'); } let filePath; if (input.saveToFile) { // In a real implementation, this would save to the file system // For now, we'll just indicate where it would be saved const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const extension = input.outputFormat === 'markdown' ? 'md' : input.outputFormat; filePath = `.atlas/reports/error-report-${timestamp}.${extension}`; } return createSuccessResult({ content, filePath, reportType: input.reportType, timeRange: input.timeRange, outputFormat: input.outputFormat, summary: { totalErrors: summary.total_errors || 0, criticalErrors: summary.critical_errors || 0, recoveryRate: summary.total_errors > 0 ? (summary.recovered_errors || 0) / summary.total_errors : 0 } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to generate error report: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Resolve error suggestion */ const resolveErrorSuggestionTool = createTool({ name: 'resolve_error_suggestion', description: 'Mark an error resolution suggestion as implemented and track its effectiveness.', category: 'error-analysis', inputSchema: { type: 'object', properties: { errorId: { type: 'string', description: 'ID of the error that was addressed', minLength: 1 }, suggestionId: { type: 'string', description: 'ID of the suggestion that was implemented', minLength: 1 }, implementation: { type: 'string', description: 'Description of how the suggestion was implemented' }, effectiveness: { type: 'number', minimum: 1, maximum: 10, description: 'Effectiveness rating (1-10) of the implemented solution' }, notes: { type: 'string', description: 'Additional notes about the implementation' } }, required: ['errorId', 'suggestionId'], additionalProperties: false }, async execute(input, context) { try { const resolutionId = randomUUID(); const now = Date.now(); // Insert resolution record const result = await context.db.run(`INSERT INTO error_resolutions (id, error_id, suggestion_id, project_id, implementation, effectiveness, notes, resolved_by, resolved_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ resolutionId, input.errorId, input.suggestionId, context.projectId || 'default', input.implementation || '', input.effectiveness || null, input.notes || '', context.userId || 'system', now, now ]); if (!result.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to track error resolution', details: { error: result.error }, category: 'system' }); } // Update error log to mark as resolved await context.db.run(`UPDATE error_logs SET resolution_id = ? WHERE id = ?`, [resolutionId, input.errorId]); return createSuccessResult({ errorId: input.errorId, suggestionId: input.suggestionId, resolutionId, implementation: input.implementation, effectiveness: input.effectiveness, notes: input.notes, outcome: { success: true, message: 'Resolution tracked successfully' } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to resolve error suggestion: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Simulate error recovery */ const simulateErrorRecoveryTool = createTool({ name: 'simulate_error_recovery', description: 'Simulate error recovery procedures to test and validate error handling mechanisms.', category: 'error-analysis', inputSchema: { type: 'object', properties: { errorType: { type: 'string', enum: ['validation', 'network', 'permission', 'resource', 'logic', 'external'], description: 'Type of error to simulate' }, severity: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], default: 'medium', description: 'Severity level for simulation' }, context: { type: 'object', description: 'Simulation context and parameters' }, dryRun: { type: 'boolean', default: true, description: 'Run simulation without triggering actual errors' } }, required: ['errorType'], additionalProperties: false }, async execute(input, context) { try { const simulationId = randomUUID(); const startTime = Date.now(); // Define recovery steps based on error type const recoverySteps = getRecoverySteps(input.errorType, input.severity || 'medium'); const steps = []; let overallSuccess = true; // Execute simulation steps for (const step of recoverySteps) { const stepStart = Date.now(); const stepResult = { name: step.name, action: step.action, success: true, simulated: input.dryRun || true, duration: 0, message: '' }; // Simulate step execution await new Promise(resolve => setTimeout(resolve, step.simulatedDuration || 100)); stepResult.duration = Date.now() - stepStart; // Simulate potential failures based on error type and severity if (!input.dryRun && Math.random() < step.failureChance) { stepResult.success = false; stepResult.message = `Step failed: ${step.failureMessage}`; overallSuccess = false; } steps.push(stepResult); // Stop if step failed and it's critical if (!stepResult.success && step.critical) { break; } } const totalDuration = Date.now() - startTime; const effectiveness = overallSuccess ? 95 : 50; // Log simulation await context.db.run(`INSERT INTO error_simulations (id, project_id, error_type, severity, dry_run, steps, success, duration, effectiveness, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ simulationId, context.projectId || 'default', input.errorType, input.severity || 'medium', input.dryRun ? 1 : 0, JSON.stringify(steps), overallSuccess ? 1 : 0, totalDuration, effectiveness, Date.now() ]); return createSuccessResult({ errorType: input.errorType, severity: input.severity || 'medium', steps, results: { success: overallSuccess, recoverable: true, issues: steps.filter(s => !s.success).map(s => s.message), recommendations: overallSuccess ? ['Recovery mechanisms are working correctly'] : ['Review failed recovery steps', 'Implement fallback mechanisms'] }, recoveryTime: `${totalDuration}ms`, effectiveness: `${effectiveness}%`, dryRun: input.dryRun || true }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to simulate error recovery: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); // Helper functions function generateMarkdownReport(summary, topErrors, reportType, timeRange, includeRecommendations) { let report = `# Error Analysis Report\n\n`; report += `**Time Range**: ${timeRange}\n`; report += `**Report Type**: ${reportType}\n`; report += `**Generated**: ${new Date().toISOString()}\n\n`; report += `## Summary\n`; report += `- **Total Errors**: ${summary.total_errors || 0}\n`; report += `- **Critical Errors**: ${summary.critical_errors || 0}\n`; report += `- **High Priority Errors**: ${summary.high_errors || 0}\n`; report += `- **Affected Tools**: ${summary.affected_tools || 0}\n`; report += `- **Error Types**: ${summary.error_types || 0}\n`; report += `- **Recovery Rate**: ${summary.total_errors > 0 ? Math.round((summary.recovered_errors || 0) / summary.total_errors * 100) : 0}%\n\n`; if (topErrors.length > 0) { report += `## Top Error Types\n`; topErrors.forEach((error, i) => { report += `${i + 1}. **${error.error_type}** - ${error.count} occurrences`; report += ` (${Math.round(error.recovery_rate * 100)}% recovery rate)\n`; }); report += '\n'; } if (includeRecommendations) { report += `## Recommendations\n`; if (summary.critical_errors > 0) { report += `- Address ${summary.critical_errors} critical errors immediately\n`; } if (summary.total_errors > 0 && (summary.recovered_errors || 0) / summary.total_errors < 0.8) { report += `- Improve error recovery mechanisms (current rate: ${Math.round((summary.recovered_errors || 0) / summary.total_errors * 100)}%)\n`; } if (topErrors.length > 0 && topErrors[0].count > summary.total_errors * 0.3) { report += `- Focus on reducing ${topErrors[0].error_type} errors (${Math.round(topErrors[0].count / summary.total_errors * 100)}% of all errors)\n`; } } return report; } function getRecoverySteps(errorType, severity) { const baseSteps = [ { name: 'Detection', action: 'Detect and classify error', simulatedDuration: 50, failureChance: 0.05, failureMessage: 'Error detection failed', critical: true }, { name: 'Isolation', action: 'Isolate affected components', simulatedDuration: 100, failureChance: 0.1, failureMessage: 'Component isolation failed', critical: false } ]; const typeSpecificSteps = { validation: [ { name: 'Data Cleanup', action: 'Clean and validate input data', simulatedDuration: 150, failureChance: 0.15, failureMessage: 'Data cleanup failed', critical: false } ], network: [ { name: 'Connection Retry', action: 'Retry network connection', simulatedDuration: 200, failureChance: 0.2, failureMessage: 'Connection retry failed', critical: false }, { name: 'Fallback', action: 'Switch to fallback endpoint', simulatedDuration: 100, failureChance: 0.1, failureMessage: 'Fallback activation failed', critical: false } ], permission: [ { name: 'Permission Check', action: 'Re-validate permissions', simulatedDuration: 100, failureChance: 0.1, failureMessage: 'Permission validation failed', critical: true } ], resource: [ { name: 'Resource Release', action: 'Release locked resources', simulatedDuration: 150, failureChance: 0.15, failureMessage: 'Resource release failed', critical: false }, { name: 'Resource Allocation', action: 'Allocate new resources', simulatedDuration: 200, failureChance: 0.2, failureMessage: 'Resource allocation failed', critical: true } ], logic: [ { name: 'State Reset', action: 'Reset to known good state', simulatedDuration: 100, failureChance: 0.1, failureMessage: 'State reset failed', critical: false } ], external: [ { name: 'External Service Check', action: 'Verify external service status', simulatedDuration: 300, failureChance: 0.25, failureMessage: 'External service unavailable', critical: false } ] }; const finalSteps = [ { name: 'Verification', action: 'Verify system recovery', simulatedDuration: 100, failureChance: 0.05, failureMessage: 'Recovery verification failed', critical: true }, { name: 'Logging', action: 'Log recovery details', simulatedDuration: 50, failureChance: 0.02, failureMessage: 'Logging failed', critical: false } ]; return [ ...baseSteps, ...(typeSpecificSteps[errorType] || []), ...finalSteps ]; } /** * Setup error analysis tools */ export async function setupErrorAnalysisTools() { return { module: 'error-analysis', tools: [ getErrorDetailsTool, analyzeErrorPatternsTool, getErrorTimelineTool, generateErrorReportTool, resolveErrorSuggestionTool, simulateErrorRecoveryTool ] }; } //# sourceMappingURL=tools.js.map