Skip to main content
Glama
models.py8.22 kB
"""Pydantic models for ComfyUI MCP Server. These models provide structured data validation for ComfyUI API responses and MCP tool parameters. Workflow Formats: - API Format: Flat dict used for execution via /prompt endpoint - UI Format (Litegraph): Visual format with node positions, links, used by ComfyUI editor Schema: https://docs.comfy.org/specs/workflow_json """ from typing import Any, Literal from pydantic import BaseModel, Field # === ComfyUI Response Models === class DeviceInfo(BaseModel): """Device information from ComfyUI /system_stats.""" name: str type: str index: int | None = None vram_total: int vram_free: int torch_vram_total: int torch_vram_free: int class SystemInfo(BaseModel): """System information from ComfyUI /system_stats.""" os: str ram_total: int ram_free: int comfyui_version: str python_version: str pytorch_version: str embedded_python: bool # Optional fields that may not exist in all versions required_frontend_version: str | None = None installed_templates_version: str | None = None class SystemStats(BaseModel): """Complete system statistics from /system_stats.""" system: SystemInfo devices: list[DeviceInfo] class QueueStatus(BaseModel): """Queue status from /queue endpoint.""" queue_running: list[list[Any]] = Field(default_factory=list) queue_pending: list[list[Any]] = Field(default_factory=list) @property def running_count(self) -> int: return len(self.queue_running) @property def pending_count(self) -> int: return len(self.queue_pending) @property def is_empty(self) -> bool: return self.running_count == 0 and self.pending_count == 0 class NodeInput(BaseModel): """Node input specification.""" required: dict[str, Any] = Field(default_factory=dict) optional: dict[str, Any] = Field(default_factory=dict) class NodeInfo(BaseModel): """Information about a ComfyUI node from /object_info.""" name: str display_name: str | None = None description: str | None = None category: str | None = None input: NodeInput = Field(default_factory=NodeInput) output: list[str] = Field(default_factory=list) output_name: list[str] = Field(default_factory=list) output_node: bool = False deprecated: bool = False experimental: bool = False # === Workflow Models === class WorkflowNode(BaseModel): """A node in a ComfyUI workflow.""" class_type: str = Field(description="Node class name") inputs: dict[str, Any] = Field( default_factory=dict, description="Node inputs (values or connections)" ) class Workflow(BaseModel): """A ComfyUI workflow structure (API format).""" nodes: dict[str, WorkflowNode] = Field(default_factory=dict) def to_api_format(self) -> dict: """Convert to ComfyUI API format.""" return {k: v.model_dump() for k, v in self.nodes.items()} @classmethod def from_api_format(cls, data: dict) -> "Workflow": """Create from ComfyUI API format.""" nodes = {} for node_id, node_data in data.items(): nodes[node_id] = WorkflowNode(**node_data) return cls(nodes=nodes) def add_node(self, node_id: str, class_type: str, inputs: dict) -> "Workflow": """Add a node to the workflow.""" self.nodes[node_id] = WorkflowNode(class_type=class_type, inputs=inputs) return self # === UI Workflow Models (Litegraph format) === # Based on: https://docs.comfy.org/specs/workflow_json class UINodeFlags(BaseModel): """Visual flags for a node.""" collapsed: bool = False pinned: bool = False allow_interaction: bool = True horizontal: bool = False skip_repeated_outputs: bool = False class UINodeSlot(BaseModel): """Input or output slot on a node.""" name: str type: str | list[str] link: int | None = None # For inputs: link ID links: list[int] | None = None # For outputs: list of link IDs slot_index: int | None = None class UINode(BaseModel): """A node in Litegraph/UI format.""" id: int type: str pos: tuple[float, float] | dict[str, float] size: tuple[float, float] | dict[str, float] flags: UINodeFlags = Field(default_factory=UINodeFlags) order: int = 0 mode: int = 0 inputs: list[UINodeSlot] = Field(default_factory=list) outputs: list[UINodeSlot] = Field(default_factory=list) properties: dict[str, Any] = Field(default_factory=dict) widgets_values: list[Any] | dict[str, Any] | None = None title: str | None = None color: str | None = None bgcolor: str | None = None class UIWorkflowExtra(BaseModel): """Extra workflow metadata.""" ds: dict[str, Any] | None = Field(default_factory=lambda: {"scale": 1, "offset": [0, 0]}) class SerialisableLLink(BaseModel): """A link in the SerialisableGraph format (object-based, version 1). Based on: ComfyUI_frontend/src/platform/workflow/validation/schemas/workflowSchema.ts zComfyLinkObject schema. """ id: int = Field(description="Link ID") origin_id: int = Field(description="Output node ID") origin_slot: int = Field(description="Output slot index") target_id: int = Field(description="Input node ID") target_slot: int = Field(description="Input slot index") type: str = Field(description="Data type of the link") parentId: int | None = Field(default=None, description="Reroute parent ID") class UIWorkflowState(BaseModel): """State tracking for version 1 workflows. Based on: zGraphState in workflowSchema.ts """ lastNodeId: int = 0 lastLinkId: int = 0 lastGroupId: int = 0 lastRerouteId: int = 0 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) # === Error Models === class ErrorResponse(BaseModel): """Structured error response for MCP tools.""" error: str code: str details: dict | None = None suggestion: str | None = None @classmethod def unavailable(cls, message: str = "ComfyUI is not reachable") -> "ErrorResponse": return cls( error=message, code="COMFY_UNAVAILABLE", suggestion="Ensure ComfyUI is running at the configured URL", ) @classmethod def not_found(cls, resource: str, suggestion: str = None) -> "ErrorResponse": return cls(error=f"{resource} not found", code="NOT_FOUND", suggestion=suggestion) @classmethod def validation_error(cls, message: str, field: str = None) -> "ErrorResponse": return cls( error=message, code="VALIDATION_ERROR", details={"field": field} if field else None ) @classmethod def not_configured(cls, setting: str) -> "ErrorResponse": return cls( error=f"{setting} not configured", code="NOT_CONFIGURED", suggestion=f"Set the {setting} environment variable", ) # === History Models === class HistoryStatus(BaseModel): """Status of a completed workflow.""" status_str: str = "unknown" completed: bool = False messages: list[Any] = Field(default_factory=list) class HistoryEntry(BaseModel): """A history entry from /history endpoint.""" prompt: list[Any] = Field(default_factory=list) outputs: dict[str, Any] = Field(default_factory=dict) status: HistoryStatus | None = None

Implementation Reference

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