@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
395 lines ⢠14 kB
JavaScript
/**
* Enhanced Error Handling Utility
* Implements MCP Design Guide Section 5.1 principles for error visibility and self-correction
*/
export class MCPError extends Error {
code;
category;
details;
context;
recoverable;
suggestedActions;
originalError;
constructor(error) {
super(error.message);
this.name = 'MCPError';
this.code = error.code;
this.category = error.category;
this.details = error.details;
this.context = error.context;
this.recoverable = error.recoverable;
this.suggestedActions = error.suggestedActions;
this.originalError = error.originalError;
}
toMCPResponse() {
return {
content: [
{
type: 'text',
text: this.formatErrorMessage(),
},
],
structured: {
error: true,
code: this.code,
category: this.category,
recoverable: this.recoverable,
details: this.details,
context: this.context,
suggestedActions: this.suggestedActions,
},
};
}
formatErrorMessage() {
let message = `ā **Error**: ${this.message}\n\n`;
message += `š **Code**: ${this.code}\n`;
message += `š **Category**: ${this.category}\n`;
if (Object.keys(this.details).length > 0) {
message += `š **Details**:\n`;
for (const [key, value] of Object.entries(this.details)) {
message += ` ⢠${key}: ${JSON.stringify(value)}\n`;
}
}
if (this.context && Object.keys(this.context).length > 0) {
message += `šÆ **Context**:\n`;
for (const [key, value] of Object.entries(this.context)) {
message += ` ⢠${key}: ${JSON.stringify(value)}\n`;
}
}
if (this.suggestedActions && this.suggestedActions.length > 0) {
message += `\nš” **Suggested Actions**:\n`;
this.suggestedActions.forEach((action, index) => {
message += `${index + 1}. ${action}\n`;
});
}
if (this.recoverable) {
message += `\nš This error is recoverable. You can retry the operation.`;
}
return message;
}
}
export class ErrorHandler {
static DEFAULT_RETRY_CONFIG = {
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
retryableErrors: ['NETWORK_ERROR', 'TIMEOUT', 'RATE_LIMIT', 'TEMPORARY_UNAVAILABLE'],
};
/**
* Wraps a function with comprehensive error handling and retry logic
*/
static async withErrorHandling(operation, context, retryConfig = {}) {
const config = { ...this.DEFAULT_RETRY_CONFIG, ...retryConfig };
let lastError;
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
try {
return await operation();
}
catch (error) {
lastError = error;
const mcpError = this.transformError(error, context);
// Don't retry non-recoverable errors
if (!mcpError.recoverable || attempt === config.maxAttempts) {
throw mcpError;
}
// Don't retry errors that aren't in the retryable list
if (!config.retryableErrors.includes(mcpError.code)) {
throw mcpError;
}
// Calculate exponential backoff delay
const delay = Math.min(config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1), config.maxDelay);
console.error(`[${context.module}:${context.tool}] Attempt ${attempt} failed, retrying in ${delay}ms:`, mcpError.message);
await this.sleep(delay);
}
}
// This should never be reached, but TypeScript requires it
throw this.transformError(lastError, context);
}
/**
* Transform generic errors into detailed MCP errors with full visibility
*/
static transformError(error, context) {
// Network/HTTP errors
if (error.message.includes('fetch') || error.message.includes('network')) {
return new MCPError({
message: `Network connection failed: ${error.message}`,
code: 'NETWORK_ERROR',
category: 'external',
details: {
originalMessage: error.message,
stack: error.stack,
},
context,
recoverable: true,
suggestedActions: [
'Check your internet connection',
'Verify the service endpoint is accessible',
'Try again in a few moments',
],
originalError: error,
});
}
// Validation errors
if (error.message.includes('required') || error.message.includes('invalid')) {
return new MCPError({
message: `Input validation failed: ${error.message}`,
code: 'VALIDATION_ERROR',
category: 'validation',
details: {
originalMessage: error.message,
providedParams: context.params,
},
context,
recoverable: true,
suggestedActions: [
'Check all required parameters are provided',
'Verify parameter types match the schema',
'Review the tool documentation for correct usage',
],
originalError: error,
});
}
// File system errors
if (error.message.includes('ENOENT') || error.message.includes('EACCES')) {
return new MCPError({
message: `File system operation failed: ${error.message}`,
code: 'FILE_SYSTEM_ERROR',
category: 'system',
details: {
originalMessage: error.message,
operation: context.tool,
},
context,
recoverable: false,
suggestedActions: [
'Check file paths are correct and accessible',
'Verify you have necessary permissions',
'Ensure the target directory exists',
],
originalError: error,
});
}
// Rate limiting
if (error.message.includes('rate limit') || error.message.includes('429')) {
return new MCPError({
message: `Rate limit exceeded: ${error.message}`,
code: 'RATE_LIMIT',
category: 'external',
details: {
originalMessage: error.message,
},
context,
recoverable: true,
suggestedActions: [
'Wait before retrying the operation',
'Reduce the frequency of requests',
'Check API quota and limits',
],
originalError: error,
});
}
// Authentication errors
if (error.message.includes('unauthorized') || error.message.includes('401')) {
return new MCPError({
message: `Authentication failed: ${error.message}`,
code: 'AUTH_ERROR',
category: 'external',
details: {
originalMessage: error.message,
},
context,
recoverable: false,
suggestedActions: [
'Check your credentials are correct',
'Verify API keys or tokens are valid',
'Ensure you have necessary permissions',
],
originalError: error,
});
}
// Generic error fallback
return new MCPError({
message: `Unexpected error in ${context.module}.${context.tool}: ${error.message}`,
code: 'UNKNOWN_ERROR',
category: 'system',
details: {
originalMessage: error.message,
stack: error.stack,
errorType: error.constructor.name,
},
context,
recoverable: false,
suggestedActions: [
'Review the error details for specific issues',
'Check system logs for additional information',
'Report this error if it persists',
],
originalError: error,
});
}
/**
* Create a validation error for schema violations
*/
static createValidationError(field, value, constraint, context) {
return new MCPError({
message: `Parameter '${field}' violates constraint: ${constraint}`,
code: 'PARAMETER_VALIDATION_ERROR',
category: 'validation',
details: {
field,
value,
constraint,
valueType: typeof value,
},
context,
recoverable: true,
suggestedActions: [
`Ensure '${field}' meets the requirement: ${constraint}`,
'Check the tool documentation for valid parameter formats',
'Verify the parameter type matches expectations',
],
});
}
/**
* Create a resource not found error
*/
static createNotFoundError(resourceType, identifier, context) {
return new MCPError({
message: `${resourceType} not found: ${identifier}`,
code: 'RESOURCE_NOT_FOUND',
category: 'validation',
details: {
resourceType,
identifier,
},
context,
recoverable: false,
suggestedActions: [
`Verify the ${resourceType} ID '${identifier}' exists`,
`Use list commands to find available ${resourceType}s`,
'Check for typos in the identifier',
],
});
}
/**
* Create a dependency error for missing prerequisites
*/
static createDependencyError(dependency, context) {
return new MCPError({
message: `Missing dependency: ${dependency}`,
code: 'DEPENDENCY_ERROR',
category: 'validation',
details: {
dependency,
tool: context.tool,
},
context,
recoverable: true,
suggestedActions: [
`Ensure ${dependency} is properly configured`,
'Check prerequisite setup steps',
'Initialize required components first',
],
});
}
static sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get detailed information about a specific error
*/
async getErrorDetails(errorId) {
// This would normally fetch from a database
return {
errorId,
type: 'validation',
severity: 'medium',
message: 'Sample error for development',
timestamp: new Date().toISOString(),
tool: 'unknown',
context: {},
stackTrace: null,
rootCause: null,
suggestions: [],
relatedErrors: []
};
}
/**
* Analyze patterns in errors
*/
async analyzeErrorPatterns(options) {
return {
totalErrors: 0,
patternsFound: [],
recommendations: [],
insights: []
};
}
/**
* Get timeline of errors
*/
async getErrorTimeline(options) {
return [];
}
/**
* Generate error report
*/
async generateErrorReport(options) {
const content = options.outputFormat === 'markdown'
? '# Error Report\n\nNo errors to report.'
: { errors: [], summary: 'No errors' };
return {
content,
summary: { totalIssues: 0, critical: 0, high: 0, medium: 0, low: 0 },
filePath: options.saveToFile ? '.atlas/reports/error-report.md' : undefined
};
}
/**
* Track error resolution
*/
async resolveErrorSuggestion(options) {
return {
errorId: options.errorId,
suggestionId: options.suggestionId,
implementation: options.implementation,
effectiveness: options.effectiveness,
notes: options.notes,
outcome: { success: true, message: 'Tracked' }
};
}
/**
* Simulate error recovery
*/
async simulateErrorRecovery(options) {
return {
errorType: options.errorType,
severity: options.severity || 'medium',
steps: [],
results: { success: true, recoverable: true },
recoveryTime: '0ms',
effectiveness: '100%'
};
}
}
/**
* Decorator for automatic error handling in tool functions
*/
export function withMCPErrorHandling(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args) {
try {
return await originalMethod.apply(this, args);
}
catch (error) {
const context = {
tool: propertyKey,
module: target.constructor.name,
params: args[0],
};
throw ErrorHandler.transformError(error, context);
}
};
return descriptor;
}
//# sourceMappingURL=error-handler.js.map