Skip to main content
Glama
kurdin

GitHub Repos Manager MCP Server

error-handler.cjs9.87 kB
/** * @fileoverview Comprehensive error handling utilities for the GitHub MCP Server * Ensures the server never crashes and provides meaningful error responses */ /** * Error types for categorizing different kinds of errors */ const ErrorTypes = { VALIDATION: 'VALIDATION', AUTHENTICATION: 'AUTHENTICATION', AUTHORIZATION: 'AUTHORIZATION', API_ERROR: 'API_ERROR', NETWORK_ERROR: 'NETWORK_ERROR', RATE_LIMIT: 'RATE_LIMIT', NOT_FOUND: 'NOT_FOUND', INTERNAL: 'INTERNAL', CONFIGURATION: 'CONFIGURATION' }; /** * Enhanced error class with additional context */ class MCPError extends Error { constructor(message, type = ErrorTypes.INTERNAL, originalError = null, context = {}) { super(message); this.name = 'MCPError'; this.type = type; this.originalError = originalError; this.context = context; this.timestamp = new Date().toISOString(); // Capture stack trace if (Error.captureStackTrace) { Error.captureStackTrace(this, MCPError); } } } /** * Safe wrapper for async functions that ensures no unhandled errors * @param {Function} fn - The async function to wrap * @param {string} operationName - Name of the operation for logging * @returns {Function} - Wrapped function that never throws */ function safeAsync(fn, operationName = 'unknown operation') { return async (...args) => { try { return await fn(...args); } catch (error) { return handleError(error, operationName, args); } }; } /** * Safe wrapper for sync functions * @param {Function} fn - The function to wrap * @param {string} operationName - Name of the operation for logging * @returns {Function} - Wrapped function that never throws */ function safeSync(fn, operationName = 'unknown operation') { return (...args) => { try { return fn(...args); } catch (error) { return handleError(error, operationName, args); } }; } /** * Categorizes error based on error message and properties * @param {Error} error - The error to categorize * @returns {string} - Error type */ function categorizeError(error) { if (!error) return ErrorTypes.INTERNAL; const message = error.message?.toLowerCase() || ''; const status = error.status || error.statusCode; // Network/Connection errors if (message.includes('network') || message.includes('timeout') || message.includes('econnreset') || message.includes('enotfound') || error.code === 'ECONNRESET' || error.code === 'ENOTFOUND') { return ErrorTypes.NETWORK_ERROR; } // Rate limiting if (status === 429 || message.includes('rate limit') || message.includes('api rate limit exceeded')) { return ErrorTypes.RATE_LIMIT; } // Authentication if (status === 401 || message.includes('unauthorized') || message.includes('authentication') || message.includes('bad credentials')) { return ErrorTypes.AUTHENTICATION; } // Authorization if (status === 403 || message.includes('forbidden') || message.includes('permission') || message.includes('not allowed')) { return ErrorTypes.AUTHORIZATION; } // Not found if (status === 404 || message.includes('not found') || message.includes('does not exist')) { return ErrorTypes.NOT_FOUND; } // Validation errors if (message.includes('required') || message.includes('invalid') || message.includes('missing') || message.includes('validation') || status === 400 || status === 422) { return ErrorTypes.VALIDATION; } // API errors (general GitHub API errors) if (status >= 400 && status < 500) { return ErrorTypes.API_ERROR; } // Server errors if (status >= 500) { return ErrorTypes.API_ERROR; } return ErrorTypes.INTERNAL; } /** * Creates user-friendly error message based on error type * @param {Error} error - The original error * @param {string} type - Error type * @param {string} operation - Operation that failed * @returns {string} - User-friendly error message */ function createUserFriendlyMessage(error, type, operation) { const baseMessage = error.message || 'An unknown error occurred'; switch (type) { case ErrorTypes.AUTHENTICATION: return `Authentication failed. Please check your GitHub token. Original error: ${baseMessage}`; case ErrorTypes.AUTHORIZATION: return `Access denied. You don't have permission to perform this operation. Original error: ${baseMessage}`; case ErrorTypes.RATE_LIMIT: return `GitHub API rate limit exceeded. Please wait before making more requests. Original error: ${baseMessage}`; case ErrorTypes.NOT_FOUND: return `The requested resource was not found. Please check the repository/resource name. Original error: ${baseMessage}`; case ErrorTypes.VALIDATION: return `Invalid input provided. ${baseMessage}`; case ErrorTypes.NETWORK_ERROR: return `Network error occurred while connecting to GitHub. Please check your internet connection. Original error: ${baseMessage}`; case ErrorTypes.API_ERROR: return `GitHub API error occurred during ${operation}. ${baseMessage}`; case ErrorTypes.CONFIGURATION: return `Configuration error: ${baseMessage}`; case ErrorTypes.INTERNAL: default: return `Internal error occurred during ${operation}. ${baseMessage}`; } } /** * Main error handling function * @param {Error} error - The error to handle * @param {string} operation - Name of the operation that failed * @param {any} context - Additional context (arguments, etc.) * @returns {object} - Standardized error response */ function handleError(error, operation = 'unknown operation', context = null) { // Ensure we have an error object if (!error) { error = new Error('Unknown error occurred'); } // If error is already an MCPError, don't wrap it again if (error instanceof MCPError) { return formatErrorResponse(error, operation); } // Categorize the error const errorType = categorizeError(error); // Create enhanced error const mcpError = new MCPError( error.message || 'Unknown error', errorType, error, { operation, context, originalStack: error.stack } ); return formatErrorResponse(mcpError, operation); } /** * Formats error into standardized MCP response * @param {MCPError} mcpError - The MCP error object * @param {string} operation - Operation name * @returns {object} - Formatted error response */ function formatErrorResponse(mcpError, operation) { const userMessage = createUserFriendlyMessage(mcpError, mcpError.type, operation); // Log error for debugging (but don't interfere with MCP protocol) const logMessage = `[${mcpError.type}] Error in ${operation}: ${mcpError.message}`; if (mcpError.originalError?.stack) { // Use stderr to avoid interfering with MCP protocol process.stderr.write(`${logMessage}\nStack: ${mcpError.originalError.stack}\n`); } else { process.stderr.write(`${logMessage}\n`); } return { success: false, message: userMessage, error: mcpError.message, errorType: mcpError.type, timestamp: mcpError.timestamp }; } /** * Validates required parameters * @param {object} params - Parameters to validate * @param {string[]} required - Required parameter names * @param {string} operation - Operation name for error context * @throws {MCPError} - If validation fails */ function validateRequired(params, required, operation = 'operation') { if (!params || typeof params !== 'object') { throw new MCPError( 'Parameters object is required', ErrorTypes.VALIDATION, null, { operation, required } ); } const missing = required.filter(param => { const value = params[param]; return value === undefined || value === null || value === ''; }); if (missing.length > 0) { throw new MCPError( `Missing required parameters: ${missing.join(', ')}`, ErrorTypes.VALIDATION, null, { operation, required, missing, provided: Object.keys(params) } ); } } /** * Safely parses JSON with error handling * @param {string} jsonString - JSON string to parse * @param {string} operation - Operation context * @returns {object} - Parsed object or error response */ function safeJsonParse(jsonString, operation = 'JSON parsing') { try { return JSON.parse(jsonString); } catch (error) { return handleError( new Error(`Invalid JSON: ${error.message}`), operation, { jsonString: jsonString?.substring(0, 100) + '...' } ); } } /** * Wraps handler functions to ensure they never crash the server * @param {Function} handler - Handler function to wrap * @param {string} handlerName - Name of the handler * @returns {Function} - Safe wrapped handler */ function wrapHandler(handler, handlerName) { return async (...args) => { try { // Validate handler is a function if (typeof handler !== 'function') { throw new MCPError( `Handler ${handlerName} is not a function`, ErrorTypes.INTERNAL, null, { handlerName, handlerType: typeof handler } ); } const result = await handler(...args); // Ensure result is in expected format if (result && typeof result === 'object') { return result; } // If result is not in expected format, wrap it return { success: true, data: result, message: `${handlerName} completed successfully` }; } catch (error) { return handleError(error, handlerName, { args }); } }; } module.exports = { ErrorTypes, MCPError, safeAsync, safeSync, handleError, validateRequired, safeJsonParse, wrapHandler, formatErrorResponse, categorizeError };

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/kurdin/github-repos-manager-mcp'

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