Skip to main content
Glama

validate_diagram_tool

Validate Ilograph diagram YAML syntax and structure to ensure correctness before use, providing detailed error messages and suggestions.

Instructions

    Validates Ilograph YAML syntax and structure.

    This tool performs comprehensive validation of Ilograph diagrams:
    1. First validates YAML syntax for structural correctness
    2. Then validates Ilograph-specific schema requirements
    3. Provides detailed error messages, warnings, and suggestions
    4. Can optionally use official Ilograph specification for context

    Args:
        content: The Ilograph diagram content as a string

    Returns:
        dict: Validation result with success/failure, errors, warnings, and suggestions
             Format: {
                 "success": bool,
                 "yaml_valid": bool,
                 "schema_valid": bool,
                 "summary": {"total_errors": int, "total_warnings": int, "total_info": int},
                 "errors": [{"level": str, "message": str, "line": int, "suggestion": str}, ...],
                 "warnings": [...],
                 "info": [...],
                 "assessment": str
             }
    

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
contentYes

Implementation Reference

  • The main asynchronous handler function for the 'validate_diagram_tool'. It takes diagram content, validates YAML syntax and Ilograph schema using IlographValidator, formats the result with errors/warnings/info, and returns a structured dict response.
    async def validate_diagram_tool(content: str, ctx: Context) -> Dict[str, Any]:
        """
        Validates Ilograph YAML syntax and structure.
    
        This tool performs comprehensive validation of Ilograph diagrams:
        1. First validates YAML syntax for structural correctness
        2. Then validates Ilograph-specific schema requirements
        3. Provides detailed error messages, warnings, and suggestions
        4. Can optionally use official Ilograph specification for context
    
        Args:
            content: The Ilograph diagram content as a string
    
        Returns:
            dict: Validation result with success/failure, errors, warnings, and suggestions
                 Format: {
                     "success": bool,
                     "yaml_valid": bool,
                     "schema_valid": bool,
                     "summary": {"total_errors": int, "total_warnings": int, "total_info": int},
                     "errors": [{"level": str, "message": str, "line": int, "suggestion": str}, ...],
                     "warnings": [...],
                     "info": [...],
                     "assessment": str
                 }
        """
        try:
            await ctx.info("Starting Ilograph diagram validation")
    
            # Validate input
            if not isinstance(content, str):
                error_result = {
                    "success": False,
                    "yaml_valid": False,
                    "schema_valid": False,
                    "errors": [
                        {
                            "level": "error",
                            "message": "Content parameter is required and must be a non-empty string",
                            "suggestion": "Provide the Ilograph diagram content as a string parameter",
                        }
                    ],
                    "assessment": "Invalid - no content provided",
                }
                await ctx.error("Validation failed: no content provided")
                return error_result
    
            content = content.strip()
            if not content:
                error_result = {
                    "success": False,
                    "yaml_valid": False,
                    "schema_valid": False,
                    "errors": [
                        {
                            "level": "error",
                            "message": "Content is empty",
                            "suggestion": "Provide actual Ilograph diagram content",
                        }
                    ],
                    "assessment": "Invalid - no content provided",
                }
                await ctx.error("Validation failed: empty content")
                return error_result
    
            # Create validator and run validation
            validator = IlographValidator()
            result = validator.validate(content)
    
            # Format the result
            formatted_result = format_validation_result(result)
    
            # Log results
            if result.success:
                await ctx.info(
                    f"Validation successful - {len(result.warnings)} warnings, {len(result.info)} suggestions"
                )
            else:
                await ctx.error(
                    f"Validation failed - {len(result.errors)} errors, {len(result.warnings)} warnings"
                )
    
            return formatted_result
    
        except Exception as e:
            error_msg = f"Unexpected error during validation: {str(e)}"
            await ctx.error(error_msg)
            logger.exception("Error in validate_diagram_tool")
    
            return {
                "success": False,
                "yaml_valid": False,
                "schema_valid": False,
                "errors": [
                    {
                        "level": "error",
                        "message": "An unexpected error occurred during validation",
                        "suggestion": "Please check your diagram format and try again",
                    }
                ],
                "assessment": "Error - validation failed",
            }
  • FastMCP decorator that registers the validate_diagram_tool with metadata including title and description. The tool name is derived from the function name.
    @mcp.tool(
        annotations={
            "title": "Validate Ilograph Diagram",
            "readOnlyHint": True,
            "description": "Validates Ilograph diagram syntax and provides detailed error messages with suggestions",
        }
    )
  • Pydantic models defining the structure for validation errors and the overall result, used for output schema and type safety in the tool response.
    class ValidationError(BaseModel):
        """Represents a validation error with detailed information."""
    
        level: str  # 'error', 'warning', 'info'
        message: str
        line: Optional[int] = None
        column: Optional[int] = None
        path: Optional[str] = None
        suggestion: Optional[str] = None
    
    
    class ValidationResult(BaseModel):
        """Represents the complete validation result."""
    
        success: bool
        errors: List[ValidationError] = []
        warnings: List[ValidationError] = []
        info: List[ValidationError] = []
        yaml_valid: bool = False
        schema_valid: bool = False
    
        @property
        def total_issues(self) -> int:
            return len(self.errors) + len(self.warnings)
    
        def add_error(
            self,
            message: str,
            line: Optional[int] = None,
            column: Optional[int] = None,
            path: Optional[str] = None,
            suggestion: Optional[str] = None,
        ) -> None:
            """Add a validation error."""
            self.errors.append(
                ValidationError(
                    level="error",
                    message=message,
                    line=line,
                    column=column,
                    path=path,
                    suggestion=suggestion,
                )
            )
            self.success = False
    
        def add_warning(
            self,
            message: str,
            line: Optional[int] = None,
            column: Optional[int] = None,
            path: Optional[str] = None,
            suggestion: Optional[str] = None,
        ) -> None:
            """Add a validation warning."""
            self.warnings.append(
                ValidationError(
                    level="warning",
                    message=message,
                    line=line,
                    column=column,
                    path=path,
                    suggestion=suggestion,
                )
            )
    
        def add_info(
            self, message: str, path: Optional[str] = None, suggestion: Optional[str] = None
        ) -> None:
            """Add validation info."""
            self.info.append(
                ValidationError(level="info", message=message, path=path, suggestion=suggestion)
            )
  • Core helper class implementing detailed Ilograph diagram validation logic, including YAML syntax check and schema validation for resources, perspectives, relations, etc.
    class IlographValidator:
        """Core validator for Ilograph diagrams."""
    
        def __init__(self) -> None:
            self.known_top_level_properties = {
                "resources",
                "perspectives",
                "contexts",
                "imports",
                "layout",
            }
            self.known_resource_properties = {
                "id",
                "name",
                "subtitle",
                "description",
                "icon",
                "iconStyle",
                "color",
                "children",
                "instanceOf",
                "abstract",
                "alias",
                "for",
            }
            self.known_perspective_properties = {
                "name",
                "description",
                "notes",
                "extends",
                "aliases",
                "overrides",
                "relations",
                "sequences",
            }
            self.known_relation_properties = {
                "from",
                "to",
                "via",
                "label",
                "description",
                "color",
                "arrowDirection",
                "secondary",
            }
    
        def validate_yaml_syntax(
            self, content: str, result: ValidationResult
        ) -> Optional[Dict[str, Any]]:
            """Validate YAML syntax and return parsed data if valid."""
            try:
                # Try to parse the YAML
                data = yaml.safe_load(content)
                result.yaml_valid = True
                return data if isinstance(data, dict) else None
            except yaml.YAMLError as e:
                # Handle problem_mark attribute safely
                line_num = None
                col_num = None
                if hasattr(e, "problem_mark") and e.problem_mark is not None:
                    if hasattr(e.problem_mark, "line"):
                        line_num = e.problem_mark.line
                    if hasattr(e.problem_mark, "column"):
                        col_num = e.problem_mark.column
    
                result.add_error(
                    f"Invalid YAML syntax: {str(e)}",
                    line=line_num,
                    column=col_num,
                    suggestion="Check for indentation issues, missing colons, or invalid characters",
                )
                return None
    
        def validate_top_level_structure(self, data: Dict[str, Any], result: ValidationResult) -> None:
            """Validate the top-level structure of the Ilograph diagram."""
            if not isinstance(data, dict):
                result.add_error(
                    "Ilograph diagram must be a YAML object (dictionary) at the top level",
                    suggestion="Ensure your diagram starts with properties like 'resources:', 'perspectives:', etc.",
                )
                return
    
            # Check for unknown top-level properties
            for key in data.keys():
                if key not in self.known_top_level_properties:
                    result.add_warning(
                        f"Unknown top-level property: '{key}'",
                        path=key,
                        suggestion=f"Valid top-level properties are: {', '.join(sorted(self.known_top_level_properties))}",
                    )
    
            # Check if we have at least resources
            if "resources" not in data:
                result.add_warning(
                    "No 'resources' section found - diagrams typically need resources to be meaningful",
                    suggestion="Add a 'resources:' section with your diagram's components",
                )
    
        def validate_resources(self, resources: Any, result: ValidationResult) -> None:
            """Validate the resources section."""
            if not isinstance(resources, list):
                result.add_error(
                    "The 'resources' property must be a list",
                    path="resources",
                    suggestion="Change 'resources: ...' to 'resources: [...]' or use YAML list syntax with dashes",
                )
                return
    
            resource_ids: Set[str] = set()
            for i, resource in enumerate(resources):
                self.validate_resource(resource, result, f"resources[{i}]", resource_ids)
    
        def validate_resource(
            self, resource: Any, result: ValidationResult, path: str, resource_ids: Set[str]
        ) -> None:
            """Validate a single resource."""
            if not isinstance(resource, dict):
                result.add_error(
                    "Each resource must be an object (dictionary)",
                    path=path,
                    suggestion="Use 'name: ResourceName' format for resources",
                )
                return
    
            # Check for required properties - either 'name' or 'id' should be present
            if "name" not in resource and "id" not in resource:
                result.add_error(
                    "Resource must have either 'name' or 'id' property",
                    path=path,
                    suggestion="Add 'name: YourResourceName' or 'id: your-resource-id'",
                )
    
            # Check for duplicate IDs
            resource_id = resource.get("id")
            if resource_id:
                if resource_id in resource_ids:
                    result.add_error(
                        f"Duplicate resource ID: '{resource_id}'",
                        path=f"{path}.id",
                        suggestion="Resource IDs must be unique across the diagram",
                    )
                else:
                    resource_ids.add(resource_id)
    
            # Check for unknown properties
            for key in resource.keys():
                if key not in self.known_resource_properties:
                    result.add_warning(
                        f"Unknown resource property: '{key}'",
                        path=f"{path}.{key}",
                        suggestion=f"Valid resource properties include: {', '.join(sorted(self.known_resource_properties))}",
                    )
    
            # Validate instanceOf format
            instance_of = resource.get("instanceOf")
            if instance_of and isinstance(instance_of, str):
                if "::" in instance_of:
                    # This looks like a namespace reference - that's good
                    pass
                else:
                    result.add_info(
                        f"Resource uses instanceOf without namespace: '{instance_of}'",
                        path=f"{path}.instanceOf",
                        suggestion="Consider using namespace format like 'AWS::EC2::Instance' or define the resource locally",
                    )
    
            # Validate children if present
            children = resource.get("children")
            if children:
                if not isinstance(children, list):
                    result.add_error(
                        "The 'children' property must be a list",
                        path=f"{path}.children",
                        suggestion="Use list format: 'children: [{name: Child1}, {name: Child2}]'",
                    )
                else:
                    for j, child in enumerate(children):
                        self.validate_resource(child, result, f"{path}.children[{j}]", resource_ids)
    
        def validate_perspectives(self, perspectives: Any, result: ValidationResult) -> None:
            """Validate the perspectives section."""
            if not isinstance(perspectives, list):
                result.add_error(
                    "The 'perspectives' property must be a list",
                    path="perspectives",
                    suggestion="Use list format with dashes: '- name: PerspectiveName'",
                )
                return
    
            for i, perspective in enumerate(perspectives):
                self.validate_perspective(perspective, result, f"perspectives[{i}]")
    
        def validate_perspective(self, perspective: Any, result: ValidationResult, path: str) -> None:
            """Validate a single perspective."""
            if not isinstance(perspective, dict):
                result.add_error(
                    "Each perspective must be an object (dictionary)",
                    path=path,
                    suggestion="Use 'name: PerspectiveName' format for perspectives",
                )
                return
    
            # Check for name (recommended)
            if "name" not in perspective:
                result.add_warning(
                    "Perspective should have a 'name' property",
                    path=path,
                    suggestion="Add 'name: YourPerspectiveName' for better clarity",
                )
    
            # Check for unknown properties
            for key in perspective.keys():
                if key not in self.known_perspective_properties:
                    result.add_warning(
                        f"Unknown perspective property: '{key}'",
                        path=f"{path}.{key}",
                        suggestion=f"Valid perspective properties include: {', '.join(sorted(self.known_perspective_properties))}",
                    )
    
            # Validate relations if present
            relations = perspective.get("relations")
            if relations:
                self.validate_relations(relations, result, f"{path}.relations")
    
        def validate_relations(self, relations: Any, result: ValidationResult, path: str) -> None:
            """Validate relations in a perspective."""
            if not isinstance(relations, list):
                result.add_error(
                    "The 'relations' property must be a list",
                    path=path,
                    suggestion="Use list format with dashes for each relation",
                )
                return
    
            for i, relation in enumerate(relations):
                self.validate_relation(relation, result, f"{path}[{i}]")
    
        def validate_relation(self, relation: Any, result: ValidationResult, path: str) -> None:
            """Validate a single relation."""
            if not isinstance(relation, dict):
                result.add_error(
                    "Each relation must be an object (dictionary)",
                    path=path,
                    suggestion="Use 'from: source, to: target' format",
                )
                return
    
            # Check for required properties
            if "from" not in relation or "to" not in relation:
                result.add_error(
                    "Relation must have both 'from' and 'to' properties",
                    path=path,
                    suggestion="Add both 'from: SourceResource' and 'to: TargetResource'",
                )
    
            # Check for unknown properties
            for key in relation.keys():
                if key not in self.known_relation_properties:
                    result.add_warning(
                        f"Unknown relation property: '{key}'",
                        path=f"{path}.{key}",
                        suggestion=f"Valid relation properties include: {', '.join(sorted(self.known_relation_properties))}",
                    )
    
            # Validate arrowDirection if present
            arrow_direction = relation.get("arrowDirection")
            if arrow_direction:
                valid_directions = {"forward", "backward", "bidirectional"}
                if arrow_direction not in valid_directions:
                    result.add_error(
                        f"Invalid arrowDirection: '{arrow_direction}'",
                        path=f"{path}.arrowDirection",
                        suggestion=f"Valid values are: {', '.join(valid_directions)}",
                    )
    
        def validate_imports(self, imports: Any, result: ValidationResult) -> None:
            """Validate the imports section."""
            if not isinstance(imports, list):
                result.add_error(
                    "The 'imports' property must be a list",
                    path="imports",
                    suggestion="Use list format: '- from: namespace'",
                )
                return
    
            for i, import_item in enumerate(imports):
                if not isinstance(import_item, dict):
                    result.add_error(
                        "Each import must be an object (dictionary)",
                        path=f"imports[{i}]",
                        suggestion="Use format: 'from: namespace, namespace: alias'",
                    )
                    continue
    
                if "from" not in import_item:
                    result.add_error(
                        "Import must have 'from' property",
                        path=f"imports[{i}]",
                        suggestion="Add 'from: namespace/path' to specify what to import",
                    )
    
        def validate_schema(self, data: Dict[str, Any], result: ValidationResult) -> None:
            """Validate the Ilograph schema structure."""
            try:
                self.validate_top_level_structure(data, result)
    
                if "resources" in data:
                    self.validate_resources(data["resources"], result)
    
                if "perspectives" in data:
                    self.validate_perspectives(data["perspectives"], result)
    
                if "imports" in data:
                    self.validate_imports(data["imports"], result)
    
                # If we got here without critical errors, schema is basically valid
                if not result.errors:
                    result.schema_valid = True
                    if not result.warnings:
                        result.add_info(
                            "Diagram structure looks good!",
                            suggestion="Consider adding descriptions to your resources and perspectives for better documentation",
                        )
    
            except Exception as e:
                result.add_error(
                    f"Unexpected error during schema validation: {str(e)}",
                    suggestion="This might indicate a complex structure that needs manual review",
                )
    
        def validate(self, content: str) -> ValidationResult:
            """Main validation method."""
            result = ValidationResult(success=True)
    
            # Step 1: Validate YAML syntax
            data = self.validate_yaml_syntax(content, result)
            if data is None:
                return result  # YAML is invalid, no point in continuing
    
            # Step 2: Validate Ilograph schema
            self.validate_schema(data, result)
    
            # Set final success status
            result.success = len(result.errors) == 0
    
            return result
  • Helper function to convert ValidationResult model into the dictionary format returned by the tool.
    def format_validation_result(result: ValidationResult) -> Dict[str, Any]:
        """Format validation result for JSON response."""
        formatted = {
            "success": result.success,
            "yaml_valid": result.yaml_valid,
            "schema_valid": result.schema_valid,
            "summary": {
                "total_errors": len(result.errors),
                "total_warnings": len(result.warnings),
                "total_info": len(result.info),
            },
        }
    
        if result.errors:
            formatted["errors"] = [error.model_dump() for error in result.errors]
    
        if result.warnings:
            formatted["warnings"] = [warning.model_dump() for warning in result.warnings]
    
        if result.info:
            formatted["info"] = [info.model_dump() for info in result.info]
    
        # Add overall assessment
        if result.success:
            if result.warnings:
                formatted["assessment"] = "Valid with suggestions"
            else:
                formatted["assessment"] = "Valid"
        else:
            formatted["assessment"] = "Invalid - contains errors"
    
        return formatted

Latest Blog Posts

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/QuincyMillerDev/ilograph-mcp-server'

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