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