UNPKG

voyage-and-consumption-mcp-server

Version:

Voyage and consumption management server handling vessel voyages, fuel consumption, performance monitoring, and operational data with ERP access for data extraction

276 lines 9.87 kB
/** * Error Handling Middleware * * Centralizes error handling logic that was duplicated across * 29+ catch blocks in the codebase. * * This middleware standardizes: * - Error classification and type checking * - Error logging with consistent formatting * - Error response formatting for tools * - Service-specific error handling * - Parameter validation error handling */ import { logger } from '../utils/logger.js'; import { formatTextResponse } from '../utils/response-formatter.js'; // Re-export error classes for convenience export class MissingParameterError extends Error { constructor(param, tool_name) { super(`Missing required parameter '${param}' for tool '${tool_name}'`); this.param = param; this.tool_name = tool_name; } } export class NavtorServiceError extends Error { constructor(message, original_error) { super(message); this.original_error = original_error; } } export class SiyaServiceError extends Error { constructor(message, original_error) { super(message); this.original_error = original_error; } } export class StormglassServiceError extends Error { constructor(message, original_error) { super(message); this.original_error = original_error; } } export class VesselPositionError extends Error { constructor(imo) { super(`Vessel position data not available for IMO ${imo}`); this.imo = imo; } } export class VesselFuelConsumptionError extends Error { constructor(imo) { super(`Vessel fuel consumption data not available for IMO ${imo}`); this.imo = imo; } } export class VesselEtaError extends Error { constructor(imo) { super(`Vessel ETA data not available for IMO ${imo}`); this.imo = imo; } } export class WeatherDataError extends Error { constructor(coordinates, message) { super(message || `Weather data not available for coordinates ${coordinates}`); this.coordinates = coordinates; } } export class SearchError extends Error { constructor(query, message, original_error) { super(message || `Search failed for query: ${query}`); this.query = query; this.original_error = original_error; } } export class MongoDBError extends Error { constructor(message, original_error) { super(message); this.original_error = original_error; } } // Error classification utilities export function isParameterError(error) { return error instanceof MissingParameterError; } export function isServiceError(error) { return error instanceof NavtorServiceError || error instanceof SiyaServiceError || error instanceof StormglassServiceError; } export function isVesselDataError(error) { return error instanceof VesselPositionError || error instanceof VesselFuelConsumptionError || error instanceof VesselEtaError; } export function isDatabaseError(error) { return error instanceof MongoDBError; } /** * Logs errors with consistent formatting * @param error - The error to log * @param context - Additional context for the error * @param imo - Optional IMO number for context */ export function logError(error, context, imo) { let logMessage; if (typeof error === 'string') { logMessage = context ? `${context}: ${error}` : error; } else { logMessage = context ? `${context}${imo ? ` for IMO ${imo}` : ''}: ${error.message || error}` : error.message || String(error); } logger.error(logMessage); // Log original error if available if (error instanceof Error && 'original_error' in error && error.original_error) { logger.error(`Original error: ${error.original_error}`); } } /** * Creates a standardized error response for tools * @param error - The error to format * @param context - Optional context for the error * @param imo - Optional IMO number for context * @returns Formatted error response array */ export function createToolErrorResponse(error, context, imo) { // Log the error first logError(error, context, imo); let errorMessage; if (isParameterError(error)) { errorMessage = `Error: ${error.message}`; } else if (error instanceof NavtorServiceError) { errorMessage = `Error with NAVTOR service: ${error.message}`; } else if (error instanceof SiyaServiceError) { errorMessage = `Error with SIYA service: ${error.message}`; } else if (error instanceof StormglassServiceError) { errorMessage = `Error with Stormglass service: ${error.message}`; } else if (isDatabaseError(error)) { errorMessage = `Error with MongoDB: ${error.message}`; } else if (isVesselDataError(error)) { errorMessage = `Error: ${error instanceof Error ? error.message : String(error)}`; } else if (error instanceof WeatherDataError) { errorMessage = `Error: ${error.message}`; } else if (error instanceof SearchError) { errorMessage = `Error: ${error.message}`; } else if (typeof error === 'string') { errorMessage = context ? `Failed to ${context}${imo ? ` for IMO ${imo}` : ''}: ${error}` : `Error: ${error}`; } else { const message = error instanceof Error ? error.message : String(error); errorMessage = context ? `Failed to ${context}${imo ? ` for IMO ${imo}` : ''}: ${message}` : `Error: ${message}`; } return [formatTextResponse(errorMessage)]; } /** * Handles API authentication errors with specific response formatting * @param response - The HTTP response object * @param responseText - The response body text * @param service - The service name (e.g., "NAVTOR", "SIYA") * @returns Error object with detailed message */ export function handleAuthenticationError(response, responseText, service) { if (response.status === 400 && responseText.includes("invalid_client")) { return new Error(`Invalid ${service} client credentials. Please verify your ${service}_CLIENT_ID and ${service}_CLIENT_SECRET are correct. Status: ${response.status}, Response: ${responseText}`); } else if (response.status === 401) { return new Error(`Invalid ${service} username/password. Please verify your ${service}_USERNAME and ${service}_PASSWORD are correct. Status: ${response.status}, Response: ${responseText}`); } else { return new Error(`HTTP ${response.status}: ${responseText}`); } } /** * Handles JSON parsing errors with context * @param responseText - The response text that failed to parse * @param service - The service name * @returns Error object with detailed message */ export function handleJsonParsingError(responseText, service) { return new Error(`Invalid JSON response from ${service}: ${responseText}`); } /** * Validates API response and throws appropriate errors * @param response - The API response to validate * @param imo - The IMO number for context * @param errorClass - The error class to throw if validation fails * @param customMessage - Optional custom error message */ export function validateApiResponse(response, imo, errorClass, customMessage) { if (!response || !response.resultData) { throw new errorClass(String(imo)); } } /** * Wraps a tool method with standardized error handling * @param toolMethod - The tool method to execute * @param toolName - The name of the tool for error context * @param imo - Optional IMO number for context * @returns Promise that resolves to tool response or error response */ export async function withErrorHandling(toolMethod, toolName, imo) { try { return await toolMethod(); } catch (error) { return createToolErrorResponse(error, `execute ${toolName}`, imo); } } /** * Handles parameter validation errors consistently * @param parameterName - The name of the missing parameter * @param toolName - The name of the tool * @throws MissingParameterError */ export function validateRequiredParameter(value, parameterName, toolName) { if (!value || (typeof value === 'string' && value.trim() === "")) { throw new MissingParameterError(parameterName, toolName); } } /** * Handles multiple parameter validation * @param parameters - Object with parameter names and values * @param toolName - The name of the tool * @throws MissingParameterError for the first missing parameter */ export function validateRequiredParameters(parameters, toolName) { for (const [paramName, paramValue] of Object.entries(parameters)) { if (!paramValue || (typeof paramValue === 'string' && paramValue.trim() === "")) { throw new MissingParameterError(paramName, toolName); } } } /** * Creates a graceful error handler that returns null instead of throwing * @param operation - The operation to execute * @param context - Context for logging * @returns Result of operation or null if error occurs */ export async function withGracefulHandling(operation, context) { try { return await operation(); } catch (error) { logError(error, context); return null; } } /** * Standardizes error handling for database operations * @param operation - The database operation to execute * @param context - Context for the operation * @param imo - Optional IMO for context * @returns Result or throws MongoDBError */ export async function withDatabaseErrorHandling(operation, context, imo) { try { return await operation(); } catch (error) { const contextMessage = `${context}${imo ? ` for IMO ${imo}` : ''}`; logError(error, contextMessage); throw new MongoDBError(contextMessage, error); } } //# sourceMappingURL=error-handler.js.map