Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
error-handler.ts•7.83 kB
/** * Shared service for consistent error handling and user-friendly error messages * Consolidates error handling patterns and provides contextual help */ import { OutputFormatter } from './output-formatter.js'; import { logger } from '../utils/logger.js'; import chalk from 'chalk'; export interface ErrorContext { component: string; operation: string; userInput?: string; suggestions?: string[]; } export interface ErrorResult { success: false; error: string; context?: ErrorContext; suggestions?: string[]; } export interface SuccessResult<T = any> { success: true; data: T; } export type Result<T = any> = SuccessResult<T> | ErrorResult; export class ErrorHandler { /** * Handle and format errors consistently */ static handle(error: Error | string, context?: ErrorContext): ErrorResult { const errorMessage = typeof error === 'string' ? error : error.message; // Log the error for debugging logger.debug(`Error in ${context?.component || 'unknown'}.${context?.operation || 'unknown'}: ${errorMessage}`); // Determine user-friendly error message const friendlyMessage = this.getFriendlyMessage(errorMessage, context); const suggestions = this.getSuggestions(errorMessage, context); return { success: false, error: friendlyMessage, context, suggestions }; } /** * Wrap async operations with consistent error handling */ static async wrap<T>( operation: () => Promise<T>, context: ErrorContext ): Promise<Result<T>> { try { const data = await operation(); return { success: true, data }; } catch (error) { return this.handle(error as Error, context); } } /** * Wrap sync operations with consistent error handling */ static wrapSync<T>( operation: () => T, context: ErrorContext ): Result<T> { try { const data = operation(); return { success: true, data }; } catch (error) { return this.handle(error as Error, context); } } /** * Convert technical error messages to user-friendly ones */ private static getFriendlyMessage(error: string, context?: ErrorContext): string { // File system errors if (error.includes('ENOENT')) { return `File or directory not found${context?.userInput ? `: ${context.userInput}` : ''}`; } if (error.includes('EACCES') || error.includes('permission denied')) { return `Permission denied${context?.userInput ? ` for: ${context.userInput}` : ''}`; } if (error.includes('EISDIR')) { return `Expected a file but found a directory${context?.userInput ? `: ${context.userInput}` : ''}`; } // Network errors if (error.includes('ECONNREFUSED') || error.includes('connect ECONNREFUSED')) { return 'Connection refused - service may not be running'; } if (error.includes('ETIMEDOUT') || error.includes('timeout')) { return 'Operation timed out - please try again'; } if (error.includes('ENOTFOUND')) { return 'Host not found - check your internet connection'; } // MCP-specific errors if (error.includes('Unknown tool')) { const toolName = this.extractToolName(error); return `Tool "${toolName}" not found`; } if (error.includes('MCP') && error.includes('not found')) { return 'MCP server not found in current profile'; } if (error.includes('not in allowed directories')) { return 'Access denied - path not in allowed directories'; } // JSON/Parsing errors if (error.includes('JSON') && error.includes('parse')) { return 'Invalid JSON format in configuration'; } // Configuration errors if (error.includes('Profile') && error.includes('not found')) { return `Profile not found${context?.userInput ? `: ${context.userInput}` : ''}`; } // Default: return original message but cleaned up return this.cleanErrorMessage(error); } /** * Generate helpful suggestions based on error type */ private static getSuggestions(error: string, context?: ErrorContext): string[] { const suggestions: string[] = []; // File not found suggestions if (error.includes('ENOENT') || error.includes('not found')) { suggestions.push('Check the file path and ensure it exists'); if (context?.userInput) { suggestions.push(`Verify the spelling of "${context.userInput}"`); } } // Permission denied suggestions if (error.includes('EACCES') || error.includes('permission denied')) { suggestions.push('Check file permissions'); suggestions.push('Try running with appropriate permissions'); } // Tool not found suggestions if (error.includes('Unknown tool')) { const [mcpName] = (context?.userInput || '').split(':'); if (mcpName) { suggestions.push(`Try 'ncp find "${mcpName}"' to see available tools`); } suggestions.push('Use \'ncp find\' to explore all available tools'); } // Connection errors if (error.includes('ECONNREFUSED') || error.includes('connect ECONNREFUSED')) { suggestions.push('Ensure the MCP server is running'); suggestions.push('Check your configuration'); } // Configuration suggestions if (error.includes('Profile') && error.includes('not found')) { suggestions.push('Use \'ncp list\' to see available profiles'); suggestions.push('Check your profile configuration'); } // Add context-specific suggestions if (context?.suggestions) { suggestions.push(...context.suggestions); } return suggestions; } /** * Extract tool name from error message */ private static extractToolName(error: string): string { const match = error.match(/Unknown tool[:\s]+([^"\s]+)/); return match?.[1] || 'unknown'; } /** * Clean up technical error messages */ private static cleanErrorMessage(error: string): string { return error .replace(/^Error:\s*/, '') // Remove "Error:" prefix .replace(/\s+/g, ' ') // Normalize whitespace .trim(); } /** * Format error for console output */ static formatForConsole(result: ErrorResult): string { let output = OutputFormatter.error(result.error); if (result.suggestions && result.suggestions.length > 0) { output += '\n\n' + result.suggestions.map(s => { // If suggestion already starts with emoji or bullet, use as-is with blue color if (s.startsWith('💡') || s.startsWith(' •')) { return chalk.blue(s); } // Otherwise, format as a tip return OutputFormatter.tip(s); }).join('\n'); } return output; } /** * Create context for error handling */ static createContext( component: string, operation: string, userInput?: string, suggestions?: string[] ): ErrorContext { return { component, operation, userInput, suggestions }; } /** * Common file operation error handler */ static fileOperation(operation: string, path: string): ErrorContext { return this.createContext('filesystem', operation, path, [ 'Ensure the path exists and is accessible', 'Check file permissions' ]); } /** * Common network operation error handler */ static networkOperation(operation: string, target?: string): ErrorContext { return this.createContext('network', operation, target, [ 'Check your internet connection', 'Verify the service is running' ]); } /** * Common MCP operation error handler */ static mcpOperation(operation: string, tool?: string): ErrorContext { return this.createContext('mcp', operation, tool, [ 'Verify the MCP server is configured correctly', 'Check if the tool exists using \'ncp find\'' ]); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/portel-dev/ncp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server