Skip to main content
Glama

convert_workflow_to_ui

Convert API-format workflows to UI-compatible formats for editing in ComfyUI, assigning node IDs, creating visual links, and arranging layouts.

Instructions

Convert an API format workflow to UI/Litegraph format.

    Args:
        workflow: Workflow in API format (flat dict with class_type/inputs)

    Returns:
        Workflow in UI format compatible with ComfyUI editor.
        This format includes node positions, visual links, and metadata
        required for the workflow to be displayed and edited in the UI.

    The conversion:
    - Assigns integer IDs to nodes
    - Places nodes in a grid layout
    - Creates visual links from input connections
    - Sets appropriate node sizes based on type
    

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
workflowYesWorkflow in API format

Implementation Reference

  • The MCP tool handler for convert_workflow_to_ui, decorated with @mcp.tool(). Includes input schema via Field and delegates to api_to_ui_workflow helper.
    @mcp.tool()
    def convert_workflow_to_ui(
        workflow: dict = Field(description="Workflow in API format"),
        ctx: Context = None,
    ) -> dict:
        """Convert an API format workflow to UI/Litegraph format.
    
        Args:
            workflow: Workflow in API format (flat dict with class_type/inputs)
    
        Returns:
            Workflow in UI format compatible with ComfyUI editor.
            This format includes node positions, visual links, and metadata
            required for the workflow to be displayed and edited in the UI.
    
        The conversion:
        - Assigns integer IDs to nodes
        - Places nodes in a grid layout
        - Creates visual links from input connections
        - Sets appropriate node sizes based on type
        """
        if ctx:
            ctx.info("Converting workflow to UI format")
    
        return api_to_ui_workflow(workflow)
  • Core helper function that performs the actual conversion from API workflow dict to UI-compatible Litegraph format, including node positioning, link creation, and serialization.
    def api_to_ui_workflow(api_workflow: dict) -> dict:
        """Convert API format workflow to SerialisableGraph format (version 1).
    
        API format: { "node_id": {"class_type": "...", "inputs": {...}}, ... }
        UI format: { "version": 1, "nodes": [...], "links": [...], ... }
    
        Based on: ComfyUI_frontend/src/lib/litegraph/src/types/serialisation.ts
    
        Args:
            api_workflow: Workflow in API format
    
        Returns:
            Workflow in SerialisableGraph format compatible with ComfyUI editor
        """
        ui_nodes: list[UINode] = []
        ui_links: list[SerialisableLLink] = []
    
        # Map string node IDs to integer IDs
        node_id_map: dict[str, int] = {}
        for idx, node_id in enumerate(api_workflow.keys(), start=1):
            node_id_map[node_id] = idx
    
        # Track link ID
        link_id = 0
    
        # Compute node sizes for layout
        node_sizes = {
            node_id: get_node_size(data.get("class_type", "")) for node_id, data in api_workflow.items()
        }
    
        # Graph-aware layout using topological layers
        positions = compute_workflow_layout(api_workflow, node_sizes)
    
        for idx, (node_id, node_data) in enumerate(api_workflow.items()):
            int_id = node_id_map[node_id]
            class_type = node_data.get("class_type", "Unknown")
            inputs = node_data.get("inputs", {})
    
            # Get position from graph layout
            pos_x, pos_y = positions.get(node_id, (100, 100))
            size = node_sizes[node_id]
    
            # Separate connection inputs from widget values
            node_inputs: list[UINodeSlot] = []
            node_outputs: list[UINodeSlot] = []
            widgets_values: list = []
    
            for input_name, value in inputs.items():
                if isinstance(value, list) and len(value) == 2:
                    # This is a connection: [source_node_id, output_index]
                    source_node_str = str(value[0])
                    source_slot = value[1]
    
                    if source_node_str in node_id_map:
                        link_id += 1
                        source_int_id = node_id_map[source_node_str]
    
                        # Add input slot
                        node_inputs.append(
                            UINodeSlot(
                                name=input_name,
                                type="*",  # Wildcard type
                                link=link_id,
                                slot_index=len(node_inputs),
                            )
                        )
    
                        # Add link (object format for SerialisableGraph)
                        ui_links.append(
                            SerialisableLLink(
                                id=link_id,
                                origin_id=source_int_id,
                                origin_slot=source_slot,
                                target_id=int_id,
                                target_slot=len(node_inputs) - 1,
                                type="*",
                            )
                        )
                else:
                    # This is a widget value
                    widgets_values.append(value)
    
            # Add a default output for nodes that might be sources
            # (detected by checking if other nodes reference them)
            has_outgoing = any(
                isinstance(inp, list) and len(inp) == 2 and str(inp[0]) == node_id
                for n in api_workflow.values()
                for inp in n.get("inputs", {}).values()
            )
            if has_outgoing or "SaveImage" not in class_type:
                node_outputs.append(UINodeSlot(name="output", type="*", links=[], slot_index=0))
    
            # Create UI node
            ui_node = UINode(
                id=int_id,
                type=class_type,
                pos=(pos_x, pos_y),
                size=size,
                flags=UINodeFlags(),
                order=idx,
                mode=0,
                inputs=node_inputs,
                outputs=node_outputs,
                properties={"Node name for S&R": class_type},
                widgets_values=widgets_values if widgets_values else None,
            )
            ui_nodes.append(ui_node)
    
        # Update output links on source nodes
        for link in ui_links:
            for node in ui_nodes:
                if node.id == link.origin_id and node.outputs:
                    if node.outputs[link.origin_slot].links is None:
                        node.outputs[link.origin_slot].links = []
                    node.outputs[link.origin_slot].links.append(link.id)
    
        # Build UI workflow (SerialisableGraph version 1 format)
        max_node_id = max(node_id_map.values()) if node_id_map else 0
        ui_workflow = UIWorkflow(
            version=1,
            state=UIWorkflowState(
                lastNodeId=max_node_id,
                lastLinkId=link_id,
                lastGroupId=0,
                lastRerouteId=0,
            ),
            nodes=ui_nodes,
            links=ui_links,
            groups=[],
            extra=UIWorkflowExtra(),
        )
    
        return ui_workflow.to_dict()
