UNPKG

pms-analysis-reports-mcp-server

Version:

PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction

199 lines 8.21 kB
import { logger } from "../utils/logger.js"; export class ValidationMiddleware { // IMO number validation (numeric format - accepts any digits) static validateIMO(imo) { if (imo === undefined || imo === null) { return null; // Allow undefined for optional parameters } // Convert to string to check for digits-only pattern const imoString = String(imo).trim(); // Check if it contains only digits (no characters) if (!/^\d+$/.test(imoString)) { return { field: 'imo', message: 'IMO must contain only digits' }; } // Ensure it's a valid number const imoNumber = Number(imo); if (isNaN(imoNumber) || imoNumber <= 0) { return { field: 'imo', message: 'IMO must be a positive number' }; } return null; } // Date validation and parsing static validateDateRange(startDate, endDate) { const errors = []; if (startDate) { const start = new Date(startDate); if (isNaN(start.getTime())) { errors.push({ field: 'startDate', message: 'Invalid start date format' }); } } if (endDate) { const end = new Date(endDate); if (isNaN(end.getTime())) { errors.push({ field: 'endDate', message: 'Invalid end date format' }); } } if (startDate && endDate && errors.length === 0) { const start = new Date(startDate); const end = new Date(endDate); if (start > end) { errors.push({ field: 'dateRange', message: 'Start date must be before end date' }); } } return errors; } // String validation with length limits static validateString(value, fieldName, options = {}) { const { required = false, minLength = 0, maxLength = 1000, pattern } = options; if (value === undefined || value === null) { return required ? { field: fieldName, message: `${fieldName} is required` } : null; } if (typeof value !== 'string') { return { field: fieldName, message: `${fieldName} must be a string` }; } if (value.length < minLength) { return { field: fieldName, message: `${fieldName} must be at least ${minLength} characters` }; } if (value.length > maxLength) { return { field: fieldName, message: `${fieldName} must be no more than ${maxLength} characters` }; } if (pattern && !pattern.test(value)) { return { field: fieldName, message: `${fieldName} format is invalid` }; } return null; } // Number validation static validateNumber(value, fieldName, options = {}) { const { required = false, min, max, integer = false } = options; if (value === undefined || value === null) { return required ? { field: fieldName, message: `${fieldName} is required` } : null; } const num = Number(value); if (isNaN(num)) { return { field: fieldName, message: `${fieldName} must be a valid number` }; } if (integer && !Number.isInteger(num)) { return { field: fieldName, message: `${fieldName} must be an integer` }; } if (min !== undefined && num < min) { return { field: fieldName, message: `${fieldName} must be at least ${min}` }; } if (max !== undefined && num > max) { return { field: fieldName, message: `${fieldName} must be no more than ${max}` }; } return null; } // Array validation static validateArray(value, fieldName, options = {}) { const { required = false, minItems = 0, maxItems = 100, itemValidator } = options; const errors = []; if (value === undefined || value === null) { if (required) { errors.push({ field: fieldName, message: `${fieldName} is required` }); } return errors; } if (!Array.isArray(value)) { errors.push({ field: fieldName, message: `${fieldName} must be an array` }); return errors; } if (value.length < minItems) { errors.push({ field: fieldName, message: `${fieldName} must have at least ${minItems} items` }); } if (value.length > maxItems) { errors.push({ field: fieldName, message: `${fieldName} must have no more than ${maxItems} items` }); } if (itemValidator) { value.forEach((item, index) => { const itemError = itemValidator(item, index); if (itemError) { errors.push({ field: `${fieldName}[${index}]`, message: itemError.message, value: item }); } }); } return errors; } // Main validation method for tool arguments static validateToolArguments(toolName, args) { const errors = []; try { // Common validations for all tools if (args.imo !== undefined) { const imoError = this.validateIMO(args.imo); if (imoError) errors.push(imoError); } if (args.startDate || args.endDate) { const dateErrors = this.validateDateRange(args.startDate, args.endDate); errors.push(...dateErrors); } // Tool-specific validations switch (toolName) { case 'search_pms_data': case 'search_fuel_analysis': case 'search_lube_oil_reports': const queryError = this.validateString(args.query, 'query', { required: true, minLength: 1, maxLength: 500 }); if (queryError) errors.push(queryError); break; case 'get_vessel_details': if (!args.imo && !args.vesselName) { errors.push({ field: 'identification', message: 'Either IMO number or vessel name is required' }); } break; case 'get_maintenance_summary': if (!args.imo) { errors.push({ field: 'imo', message: 'IMO number is required' }); } break; default: logger.warn(`No specific validation rules for tool: ${toolName}`); } const sanitizedArgs = this.sanitizeArguments(args); return { isValid: errors.length === 0, errors, sanitizedArgs: errors.length === 0 ? sanitizedArgs : undefined }; } catch (error) { logger.error(`Validation error for tool ${toolName}:`, error); return { isValid: false, errors: [{ field: 'general', message: 'Validation failed due to internal error' }] }; } } // Sanitize arguments to prevent injection attacks static sanitizeArguments(args) { const sanitized = {}; for (const [key, value] of Object.entries(args)) { if (typeof value === 'string') { // Remove potential MongoDB injection patterns sanitized[key] = value .replace(/\$\w+/g, '') // Remove MongoDB operators .replace(/[<>]/g, '') // Remove HTML tags .trim(); } else if (typeof value === 'number') { sanitized[key] = Math.max(-Number.MAX_SAFE_INTEGER, Math.min(Number.MAX_SAFE_INTEGER, value)); } else { sanitized[key] = value; } } return sanitized; } } //# sourceMappingURL=validation-middleware.js.map