Skip to main content
Glama

Documentation Generator MCP Server

by srwlli
validation.py12.6 kB
""" Input validation functions for MCP tool boundaries (REF-003). Validates inputs at the tool boundary before passing to generators, enabling fail-fast error handling with clear messages. """ import re from constants import ( MAX_PATH_LENGTH, VERSION_PATTERN, TemplateNames, ChangeType, Severity, ScanDepth, FocusArea, AuditSeverity, AuditScope ) __all__ = [ 'validate_project_path_input', 'validate_version_format', 'validate_template_name_input', 'validate_changelog_inputs', 'validate_scan_depth', 'validate_focus_areas', 'validate_severity_filter', 'validate_audit_scope', 'validate_severity_threshold', 'validate_file_list', # Planning workflow validation 'VALID_TEMPLATE_SECTIONS', 'validate_section_name', 'validate_plan_file_path', 'validate_plan_json_structure', 'validate_feature_name_input', ] def validate_project_path_input(path: str) -> str: """ Validate project path at tool boundary. Args: path: Project path string to validate Returns: Validated path string Raises: ValueError: If path is invalid """ if not path or not isinstance(path, str): raise ValueError('project_path must be a non-empty string') if len(path) > MAX_PATH_LENGTH: raise ValueError(f'project_path too long (max {MAX_PATH_LENGTH} characters)') # Check for null bytes (security) if '\x00' in path: raise ValueError('project_path contains null bytes') return path def validate_version_format(version: str) -> str: """ Validate semantic version format. Args: version: Version string to validate (e.g., '1.0.2') Returns: Validated version string Raises: ValueError: If version format is invalid """ if not version or not isinstance(version, str): raise ValueError('version must be a non-empty string') if not re.match(VERSION_PATTERN, version): raise ValueError( f'Invalid version format: {version}. ' f'Expected format: MAJOR.MINOR.PATCH (e.g., 1.0.2)' ) return version def validate_template_name_input(template_name: str) -> str: """ Validate template name at tool boundary. Args: template_name: Template name to validate Returns: Validated template name Raises: ValueError: If template name is invalid """ if not template_name or not isinstance(template_name, str): raise ValueError('template_name must be a non-empty string') # Use enum for valid templates (QUA-003) valid_templates = [t.value for t in TemplateNames] if template_name not in valid_templates: raise ValueError( f'Invalid template: {template_name}. ' f'Valid options: {", ".join(valid_templates)}' ) return template_name def validate_changelog_inputs( version: str, change_type: str, severity: str, title: str, description: str, files: list, reason: str, impact: str ) -> dict: """ Validate all required inputs for changelog entry. Args: version: Version number change_type: Type of change severity: Severity level title: Change title description: Change description files: List of affected files reason: Reason for change impact: Impact description Returns: Dictionary of validated inputs Raises: ValueError: If any input is invalid """ # Validate version validate_version_format(version) # Validate change_type (QUA-003) valid_types = [t.value for t in ChangeType] if change_type not in valid_types: raise ValueError( f'Invalid change_type: {change_type}. ' f'Valid options: {", ".join(valid_types)}' ) # Validate severity (QUA-003) valid_severities = [s.value for s in Severity] if severity not in valid_severities: raise ValueError( f'Invalid severity: {severity}. ' f'Valid options: {", ".join(valid_severities)}' ) # Validate required string fields if not title or not isinstance(title, str): raise ValueError('title must be a non-empty string') if not description or not isinstance(description, str): raise ValueError('description must be a non-empty string') if not reason or not isinstance(reason, str): raise ValueError('reason must be a non-empty string') if not impact or not isinstance(impact, str): raise ValueError('impact must be a non-empty string') # Validate files list if not files or not isinstance(files, list): raise ValueError('files must be a non-empty list') if not all(isinstance(f, str) for f in files): raise ValueError('files must contain only strings') return { 'version': version, 'change_type': change_type, 'severity': severity, 'title': title, 'description': description, 'files': files, 'reason': reason, 'impact': impact } # Standards Validation Functions (INFRA-007) def validate_scan_depth(scan_depth: str) -> str: """ Validate scan_depth parameter using ScanDepth enum. Args: scan_depth: Analysis depth ('quick', 'standard', or 'deep') Returns: Validated scan_depth string Raises: ValueError: If scan_depth is not a valid option """ valid_depths = [d.value for d in ScanDepth] # Use enum instead of hardcoded list (REF-002) if scan_depth not in valid_depths: raise ValueError( f"Invalid scan_depth: {scan_depth}. " f"Must be one of: {', '.join(valid_depths)}" ) return scan_depth def validate_focus_areas(focus_areas: list) -> list: """ Validate focus_areas parameter using FocusArea enum. Args: focus_areas: List of areas to analyze Returns: Validated focus_areas list Raises: ValueError: If any focus_area is not valid """ if not isinstance(focus_areas, list): raise ValueError("focus_areas must be a list") valid_areas = [a.value for a in FocusArea] # Use enum instead of hardcoded list (REF-002) for area in focus_areas: if area not in valid_areas: raise ValueError( f"Invalid focus_area: {area}. " f"Must be one of: {', '.join(valid_areas)}" ) return focus_areas # Audit Validation Functions (INFRA-007 Tool #9) def validate_severity_filter(severity: str) -> str: """ Validate severity_filter parameter using AuditSeverity enum. Args: severity: Severity level to filter by ('critical', 'major', 'minor', or 'all') Returns: Validated severity string Raises: ValueError: If severity is not a valid option """ valid_severities = [s.value for s in AuditSeverity] # Use enum instead of hardcoded list (REF-002) if severity not in valid_severities: raise ValueError( f"Invalid severity_filter: {severity}. " f"Must be one of: {', '.join(valid_severities)}" ) return severity def validate_audit_scope(scope: list) -> list: """ Validate audit scope parameter using AuditScope enum. Args: scope: List of audit areas to scan Returns: Validated scope list Raises: ValueError: If any scope value is not valid """ if not isinstance(scope, list): raise ValueError("scope must be a list") valid_scopes = [s.value for s in AuditScope] # Use enum instead of hardcoded list (REF-002) for scope_item in scope: if scope_item not in valid_scopes: raise ValueError( f"Invalid scope value: {scope_item}. " f"Must be one of: {', '.join(valid_scopes)}" ) return scope # Consistency Checker Validation Functions (INFRA-007 Tool #10) def validate_severity_threshold(threshold: str) -> str: """ Validate severity_threshold parameter using SeverityThreshold enum. Args: threshold: Severity threshold ('critical', 'major', or 'minor') Returns: Validated threshold string Raises: ValueError: If threshold is not a valid option """ from constants import SeverityThreshold # Import here to avoid circular dependency valid_thresholds = SeverityThreshold.values() if threshold not in valid_thresholds: raise ValueError( f"Invalid severity_threshold: {threshold}. " f"Must be one of: {', '.join(valid_thresholds)}" ) return threshold def validate_file_list(files: list) -> list: """ Validate file list parameter for check_consistency tool. Args: files: List of file paths (relative to project root) Returns: Validated files list Raises: ValueError: If files list is invalid, contains absolute paths, or has path traversal """ from pathlib import Path # Import Path for path validation if not isinstance(files, list): raise ValueError("files must be a list") for file_path in files: if not isinstance(file_path, str): raise ValueError(f"All file paths must be strings, got: {type(file_path).__name__}") # Check for absolute paths (security - SEC-001) if Path(file_path).is_absolute(): raise ValueError( f"File paths must be relative to project_path, got absolute path: {file_path}" ) # Check for path traversal (security - SEC-001) if '..' in file_path: raise ValueError( f"Path traversal detected in file path: {file_path}" ) return files # Planning Workflow System Validation Functions # Valid template sections - exported for reuse (single source of truth) VALID_TEMPLATE_SECTIONS = [ 'all', 'META_DOCUMENTATION', '0_preparation', '1_executive_summary', '2_risk_assessment', '3_current_state_analysis', '4_key_features', '5_task_id_system', '6_implementation_phases', '7_testing_strategy', '8_success_criteria', '9_implementation_checklist', 'QUALITY_CHECKLIST_FOR_PLANS', 'COMMON_MISTAKES_TO_AVOID', 'USAGE_INSTRUCTIONS' ] def validate_section_name(section: str) -> str: """Validate template section name.""" if section not in VALID_TEMPLATE_SECTIONS: raise ValueError( f"Invalid section: '{section}'. " f"Valid sections: {', '.join(VALID_TEMPLATE_SECTIONS)}" ) return section def validate_plan_file_path(project_path, plan_file: str): """Validate plan file path to prevent path traversal.""" from pathlib import Path # Resolve to absolute path plan_path = (project_path / plan_file).resolve() # Check if path is within project directory if not plan_path.is_relative_to(project_path): raise ValueError( f"Plan file path '{plan_file}' traverses outside project directory. " "Plan file must be within project directory." ) return plan_path def validate_plan_json_structure(plan_data: dict) -> dict: """Validate plan JSON has required structure.""" required_keys = ['META_DOCUMENTATION', 'UNIVERSAL_PLANNING_STRUCTURE'] for key in required_keys: if key not in plan_data: raise ValueError( f"Plan JSON missing required key: '{key}'. " f"Valid implementation plans must have: {', '.join(required_keys)}" ) return plan_data def validate_feature_name_input(feature_name: str) -> str: """ Validate feature name to prevent path traversal and invalid characters. Args: feature_name: Feature name to validate Returns: Validated feature name Raises: ValueError: If feature name contains invalid characters or path separators """ if not feature_name or not isinstance(feature_name, str): raise ValueError('feature_name must be a non-empty string') if len(feature_name) > 100: raise ValueError('feature_name too long (max 100 characters)') # Only allow alphanumeric, hyphens, and underscores (no path separators) if not re.match(r'^[a-zA-Z0-9_-]+$', feature_name): raise ValueError( f"Invalid feature_name: '{feature_name}'. " "Only alphanumeric characters, hyphens, and underscores are allowed." ) return feature_name

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/srwlli/docs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server