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
JavaScript
/**
* 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