Behavior4/5

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

With no annotations provided, the description carries full burden and does well by detailing the conversion process: assigns integer IDs, places nodes in grid layout, creates visual links from input connections, and sets node sizes based on type. It doesn't mention error handling, performance characteristics, or authentication needs, but provides substantial behavioral context.

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

Conciseness4/5

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

Well-structured with clear sections (Args, Returns, The conversion) and front-loaded purpose statement. The bullet points under conversion are helpful but slightly verbose. Every sentence adds value, though some information in Returns could be more concise.

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

Completeness4/5

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

For a single-parameter conversion tool with no annotations and no output schema, the description provides good completeness: clear purpose, parameter context, detailed conversion behavior, and output format explanation. It could benefit from error cases or performance notes but covers the essential context well.

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% with one parameter clearly documented. The description adds minimal value beyond the schema by specifying the workflow should be in 'API format (flat dict with class_type/inputs)', which slightly elaborates on the schema's 'Workflow in API format'. Baseline 3 is appropriate given 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 converts an API format workflow to UI/Litegraph format, specifying both the input (flat dict with class_type/inputs) and output (UI format with node positions, visual links, metadata). It distinguishes from siblings like create_workflow or load_workflow by focusing on format conversion rather than creation or loading.

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

Usage Guidelines3/5

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

The description implies usage when needing UI-compatible workflow format for display/editing in ComfyUI editor, but doesn't explicitly state when to use this vs alternatives like validate_workflow or run_workflow. No exclusions or specific prerequisites are mentioned.

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/IO-AtelierTech/comfyui-mcp'

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