UNPKG

firewalla-mcp-server

Version:

Model Context Protocol (MCP) server for Firewalla MSP API - Provides real-time network monitoring, security analysis, and firewall management through 28 specialized tools compatible with any MCP client

457 lines 17.7 kB
/** * Progressive Validator for Firewalla MCP Server * Provides step-by-step validation guidance for complex queries */ import { EnhancedQueryValidator } from './enhanced-query-validator.js'; import { FieldValidator } from './field-validator.js'; import { OperatorValidator } from './operator-validator.js'; import { ErrorFormatter } from './error-formatter.js'; export class ProgressiveValidator { /** * Validate query with progressive guidance */ static validateWithProgression(query, entityType) { const results = { basicSyntax: this.validateBasicSyntax(query), fieldExistence: this.validateFields(query, entityType), operatorCompatibility: this.validateOperators(query, entityType), semanticCorrectness: this.validateSemantics(query, entityType), performanceOptimization: this.validatePerformance(query, entityType) }; // Find first critical failure const firstFailure = this.findFirstCriticalFailure(results); if (firstFailure) { const errorReport = this.createStepErrorReport(firstFailure, results); return { isValid: false, currentStep: firstFailure.step, error: errorReport, nextSteps: this.getNextSteps(firstFailure.step), progress: this.calculateProgress(results), allValidations: results, overallGuidance: this.generateOverallGuidance(firstFailure.step, results) }; } // Check for non-critical issues const hasWarnings = Object.values(results).some(r => r.warnings.length > 0); const progress = this.calculateProgress(results); return { isValid: true, progress, allValidations: results, overallGuidance: hasWarnings ? this.generateOptimizationGuidance(results) : 'Query is valid and optimized!' }; } /** * Validate basic syntax */ static validateBasicSyntax(query) { const validator = new EnhancedQueryValidator(); const result = validator.validateQuery(query); const syntaxErrors = result.detailedErrors.filter(e => e.errorType === 'syntax'); return { step: 'basicSyntax', isValid: syntaxErrors.length === 0, errors: syntaxErrors.map(e => e.message), warnings: [], suggestions: syntaxErrors.map(e => e.suggestion).filter(Boolean), progress: syntaxErrors.length === 0 ? 100 : 0 }; } /** * Validate field existence and compatibility */ static validateFields(query, entityType) { const errors = []; const warnings = []; const suggestions = []; // Extract field names from query const fieldPattern = /(\w+)\s*[:=]/g; const fields = new Set(); let match; while ((match = fieldPattern.exec(query)) !== null) { fields.add(match[1]); } let validFields = 0; const totalFields = fields.size; fields.forEach(field => { const validation = FieldValidator.validateField(field, entityType); if (validation.isValid) { validFields++; } else { errors.push(validation.error || `Invalid field: ${field}`); if (validation.suggestion) { suggestions.push(validation.suggestion); } } }); const progress = totalFields > 0 ? Math.round((validFields / totalFields) * 100) : 100; return { step: 'fieldExistence', isValid: errors.length === 0, errors, warnings, suggestions, progress }; } /** * Validate operator compatibility */ static validateOperators(query, entityType) { const errors = []; const warnings = []; const suggestions = []; // Extract field:operator patterns - updated to handle >= and other operators const operatorPattern = /(\w+)\s*([!<>=:~]+|contains|startswith|endswith)/g; const operators = new Map(); let match; while ((match = operatorPattern.exec(query)) !== null) { operators.set(match[1], match[2]); } let validOperators = 0; const totalOperators = operators.size; operators.forEach((operator, field) => { const validation = OperatorValidator.validateOperator(field, operator, entityType); if (validation.isValid) { validOperators++; } else { errors.push(validation.error || `Invalid operator: ${operator} for field ${field}`); if (validation.suggestion) { suggestions.push(validation.suggestion); } } }); const progress = totalOperators > 0 ? Math.round((validOperators / totalOperators) * 100) : 100; return { step: 'operatorCompatibility', isValid: errors.length === 0, errors, warnings, suggestions, progress }; } /** * Validate semantic correctness */ static validateSemantics(query, entityType) { const errors = []; const warnings = []; const suggestions = []; // Check for contradictory conditions const contradictions = this.findContradictions(query); errors.push(...contradictions); // Check for redundant conditions const redundancies = this.findRedundancies(query); warnings.push(...redundancies); // Check for impossible ranges const impossibleRanges = this.findImpossibleRanges(query); errors.push(...impossibleRanges); // Generate semantic suggestions if (errors.length === 0 && warnings.length === 0) { const optimizations = this.suggestSemanticOptimizations(query, entityType); suggestions.push(...optimizations); } return { step: 'semanticCorrectness', isValid: errors.length === 0, errors, warnings, suggestions, progress: errors.length === 0 ? 100 : Math.max(0, 100 - (errors.length * 25)) }; } /** * Validate performance characteristics */ static validatePerformance(query, entityType) { const warnings = []; const suggestions = []; // Check query complexity const complexity = this.calculateQueryComplexity(query); if (complexity > 10) { warnings.push('Query complexity is high, consider simplifying'); suggestions.push('Break complex queries into smaller parts or use more specific filters'); } // Check for inefficient patterns if (query.includes('*') && query.split('*').length > 3) { warnings.push('Multiple wildcards may impact performance'); suggestions.push('Use more specific patterns instead of multiple wildcards'); } // Check for missing limit considerations if (!query.includes('limit') && entityType === 'flows') { suggestions.push('Consider adding a limit parameter for better performance with flow queries'); } return { step: 'performanceOptimization', isValid: true, // Performance issues are not critical errors: [], warnings, suggestions, progress: warnings.length === 0 ? 100 : Math.max(70, 100 - (warnings.length * 10)) }; } /** * Find first critical failure */ static findFirstCriticalFailure(results) { // Check all steps in order, but only fail on critical ones const orderedSteps = [ 'basicSyntax', 'fieldExistence', 'operatorCompatibility', 'semanticCorrectness', 'performanceOptimization' ]; for (const step of orderedSteps) { const result = results[step]; if (!result.isValid && this.STEP_CONFIGS[step].critical) { return result; } } return null; } /** * Calculate overall progress */ static calculateProgress(results) { let totalProgress = 0; let totalWeight = 0; Object.entries(results).forEach(([step, result]) => { const config = this.STEP_CONFIGS[step]; const stepProgress = result.progress / 100; totalProgress += stepProgress * config.weight; totalWeight += config.weight; }); return Math.round((totalProgress / totalWeight) * 100); } /** * Get next steps guidance */ static getNextSteps(failedStep) { switch (failedStep) { case 'basicSyntax': return [ 'Fix syntax errors first before proceeding', 'Check parentheses and quotes are properly matched', 'Verify operator placement and field:value syntax', 'Review query syntax documentation' ]; case 'fieldExistence': return [ 'Correct field names to match entity schema', 'Check field spelling and capitalization', 'Refer to field documentation for valid options', 'Consider field aliases if using common abbreviations' ]; case 'operatorCompatibility': return [ 'Use appropriate operators for each field type', 'Check operator documentation for compatibility', 'Consider alternative operators that match field types', 'Review examples of correct operator usage' ]; case 'semanticCorrectness': return [ 'Resolve logical contradictions in query conditions', 'Remove redundant or conflicting filters', 'Check date ranges and numeric comparisons', 'Simplify complex logical expressions' ]; case 'performanceOptimization': return [ 'Consider query complexity and performance impact', 'Use more specific filters to reduce result sets', 'Avoid excessive wildcard usage', 'Add appropriate limits for large result sets' ]; default: return ['Review query documentation for guidance']; } } /** * Generate overall guidance message */ static generateOverallGuidance(failedStep, results) { const config = this.STEP_CONFIGS[failedStep]; const progress = this.calculateProgress(results); let guidance = `Query validation failed at step: ${config.title}. `; guidance += `Overall progress: ${progress}%. `; guidance += `Focus on: ${config.description}`; return guidance; } /** * Generate optimization guidance for valid queries */ static generateOptimizationGuidance(results) { const warnings = Object.values(results).flatMap(r => r.warnings); const suggestions = Object.values(results).flatMap(r => r.suggestions); if (warnings.length > 0) { return `Query is valid but has ${warnings.length} optimization opportunities. ${suggestions[0] || 'Consider reviewing performance suggestions.'}`; } return 'Query is valid and well-optimized!'; } /** * Create error report for failed step */ static createStepErrorReport(failedStep, _allResults) { const detailedErrors = failedStep.errors.map(error => ({ message: error, errorType: this.getErrorTypeForStep(failedStep.step), suggestion: failedStep.suggestions[0] })); return ErrorFormatter.formatMultipleErrors(detailedErrors); } /** * Get error type for validation step */ static getErrorTypeForStep(step) { switch (step) { case 'basicSyntax': return 'syntax'; case 'fieldExistence': return 'field'; case 'operatorCompatibility': return 'operator'; case 'semanticCorrectness': return 'semantic'; case 'performanceOptimization': return 'semantic'; // Performance optimizations are semantic-level suggestions default: return 'syntax'; } } /** * Find contradictory conditions in query */ static findContradictions(query) { const contradictions = []; // Check for field:true AND field:false patterns const booleanFields = ['blocked', 'online']; booleanFields.forEach(field => { if (query.includes(`${field}:true`) && query.includes(`${field}:false`)) { contradictions.push(`Contradictory condition: ${field} cannot be both true and false`); } }); // Check for mutually exclusive protocol values const protocolPattern = /protocol:(\w+)/g; const protocols = []; let match; while ((match = protocolPattern.exec(query)) !== null) { protocols.push(match[1].toLowerCase()); } if (protocols.length > 1) { const uniqueProtocols = [...new Set(protocols)]; if (uniqueProtocols.length > 1) { contradictions.push(`Contradictory condition: protocol cannot be multiple values simultaneously (${uniqueProtocols.join(', ')})`); } } return contradictions; } /** * Find redundant conditions */ static findRedundancies(query) { const redundancies = []; // Check for duplicate field conditions const fieldPattern = /(\w+):[^)\s]+/g; const fieldCounts = new Map(); let match; while ((match = fieldPattern.exec(query)) !== null) { const field = match[1]; fieldCounts.set(field, (fieldCounts.get(field) || 0) + 1); } fieldCounts.forEach((count, field) => { if (count > 1) { redundancies.push(`Field '${field}' appears ${count} times - consider combining conditions`); } }); return redundancies; } /** * Find impossible range conditions */ static findImpossibleRanges(query) { const impossible = []; // Check for impossible numeric ranges const rangePattern = /(\w+):\[(\d+)\s+TO\s+(\d+)\]/g; let match; while ((match = rangePattern.exec(query)) !== null) { const [, field, min, max] = match; if (parseInt(min) > parseInt(max)) { impossible.push(`Impossible range for ${field}: minimum (${min}) is greater than maximum (${max})`); } } return impossible; } /** * Suggest semantic optimizations */ static suggestSemanticOptimizations(query, entityType) { const suggestions = []; // Suggest more specific filters if (query.length < 20 && !query.includes('AND') && !query.includes('OR')) { suggestions.push('Consider adding additional filters for more specific results'); } // Suggest field combinations if (entityType === 'flows' && query.includes('protocol:tcp') && !query.includes('blocked')) { suggestions.push('Consider adding blocked:true/false filter for TCP flows'); } return suggestions; } /** * Calculate query complexity score */ static calculateQueryComplexity(query) { let complexity = 0; // Count logical operators complexity += (query.match(/\b(AND|OR|NOT)\b/gi) || []).length * 2; // Count field conditions complexity += (query.match(/\w+\s*[:=]/g) || []).length; // Count wildcards complexity += (query.match(/\*/g) || []).length * 1.5; // Count parentheses groups complexity += (query.match(/\(/g) || []).length; // Count range queries complexity += (query.match(/\[.*TO.*\]/g) || []).length * 2; return complexity; } } ProgressiveValidator.STEP_CONFIGS = { basicSyntax: { title: 'Basic Syntax Check', description: 'Validates parentheses, quotes, and basic query structure', weight: 0.3, critical: true }, fieldExistence: { title: 'Field Validation', description: 'Checks that all field names are valid for the entity type', weight: 0.25, critical: true }, operatorCompatibility: { title: 'Operator Compatibility', description: 'Validates operators are compatible with field types', weight: 0.25, critical: true }, semanticCorrectness: { title: 'Semantic Analysis', description: 'Checks for logical consistency and conflicts', weight: 0.15, critical: true }, performanceOptimization: { title: 'Performance Check', description: 'Suggests optimizations for better query performance', weight: 0.05, critical: false } }; //# sourceMappingURL=progressive-validator.js.map