Skip to main content
Glama

convert_workflow_to_ui

Transform API-formatted workflows into 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 main handler function for the 'convert_workflow_to_ui' tool, decorated with @mcp.tool(). It wraps the core conversion logic and provides the tool interface.
    @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 implements the logic to convert an API-format workflow dictionary to the UI/Litegraph format used by ComfyUI editor, including node positioning, sizing, linking, 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()
  • Pydantic model defining the structure of the UI workflow output (SerialisableGraph format version 1), used in the conversion process.
    class UIWorkflow(BaseModel): """ComfyUI workflow in SerialisableGraph format (version 1). Based on: ComfyUI_frontend/src/platform/workflow/validation/schemas/workflowSchema.ts zComfyWorkflow1 schema. Version 1 format uses: - `state` object instead of `last_node_id`/`last_link_id` - Object-based links instead of array-based """ version: Literal[1] = 1 state: UIWorkflowState = Field(default_factory=UIWorkflowState) nodes: list[UINode] = Field(default_factory=list) links: list[SerialisableLLink] = Field(default_factory=list) groups: list[dict] = Field(default_factory=list) extra: UIWorkflowExtra | None = Field(default_factory=UIWorkflowExtra) def to_dict(self) -> dict: """Convert to dict for JSON serialization.""" return self.model_dump(exclude_none=True)
  • Top-level registration function that invokes register_workflow_tools(mcp), which defines and registers the convert_workflow_to_ui tool among others.
    def register_all_tools(mcp): """Register all tools with the MCP server.""" register_system_tools(mcp) register_discovery_tools(mcp) register_workflow_tools(mcp) register_execution_tools(mcp)
  • Helper function to determine default node dimensions based on class_type patterns.
    def get_node_size(class_type: str) -> tuple[float, float]: """Get default size for a node type.""" for pattern, size in NODE_SIZES.items(): if pattern in class_type: return size return NODE_SIZES["default"]

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