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
JavaScript
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 });
}
}
}