UNPKG

perplexity-mcp-server

Version:

A Perplexity API Model Context Protocol (MCP) server that unlocks Perplexity's search-augmented AI capabilities for LLM agents. Features robust error handling, secure input validation, and transparent reasoning with the showThinking parameter. Built with

238 lines (237 loc) 9.03 kB
import { BaseErrorCode, McpError } from '../types-global/errors.js'; import { logger } from './logger.js'; import { sanitizeInputForLogging } from './sanitization.js'; // Updated import /** * 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 if (errorName in ERROR_TYPE_MAPPINGS) { return ERROR_TYPE_MAPPINGS[errorName]; } // 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) { // Add any additional context if (context && Object.keys(context).length > 0) { error.details = { ...error.details, ...context }; } // Log the error with sanitized input logger.error(`Error ${operation}: ${error.message}`, { errorCode: error.code, requestId: context?.requestId, input: input ? sanitizeInputForLogging(input) : undefined, stack: includeStack ? error.stack : undefined, critical, ...context }); if (rethrow) { throw error; } return error; } // Sanitize input for logging const sanitizedInput = input ? sanitizeInputForLogging(input) : undefined; // Log the error with consistent format logger.error(`Error ${operation}`, { error: error instanceof Error ? error.message : String(error), 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 const transformedError = options.errorMapper ? options.errorMapper(error) : new McpError(errorCode, `Error ${operation}: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: getErrorName(error), ...context }); // Rethrow if requested if (rethrow) { throw transformedError; } 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, details: 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: typeof error } }; } /** * 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 }); } } }