UNPKG

@cyanheads/git-mcp-server

Version:

An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management,

269 lines 9.82 kB
import { BaseErrorCode, McpError } from "../../types-global/errors.js"; // Corrected path import { logger } from "./logger.js"; import { sanitizeInputForLogging } from "../index.js"; // Import from main barrel file /** * Simple mapper that maps error types to error codes */ const ERROR_TYPE_MAPPINGS = { SyntaxError: BaseErrorCode.VALIDATION_ERROR, TypeError: BaseErrorCode.VALIDATION_ERROR, ReferenceError: BaseErrorCode.INTERNAL_ERROR, RangeError: BaseErrorCode.VALIDATION_ERROR, URIError: BaseErrorCode.VALIDATION_ERROR, EvalError: BaseErrorCode.INTERNAL_ERROR, }; /** * Common error patterns for automatic classification */ const COMMON_ERROR_PATTERNS = [ // Authentication related errors { pattern: /auth|unauthorized|unauthenticated|not.*logged.*in|invalid.*token|expired.*token/i, errorCode: BaseErrorCode.UNAUTHORIZED, }, // Permission related errors { pattern: /permission|forbidden|access.*denied|not.*allowed/i, errorCode: BaseErrorCode.FORBIDDEN, }, // Not found errors { pattern: /not.*found|missing|no.*such|doesn't.*exist|couldn't.*find/i, errorCode: BaseErrorCode.NOT_FOUND, }, // Validation errors { pattern: /invalid|validation|malformed|bad request|wrong format/i, errorCode: BaseErrorCode.VALIDATION_ERROR, }, // Conflict errors { pattern: /conflict|already.*exists|duplicate|unique.*constraint/i, errorCode: BaseErrorCode.CONFLICT, }, // Rate limiting { pattern: /rate.*limit|too.*many.*requests|throttled/i, errorCode: BaseErrorCode.RATE_LIMITED, }, // Timeout errors { pattern: /timeout|timed.*out|deadline.*exceeded/i, errorCode: BaseErrorCode.TIMEOUT, }, // External service errors { pattern: /service.*unavailable|bad.*gateway|gateway.*timeout/i, errorCode: BaseErrorCode.SERVICE_UNAVAILABLE, }, ]; /** * Get a readable name for an error * @param error Error to get name for * @returns User-friendly error name */ function getErrorName(error) { if (error instanceof Error) { return error.name || "Error"; } if (error === null) { return "NullError"; } if (error === undefined) { return "UndefinedError"; } return typeof error === "object" ? "ObjectError" : "UnknownError"; } /** * Get a message from an error * @param error Error to get message from * @returns Error message */ function getErrorMessage(error) { if (error instanceof Error) { return error.message; } if (error === null) { return "Null error occurred"; } if (error === undefined) { return "Undefined error occurred"; } return typeof error === "string" ? error : String(error); } /** * Error handler utility class with various error handling methods */ export class ErrorHandler { /** * Determine the appropriate error code for an error based on patterns and type * @param error The error to classify * @returns The appropriate error code */ static determineErrorCode(error) { // If it's already an McpError, use its code if (error instanceof McpError) { return error.code; } const errorName = getErrorName(error); const errorMessage = getErrorMessage(error); // Check if the error type has a direct mapping const mappedError = ERROR_TYPE_MAPPINGS[errorName]; if (mappedError) { return mappedError; } // Check for common error patterns for (const pattern of COMMON_ERROR_PATTERNS) { const regex = pattern.pattern instanceof RegExp ? pattern.pattern : new RegExp(pattern.pattern, "i"); if (regex.test(errorMessage) || regex.test(errorName)) { return pattern.errorCode; } } // Default to internal error if no pattern matches return BaseErrorCode.INTERNAL_ERROR; } /** * Handle operation errors with consistent logging and transformation * @param error The error that occurred * @param options Error handling options * @returns The transformed error */ static handleError(error, options) { const { context, operation, input, rethrow = false, errorCode: explicitErrorCode, includeStack = true, critical = false, } = options; // If it's already an McpError, use it directly but apply additional context if (error instanceof McpError) { const existingDetails = typeof error.details === "object" && error.details !== null ? error.details : {}; const newDetails = { ...existingDetails, ...context }; // Create a new error to avoid mutating a readonly property const updatedError = new McpError(error.code, error.message, newDetails); // Log the error with sanitized input logger.error(`Error in ${operation}: ${updatedError.message}`, { errorCode: updatedError.code, requestId: context?.requestId, input: input ? sanitizeInputForLogging(input) : undefined, stack: includeStack ? updatedError.stack : undefined, critical, ...context, }); if (rethrow) { throw updatedError; } // Ensure the function returns an Error type return updatedError; } // Sanitize input for logging const sanitizedInput = input ? sanitizeInputForLogging(input) : undefined; // Log the error with consistent format logger.error(`Error in ${operation}: ${getErrorMessage(error)}`, { error: getErrorMessage(error), // Use helper function errorType: getErrorName(error), input: sanitizedInput, requestId: context?.requestId, stack: includeStack && error instanceof Error ? error.stack : undefined, critical, ...context, }); // Choose the error code (explicit > determined > default) const errorCode = explicitErrorCode || ErrorHandler.determineErrorCode(error) || BaseErrorCode.INTERNAL_ERROR; // Transform to appropriate error type let transformedError; if (options.errorMapper) { transformedError = options.errorMapper(error); } else { transformedError = new McpError(errorCode, `Error in ${operation}: ${getErrorMessage(error)}`, // Use helper function { originalError: getErrorName(error), ...context, }); } // Rethrow if requested if (rethrow) { throw transformedError; } // Ensure the function returns an Error type return transformedError; } /** * Map an error to a specific error type based on error message patterns * @param error The error to map * @param mappings Array of pattern and factory mappings * @param defaultFactory Default factory function if no pattern matches * @returns The mapped error */ static mapError(error, mappings, defaultFactory) { // If it's already the target type and we have a default factory to check against, return it if (defaultFactory && error instanceof Error) { const defaultInstance = defaultFactory(error); if (error.constructor === defaultInstance.constructor) { return error; } } const errorMessage = getErrorMessage(error); // Check each pattern and return the first match for (const mapping of mappings) { const matches = mapping.pattern instanceof RegExp ? mapping.pattern.test(errorMessage) : errorMessage.includes(mapping.pattern); if (matches) { return mapping.factory(error, mapping.additionalContext); } } // Return default or original error if (defaultFactory) { return defaultFactory(error); } return error instanceof Error ? error : new Error(String(error)); } // Removed createErrorMapper method for simplification /** * Format an error for consistent response structure * @param error The error to format * @returns Formatted error object */ static formatError(error) { if (error instanceof McpError) { return { code: error.code, message: error.message, // Ensure details is an object details: typeof error.details === "object" && error.details !== null ? error.details : {}, }; } if (error instanceof Error) { return { code: ErrorHandler.determineErrorCode(error), message: error.message, details: { errorType: error.name }, }; } return { code: BaseErrorCode.UNKNOWN_ERROR, message: String(error), details: { errorType: "string" }, }; } /** * Safely execute a function and handle any errors * @param fn Function to execute * @param options Error handling options * @returns The result of the function or error */ static async tryCatch(fn, options) { try { return await fn(); } catch (error) { throw ErrorHandler.handleError(error, { ...options, rethrow: true }); } } } //# sourceMappingURL=errorHandler.js.map