Skip to main content
Glama

create_diagram

Idempotent

Generate infrastructure diagrams as code for AWS, Azure, GCP, Kubernetes and other providers by defining nodes, connections and clusters programmatically.

Instructions

Generate infrastructure diagrams with 15+ providers (AWS, Azure, GCP, K8s, etc.).

Examples: AWS: nodes=[{"id":"r53","provider":"aws","category":"network","type":"Route53",...}] K8s: nodes=[{"id":"ing","provider":"k8s","category":"network","type":"Ingress",...}] Clusters: clusters=[{"name":"VPC","node_ids":["elb","ec2"],"graph_attr":{"bgcolor":"#E5F5FD"}}]

⚠️ CRITICAL: Node types must exist in diagrams library or diagram fails silently (no arrows). ALWAYS verify first: list_available_nodes(provider="aws", category="compute") For brands (Stripe, Vercel), use create_diagram_with_custom_icons instead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYesDiagram title
nodesYesList of nodes to include
connectionsYesList of connections between nodes
clustersNoOptional clusters for grouping nodes
directionNoDiagram direction (LR=left-right, TB=top-bottom)LR
curvestyleNoEdge curve styleortho
output_formatNoOutput format(s): png, pdf, jpg, dotpng
output_dirNoOutput directory (default: current directory). Auto-created if missing.
graph_attrNoGraphviz graph attributes
node_attrNoDefault node attributes
edge_attrNoDefault edge attributes
autolabelNoAuto-prefix nodes with class names
return_base64NoInclude base64-encoded images

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Registration of the 'create_diagram' tool using the @mcp.tool decorator, including name, detailed description, input schema via Annotated parameters, and execution hints.
    @mcp.tool(
        name="create_diagram",
        description="""Generate infrastructure diagrams with 15+ providers (AWS, Azure, GCP, K8s, etc.).
    
    Examples:
    AWS: nodes=[{"id":"r53","provider":"aws","category":"network","type":"Route53",...}]
    K8s: nodes=[{"id":"ing","provider":"k8s","category":"network","type":"Ingress",...}]
    Clusters: clusters=[{"name":"VPC","node_ids":["elb","ec2"],"graph_attr":{"bgcolor":"#E5F5FD"}}]
    
    ⚠️ CRITICAL: Node types must exist in diagrams library or diagram fails silently (no arrows).
    ALWAYS verify first: list_available_nodes(provider="aws", category="compute")
    For brands (Stripe, Vercel), use create_diagram_with_custom_icons instead.""",
        annotations={
            "readOnlyHint": False,
            "destructiveHint": False,
            "idempotentHint": True,
        },
    )
  • Core handler function for 'create_diagram' tool. Validates inputs, dynamically imports node classes from diagrams library, constructs Diagram with nodes, connections, clusters, generates multi-format outputs, computes metadata, optionally embeds base64 images, and formats response.
    async def create_diagram(
        name: Annotated[str, Field(description="Diagram title")],
        nodes: Annotated[List[NodeDef], Field(description="List of nodes to include")],
        connections: Annotated[
            List[ConnectionDef], Field(description="List of connections between nodes")
        ],
        clusters: Annotated[
            Optional[List[ClusterDef]],
            Field(description="Optional clusters for grouping nodes"),
        ] = None,
        direction: Annotated[
            Literal["LR", "RL", "TB", "BT"],
            Field(description="Diagram direction (LR=left-right, TB=top-bottom)"),
        ] = "LR",
        curvestyle: Annotated[
            Literal["ortho", "curved"], Field(description="Edge curve style")
        ] = "ortho",
        output_format: Annotated[
            str | List[str],
            Field(description="Output format(s): png, pdf, jpg, dot"),
        ] = "png",
        output_dir: Annotated[
            Optional[str],
            Field(
                description="Output directory (default: current directory). Auto-created if missing."
            ),
        ] = None,
        graph_attr: Annotated[
            Optional[Dict[str, Any]], Field(description="Graphviz graph attributes")
        ] = None,
        node_attr: Annotated[
            Optional[Dict[str, Any]], Field(description="Default node attributes")
        ] = None,
        edge_attr: Annotated[
            Optional[Dict[str, Any]], Field(description="Default edge attributes")
        ] = None,
        autolabel: Annotated[bool, Field(description="Auto-prefix nodes with class names")] = False,
        return_base64: Annotated[bool, Field(description="Include base64-encoded images")] = False,
    ) -> str:
        """Generate infrastructure/architecture diagram."""
        start_time = time.time()
    
        try:
            # Validate all node references
            node_ids = {node.id for node in nodes}
            for node in nodes:
                validate_node_reference(node.provider, node.category, node.type)
    
            # Validate connections reference existing nodes
            for conn in connections:
                if conn.from_node not in node_ids:
                    raise ValueError(
                        f"Connection references unknown node '{conn.from_node}'. "
                        f"Available nodes: {', '.join(sorted(node_ids))}"
                    )
    
                # Handle single target or list of targets
                targets = [conn.to_node] if isinstance(conn.to_node, str) else conn.to_node
                for target in targets:
                    if target not in node_ids:
                        raise ValueError(
                            f"Connection references unknown node '{target}'. "
                            f"Available nodes: {', '.join(sorted(node_ids))}"
                        )
    
            # Validate cluster node references
            if clusters:
                for cluster in clusters:
                    for node_id in cluster.node_ids:
                        if node_id not in node_ids:
                            raise ValueError(
                                f"Cluster '{cluster.name}' references unknown node '{node_id}'. "
                                f"Available nodes: {', '.join(sorted(node_ids))}"
                            )
    
            # Prepare output formats
            formats = [output_format] if isinstance(output_format, str) else output_format
    
            # Reject SVG - it's buggy and unsupported
            if any("svg" in fmt.lower() for fmt in formats):
                raise ValueError("SVG output is not supported. Use png, pdf, jpg, or dot instead.")
    
            # Change to output directory if specified
            original_dir = os.getcwd()
            if output_dir:
                os.makedirs(output_dir, exist_ok=True)
                os.chdir(output_dir)
    
            try:
                # Create diagram with Diagram context manager
                # Hide diagram title by setting label to empty string
                default_graph_attr = {"label": ""}
                merged_graph_attr = {**default_graph_attr, **(graph_attr or {})}
    
                with Diagram(
                    name=name,
                    show=False,  # Never auto-open in MCP server
                    direction=direction,
                    curvestyle=curvestyle,
                    outformat=formats,
                    autolabel=autolabel,
                    graph_attr=merged_graph_attr,
                    node_attr=node_attr or {},
                    edge_attr=edge_attr or {},
                ) as _:
                    # Create all nodes
                    node_objects = {}
                    for node in nodes:
                        # Import the node class
                        NodeClass = import_node_class(node.provider, node.category, node.type)
    
                        # Create node instance
                        node_obj = NodeClass(node.label)
                        node_objects[node.id] = node_obj
    
                    # Handle clusters
                    cluster_objects = {}
                    if clusters:
                        # Build cluster hierarchy (simple approach: process in order)
                        for cluster in clusters:
                            # Create cluster context
                            cluster_ctx = Cluster(
                                cluster.name,
                                graph_attr=cluster.graph_attr or {},
                            )
    
                            # Store for later reference
                            cluster_objects[cluster.name] = {
                                "context": cluster_ctx,
                                "node_ids": cluster.node_ids,
                                "parent": cluster.parent_cluster,
                            }
    
                        # Apply clusters (nodes will be added inside cluster contexts)
                        # For now, we'll use a simplified approach without nesting
                        # Full nesting support would require more complex logic
    
                    # Create all connections
                    edge_count = 0
                    for conn in connections:
                        from_obj = node_objects[conn.from_node]
    
                        # Handle single target or list of targets
                        targets = [conn.to_node] if isinstance(conn.to_node, str) else conn.to_node
    
                        for target in targets:
                            to_obj = node_objects[target]
    
                            # Create edge with optional styling
                            if conn.label or conn.color or conn.style:
                                edge = Edge(
                                    label=conn.label or "",
                                    color=conn.color or "black",
                                    style=conn.style or "solid",
                                )
    
                                if conn.direction == "forward":
                                    _ = from_obj >> edge >> to_obj
                                elif conn.direction == "reverse":
                                    _ = from_obj << edge << to_obj
                                else:  # bidirectional
                                    _ = from_obj - edge - to_obj
                            else:
                                # Simple connection without styling
                                if conn.direction == "forward":
                                    _ = from_obj >> to_obj
                                elif conn.direction == "reverse":
                                    _ = from_obj << to_obj
                                else:  # bidirectional
                                    _ = from_obj - to_obj
    
                            edge_count += 1
    
                # Get generated file paths
                # diagrams library generates files with snake_case names
                diagram_filename = name.replace(" ", "_").replace("-", "_").lower()
                file_paths = []
                for fmt in formats:
                    file_path = f"{diagram_filename}.{fmt}"
                    if output_dir:
                        file_path = os.path.join(output_dir, file_path)
                    file_paths.append(os.path.abspath(file_path))
    
                # Build metadata
                generation_time_ms = (time.time() - start_time) * 1000
                metadata = build_diagram_metadata(
                    file_paths,
                    node_count=len(nodes),
                    edge_count=edge_count,
                    cluster_count=len(clusters) if clusters else 0,
                    generation_time_ms=generation_time_ms,
                )
    
                # Optionally encode images as base64
                base64_images = None
                if return_base64:
                    base64_images = {}
                    for path in file_paths:
                        ext = Path(path).suffix[1:]
                        if ext != "dot":  # Don't encode dot files
                            try:
                                base64_images[ext] = encode_file_base64(path)
                            except Exception:
                                # Non-fatal error, just skip
                                pass
    
                return format_diagram_result(file_paths, metadata, base64_images)
    
            finally:
                # Restore original directory
                if output_dir:
                    os.chdir(original_dir)
    
        except Exception as e:
            return format_error(
                f"Failed to generate diagram: {str(e)}",
                suggestion="Check node types with list_available_nodes tool",
            )
  • Input schema and type definitions for the create_diagram tool parameters, using Pydantic Field for descriptions and validation.
        name: Annotated[str, Field(description="Diagram title")],
        nodes: Annotated[List[NodeDef], Field(description="List of nodes to include")],
        connections: Annotated[
            List[ConnectionDef], Field(description="List of connections between nodes")
        ],
        clusters: Annotated[
            Optional[List[ClusterDef]],
            Field(description="Optional clusters for grouping nodes"),
        ] = None,
        direction: Annotated[
            Literal["LR", "RL", "TB", "BT"],
            Field(description="Diagram direction (LR=left-right, TB=top-bottom)"),
        ] = "LR",
        curvestyle: Annotated[
            Literal["ortho", "curved"], Field(description="Edge curve style")
        ] = "ortho",
        output_format: Annotated[
            str | List[str],
            Field(description="Output format(s): png, pdf, jpg, dot"),
        ] = "png",
        output_dir: Annotated[
            Optional[str],
            Field(
                description="Output directory (default: current directory). Auto-created if missing."
            ),
        ] = None,
        graph_attr: Annotated[
            Optional[Dict[str, Any]], Field(description="Graphviz graph attributes")
        ] = None,
        node_attr: Annotated[
            Optional[Dict[str, Any]], Field(description="Default node attributes")
        ] = None,
        edge_attr: Annotated[
            Optional[Dict[str, Any]], Field(description="Default edge attributes")
        ] = None,
        autolabel: Annotated[bool, Field(description="Auto-prefix nodes with class names")] = False,
        return_base64: Annotated[bool, Field(description="Include base64-encoded images")] = False,
    ) -> str:
  • Helper function to dynamically import node classes from the 'diagrams' library based on provider, category, and type, crucial for runtime node instantiation.
    def import_node_class(provider: str, category: str, node_type: str):
        """Dynamically import a node class from the diagrams library.
    
        Args:
            provider: Provider name (e.g., "aws")
            category: Category name (e.g., "compute")
            node_type: Node type (e.g., "EC2")
    
        Returns:
            Node class
    
        Raises:
            ImportError: If the node class cannot be imported
        """
        try:
            # Construct import path: diagrams.aws.compute
            module_path = f"diagrams.{provider}.{category}"
            module = __import__(module_path, fromlist=[node_type])
    
            # Get the class
            node_class = getattr(module, node_type)
            return node_class
    
        except (ImportError, AttributeError) as e:
            raise ImportError(
                f"Failed to import node '{node_type}' from {provider}.{category}. "
                f"Error: {str(e)}. "
                f"Check that the provider, category, and type are correct."
            )
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds valuable behavioral context beyond annotations. Annotations indicate it's not read-only, is idempotent, and not destructive. The description warns of a critical behavior: 'diagram fails silently (no arrows)' if node types don't exist in the library, and advises verification with list_available_nodes. This disclosure of failure modes and prerequisites enhances transparency for safe usage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured: it starts with the core purpose, provides concrete examples, highlights critical warnings, and gives clear alternatives. Every sentence adds value—no redundancy or fluff. It's front-loaded with essential information and uses formatting (⚠️, ALWAYS) for emphasis without verbosity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (13 parameters, creation operation), the description is complete. It covers purpose, usage guidelines, critical behaviors, and examples. With annotations covering safety (idempotent, non-destructive), 100% schema coverage, and an output schema (implied by context signals), no major gaps remain. It effectively complements structured data.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 13 parameters. The description adds minimal parameter semantics: it provides examples for nodes and clusters parameters (e.g., AWS and K8s node structures, cluster attributes), which helps illustrate usage but doesn't add new information beyond the schema. This meets the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Generate infrastructure diagrams with 15+ providers (AWS, Azure, GCP, K8s, etc.)'. It specifies the verb ('Generate'), resource ('infrastructure diagrams'), and scope ('15+ providers'), distinguishing it from sibling tools like create_flowchart (likely for different diagram types) and create_diagram_with_custom_icons (for brands).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit usage guidance: it tells when to use this tool (for infrastructure diagrams with standard providers) and when not to (for brands like Stripe, Vercel, use create_diagram_with_custom_icons instead). It also includes a prerequisite: 'ALWAYS verify first: list_available_nodes(provider="aws", category="compute")' to avoid silent failures.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/apetta/diagrams-mcp'

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