MCP server intelligence profile

ComfyUI MCP Server

MCP server + Claude Code plugin for ComfyUI: execute workflows, generate images, visualize pipelines as Mermaid diagrams, compose/validate workflows, manage and download models, control VRAM, and explore custom nodes. 36 tools, cross-platform, installs via npx -y comfyui-mcp

Local Onlyartokun
Awaiting current scanNpm · 0.52.198

The selected current version does not yet have completed public verification. Unknown does not mean clean or vulnerable.

1Distribution channel
41Independently observed tools
0Linked remote endpoints
AvailableVersion intelligence

Detailed security scan evidence is not public for this MCP yet. Public identity, registry metadata, and independently observed protocol inventory remain available.

Install and connect

Installation and connection instructions are shown only when supported by retained package, repository, or endpoint evidence.

Install comfyui-mcp from npm

Install exact version 0.52.198. No verified executable entrypoint is available, so use the package documentation to launch it.

npm install --save-exact comfyui-mcp@0.52.198

Identity

Canonical slugcomfyui-mcp-57fcec71DeploymentLocal Only
Canonical packagenpm:comfyui-mcpRepositoryartokun/comfyui-mcp
First publishedAug 13, 2026Latest releaseAug 14, 2026
Last security verificationClassification confidence90%
PublicationDraftOfficial distributionNot verified

Distributions

ChannelIdentifierCurrent versionVersionsSource
npmcomfyui-mcp0.52.198372Repository

Current release

PackageVersionPublished / observedInventorySecurity scan
npmcomfyui-mcp0.52.198CurrentSep 5, 202641 toolsSucceeded · 0 resources · 0 promptsEvidence restricted
Enterprise protection

Continuously monitor this MCP for security risk

Independently scan the exact version your agents use, receive alerts when its risk changes, and investigate every finding with retained version evidence.

  • Independent exact-version security scans
  • Continuous release and vulnerability monitoring
  • Risk-change alerts with capability context
  • Historical evidence and API exports
Custom pricingContact salesTailored to your organization, integrations, data needs, and support requirements.

Current version evidence

No public current-version evidence is available yet.

Current protocol inventory

2025-06-18Negotiated protocol
comfyui-mcpServer-reported name
3Capability groups
Sep 5, 2026Observed

Tools 41

ToolCategoryAnnotationsRisk
apply_manifestApply a ComfyUI setup manifest from an inline object or .json/.yaml/.yml file. Composes custom-node installs and model downloads, installs pip packages, and reports apt entries as skipped (system packages need manual/root installation). LOCAL ComfyUI: model downloads use the connected server's live/data model roots; pip uses the serving checkout (live main.py root, then COMFYUI_CODE_PATH, then COMFYUI_PATH); filesystem custom-node fallbacks use the live data/base root (live --base-directory, then COMFYUI_PATH). REMOTE ComfyUI: custom_nodes and models are routed through the ComfyUI-Manager HTTP API (handled on the host), while pip and apt entries are reported as skipped (no remote equivalent). Each item reports applied/skipped/failed/pending independently. success is true only when nothing failed AND nothing is still pending. A PARTIAL INSTALL (custom_nodes left unsubmitted when the time budget elapsed) is named in the partial field — a drained Manager queue / panel_node_queue_status does not include those entries; re-run apply_manifest to submit them. Do not restart ComfyUI until they report applied or skipped.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "manifest": {
      "description": "Inline manifest object. Provide exactly one of `manifest` or `path`.",
      "type": "object",
      "properties": {
        "apt": {
          "default": [],
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "pip": {
          "default": [],
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "custom_nodes": {
          "default": [],
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "models": {
          "default": [],
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "model_type": {
                "type": "string",
                "enum": [
                  "checkpoints",
                  "loras",
                  "vae",
                  "upscale_models",
                  "controlnet",
                  "embeddings",
                  "clip",
                  "diffusers",
                  "diffusion_models",
                  "gligen",
                  "hypernetworks",
                  "photomaker",
                  "style_models",
                  "text_encoders",
                  "unet"
                ]
              },
              "filename": {
                "type": "string",
                "minLength": 1
              },
              "local_path": {
                "type": "string",
                "minLength": 1
              }
            },
            "required": [
              "url"
            ],
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },
    "path": {
      "description": "Path to a .json, .yaml, or .yml manifest file. Provide exactly one of `manifest`, `path`, or `pack`.",
      "type": "string"
    },
    "pack": {
      "description": "A bundled installer pack by NAME, as reported by list_packs (action:\"list\"). PREFER THIS over `path` for a bundled pack: the name is resolved against the running build at apply time, while a manifest_path captured earlier points into an npx cache directory that a later respawn no longer has (#1568). Provide exactly one of `manifest`, `path`, or `pack`.",
      "type": "string"
    }
  }
}
appsMicro-apps on this ComfyUI (panel Apps feature): named workflows packaged for one-click runs. Driven by the `action` parameter: - action:"list" — List every registered app. Each entry is the app's manifest: id, name, description, appMode {inputs, outputs}, deps, hideWorkflow, published. No other parameters. Read-only. - action:"get" — One app's manifest + bundle facts (has_workflow/has_prompt/has_thumbnail) by `app_id`. The manifest's appMode.inputs is the app's run form: each input has nodeId, widget, label, kind (text|number|combo|toggle|image|model), optional choices and default. Read-only. - action:"run" — Run one app: patches `values` (keys '<nodeId>.<widget>', e.g. {"6.text": "a cat"}) into the app's stored prompt snapshot and queues it on ComfyUI. Returns the prompt_id — poll action:"run_status". Only pass values for inputs listed in appMode.inputs; omitted inputs keep their conversion-time defaults. - action:"run_status" — Check one run by `app_id` + `prompt_id`: status (pending|running|done|unknown) plus the run's outputs (image/video file refs under each output node, text outputs). Read-only. - action:"import" — Install an app from the public registry: fetches the registry bundle (manifest + prompt snapshot [+ workflow unless hidden]) and creates it locally. The registry id becomes the local id, so re-importing reports an id conflict (already installed). Deps (models/custom nodes) are NOT installed — report the manifest's deps to the user so they can install them before running.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "list",
        "get",
        "run",
        "run_status",
        "import"
      ],
      "description": "Which apps operation to perform. \"list\" takes no other parameters; \"get\"/\"run\" require `app_id`; \"run_status\" requires `app_id` + `prompt_id`; \"import\" requires `registry_url` + `app_id`."
    },
    "app_id": {
      "description": "The app's uuid. REQUIRED for actions \"get\", \"run\", \"run_status\" (from action:\"list\") and \"import\" (the REGISTRY app's uuid, from the explore list).",
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "values": {
      "description": "action:\"run\" — input overrides keyed '<nodeId>.<widget>' (e.g. {\"6.text\": \"a cat\", \"3.seed\": 42}). Unknown keys fail loudly (the manifest drifted from the snapshot).",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "prompt_id": {
      "description": "action:\"run_status\" — the prompt_id returned by action:\"run\". Required for that action.",
      "type": "string",
      "pattern": "^[0-9a-zA-Z-]{1,64}$"
    },
    "registry_url": {
      "description": "action:\"import\" — registry worker base URL (required for that action). Must be the default public registry or an origin the operator allowlisted via COMFYUI_MCP_REGISTRY_URLS (the fetch is server-side — open URLs would be SSRF).",
      "type": "string",
      "format": "uri"
    },
    "slug": {
      "description": "action:\"import\" — the app's registry slug (recorded in local metadata).",
      "type": "string"
    },
    "version": {
      "description": "action:\"import\" — the registry version (recorded in local metadata).",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "action"
  ]
}
batchRun MANY ComfyUI workflows under one durable batch_id. Driven by the `action` parameter: - action:"submit" — Enqueue a batch. Provide EITHER `workflows` (array of API-format workflows) OR one `workflow` plus a `sweep` (array of flat input-override sets — each set produces one job, applied to every node that already has that input, like create_workflow (action:"modify")). Reuses the enqueue_workflow path (seeds re-randomized unless disable_random_seed). Returns { batch_id, count, prompt_ids }; the mapping is persisted to disk and stays valid across server restarts. - action:"status" — Per-job status for `batch_id`: each prompt_id's state (pending/running/done/error/unknown) plus rollup counts and all_terminal. Same status source as queue (action:"status"). - action:"output" — Collected outputs for the batch's COMPLETED jobs: for each done prompt_id, the raw ComfyUI history `outputs` (node id → images/videos/audio filenames, same data get_history reports — feed filenames to get_image action:"get"). Jobs still pending/running are listed with their state; errored jobs carry the error message. Safe to call before the batch finishes. - action:"wait" — Block until every job is terminal (done or error) or `timeout_s` elapses, then return the same rollup as action:"status" plus timed_out/waited_s. Default timeout 300s, hard cap 600s — it can never hang; if timed_out is true, call it again or poll action:"status". Batch ids are durable — they survive server restarts.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "submit",
        "status",
        "output",
        "wait"
      ],
      "description": "Which batch operation to perform. \"submit\" takes workflows | workflow+sweep; \"status\"/\"output\"/\"wait\" each require `batch_id` (\"wait\" also takes `timeout_s`)."
    },
    "workflows": {
      "description": "action:\"submit\" — array of ComfyUI workflows in API format (node ID -> {class_type, inputs}). Mutually exclusive with workflow+sweep.",
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      }
    },
    "workflow": {
      "description": "action:\"submit\" — one base workflow in API format, used with `sweep`.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "sweep": {
      "description": "action:\"submit\" — param sweep: one job per override set, e.g. [{\"cfg\":6},{\"cfg\":8,\"steps\":30}]. Each key is set on every node that already has that input.",
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      }
    },
    "disable_random_seed": {
      "description": "action:\"submit\" — if true, do not randomize seed values (default randomizes per job).",
      "type": "boolean"
    },
    "batch_id": {
      "description": "The batch_id returned by action:\"submit\". Required for actions \"status\", \"output\" and \"wait\".",
      "type": "string"
    },
    "timeout_s": {
      "description": "action:\"wait\" — max seconds to wait (default 300, hard cap 600).",
      "type": "number"
    }
  },
  "required": [
    "action"
  ]
}
bisectBinary-search (git-bisect style) over installed ComfyUI custom nodes to find which one causes a problem. A state machine driven by the `action` parameter: - action:"start" — Begin a session over all currently-enabled custom nodes. Enables half and disables the rest for the first test round, then guide the search with good/bad. Prefers the ComfyUI-Manager HTTP API; falls back to toggling .disabled directory suffixes for local installs. A ComfyUI restart may be needed for changes to take effect. - action:"good" — Mark the currently enabled set as GOOD (the problem is absent with this set). Narrows the search to the disabled candidates and enables the next subset. Resolves and reports the culprit when one node remains. - action:"bad" — Mark the currently enabled set as BAD (the problem is present with this set). Narrows the search to the enabled subset and enables the next subset. Resolves and reports the culprit when one node remains. - action:"reset" — Re-enable all custom nodes and clear the session. Use to abort a bisection or restore the installation after the search completes. - action:"status" — Report the current session state: status (idle/running/resolved), the remaining candidate node set, which nodes are enabled this round, and the identified culprit if resolved. All actions are argument-free; `action` is the only parameter. `good`/`bad` require a session already started with action:"start".
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "start",
        "good",
        "bad",
        "reset",
        "status"
      ],
      "description": "Which bisect operation to perform. \"start\" begins a session; \"good\"/\"bad\" narrow it (require a running session); \"reset\" clears it and re-enables everything; \"status\" reports state. No other arguments are needed for any action."
    }
  },
  "required": [
    "action"
  ]
}
calculateEvaluate a batch of math expressions exactly — no ComfyUI connection needed, so it works even in cloud mode or when ComfyUI is down. A safe, zero-dependency expression evaluator (no eval): numbers only, no strings/arrays/property access. Handy for the arithmetic agents get wrong token-by-token. Each line is one expression. `name = expr` assigns a variable that persists into later lines. Lines are separated by newlines or semicolons ONLY — commas are argument separators (e.g. min(a, b)), never expression separators. Operators: + - * / // (floor div) % (modulo) ** (power, right-assoc), comparisons < <= > >= == != (return 1/0), unary minus. Constants: pi, e, tau. Functions: abs round min max pow sqrt floor ceil sin cos tan asin acos atan atan2 sinh cosh tanh exp log log10 log2 hypot radians degrees sign trunc clamp(x,lo,hi), plus seeded RNG rand() random() uniform(a,b) randint(a,b) (inclusive). Pass `seed` for reproducible RNG; it is echoed back when omitted. Examples: • SDXL-legal resolution from an aspect ratio, snapped to /64: variables={ar: 1.5}; spec="w = floor(sqrt(1024*1024*ar)/64)*64\nh = floor(sqrt(1024*1024/ar)/64)*64" • Reproducible seed batch (one 32-bit seed per line): spec="randint(0, 2**32-1)\nrandint(0, 2**32-1)\nrandint(0, 2**32-1)", seed=42 • CFG sweep: spec="3 + 0*0.5\n3 + 1*0.5\n3 + 2*0.5\n3 + 3*0.5"
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "spec": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      ],
      "description": "Expressions to evaluate, separated by newlines/semicolons (string) or one per array item. `name = expr` assigns; assignments persist across subsequent lines. NOTE: comma is an argument separator (min(a,b)), NOT an expression separator."
    },
    "variables": {
      "description": "Initial variable environment, e.g. {\"w\": 1024, \"ar\": 1.5}.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "number"
      }
    },
    "seed": {
      "description": "Seed for rand()/uniform(a,b)/randint(a,b). Same seed => identical sequence (mulberry32). Omit for a random seed (echoed in the result).",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "spec"
  ]
}
call_toolExecute a tool from the catalog by name. Pass its parameters in `args` (object). The result is exactly what the underlying tool returns.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "name": {
      "description": "Exact tool name from list_tools.",
      "type": "string"
    },
    "tool_name": {
      "description": "Alias for name.",
      "type": "string"
    },
    "args": {
      "description": "The tool's parameters as an object matching its describe_tool schema. A JSON-encoded string is also accepted. Omit for tools without parameters."
    },
    "arguments": {
      "description": "Alias for args."
    },
    "parameters": {
      "description": "Alias for args."
    }
  }
}
clear_vramFree GPU VRAM by unloading cached models from ComfyUI. Use this between generation runs with different model families (e.g. switching from SDXL to Flux) or when running low on VRAM. Optionally unload only models or only memory.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "unload_models": {
      "default": true,
      "description": "Unload all cached models (default: true)",
      "type": "boolean"
    },
    "free_memory": {
      "default": true,
      "description": "Free cached memory/intermediates (default: true)",
      "type": "boolean"
    }
  }
}
comfy_cliDrive the official comfy-cli (envelope/1 JSON contract) for the selected ComfyUI environment. The MCP resolves `comfy` from COMFY_CLI_PATH, PATH, or the selected workspace's .venv/venv. Driven by the `action` parameter: - action:"status" — Inspect the comfy-cli integration and selected environment (`comfy which` / `comfy env`); `detail` selects version/which/env/discover (default env). Call this before local CLI operations when workspace or server routing is uncertain. - action:"server_start" / "server_stop" / "server_restart" — Manage a local ComfyUI through comfy-cli background process management. Restart performs `comfy stop` followed by `comfy launch --background`; extra launch arguments go in `launchArgs`. - action:"jobs_list" — List local or Comfy Cloud jobs (`limit` optional). Local jobs include CLI-tracked async submissions plus the ComfyUI queue/history. - action:"jobs_status" / "jobs_watch" / "jobs_cancel" — Inspect, watch, or cancel one job; `promptId` required. - action:"jobs_wait" — Wait for jobs: one of `promptId`, `promptIds`, or all=true is required; `timeoutSeconds` optional. - action:"search_nodes" — Fuzzy-search actual ComfyUI node classes by name, display name, or description using `comfy nodes search`; `query` required. Complements search_custom_nodes, which searches installable node packs. Works locally, in Comfy Cloud, or offline with `objectInfoPath`. When comfy-cli is not installed/on PATH and the target is the connected (local) server, falls back to fuzzy-searching that server's live /object_info — so installed-node discovery works without the CLI. - action:"workflow_validate" — Validate an API/UI workflow file (class types, inputs, enums, edge wiring) without submission; `workflowPath` required. - action:"workflow_run" — Submit an API/UI workflow file (`workflowPath` required). Asynchronous by default; set wait=true to await outputs (`timeoutSeconds`). - action:"transfer_upload" — Upload input files (`files` required) for local ComfyUI or Comfy Cloud; overwrite=false passes --no-overwrite. - action:"transfer_download" — Download completed outputs for `promptId` (required); `outDir` and `urlOnly` optional. - action:"models_list_folders" / "models_list_folder" / "models_search" / "models_show" — Discover model folders/files locally or in Comfy Cloud (`folder` required for list_folder, `name` for show). For models_show, pass `folder`/`type` or a relative `name` path (e.g. vae/foo.safetensors) when the same basename exists in more than one folder. When comfy-cli is not installed/on PATH and the target is the connected (local) server, these read-only listings fall back to that server's own local models (via /models) — so model discovery works without the CLI. - action:"models_download" — Download a model `url` (required) into the workspace (`relativePath`, default models/checkpoints). A download can run for many minutes and is gated on an idle-liveness timeout, so a progressing download is never killed. - action:"models_remove" — Remove workspace model files (`modelNames` required; `relativePath` optional). - action:"skills_list" / "skills_show" / "skills_validate" / "skills_install" / "skills_status" / "skills_uninstall" — Manage the official comfy-cli bundled agent skills (comfy, fragments, debug, relay, director). validate requires `path`; install/uninstall default to dry-run unless apply=true; scope="project" requires `projectDir`.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "status",
        "server_start",
        "server_stop",
        "server_restart",
        "jobs_list",
        "jobs_status",
        "jobs_wait",
        "jobs_watch",
        "jobs_cancel",
        "search_nodes",
        "workflow_validate",
        "workflow_run",
        "transfer_upload",
        "transfer_download",
        "models_list_folders",
        "models_list_folder",
        "models_search",
        "models_show",
        "models_download",
        "models_remove",
        "skills_list",
        "skills_show",
        "skills_validate",
        "skills_install",
        "skills_status",
        "skills_uninstall"
      ],
      "description": "Which comfy-cli operation to perform. Families: status; server_* (lifecycle); jobs_* (list/status/wait/watch/cancel); search_nodes; workflow_* (validate/run); transfer_* (upload/download); models_* (list_folders/list_folder/search/show/download/remove); skills_* (list/show/validate/install/status/uninstall)."
    },
    "detail": {
      "default": "env",
      "description": "action:\"status\" — which inspection to run.",
      "type": "string",
      "enum": [
        "version",
        "which",
        "env",
        "discover"
      ]
    },
    "workspace": {
      "description": "Optional ComfyUI workspace override (the data/base root comfy-cli uses for custom_nodes/models). Otherwise the live --base-directory / COMFYUI_PATH is used; the CLI executable may still come from the COMFYUI_CODE_PATH checkout's .venv.",
      "type": "string"
    },
    "launchArgs": {
      "description": "actions \"server_start\"/\"server_restart\" — extra ComfyUI launch arguments, e.g. ['--listen','0.0.0.0','--port','8188'].",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "promptId": {
      "description": "Single prompt id. Required for \"jobs_status\"/\"jobs_watch\"/\"jobs_cancel\" and \"transfer_download\"; accepted for \"jobs_wait\" (normalized into a one-element promptIds list).",
      "type": "string"
    },
    "promptIds": {
      "description": "action:\"jobs_wait\" — prompt ids to wait on. Use this or promptId or all=true.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "all": {
      "description": "action:\"jobs_wait\" — wait on every known job.",
      "type": "boolean"
    },
    "limit": {
      "description": "Result cap for \"jobs_list\", \"search_nodes\" and the \"models_*\" listing/search actions.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "timeoutSeconds": {
      "description": "actions \"jobs_wait\"/\"jobs_watch\"/\"workflow_run\" — max seconds to wait.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "where": {
      "description": "Target for the jobs/search_nodes/workflow/transfer/models actions: \"local\" (default) or \"cloud\" (Comfy Cloud).",
      "type": "string",
      "enum": [
        "local",
        "cloud"
      ]
    },
    "query": {
      "description": "action:\"search_nodes\" — fuzzy search text. REQUIRED.",
      "type": "string",
      "minLength": 1
    },
    "objectInfoPath": {
      "description": "action:\"search_nodes\" — offline object_info JSON file to search instead of a live target.",
      "type": "string"
    },
    "workflowPath": {
      "description": "actions \"workflow_validate\"/\"workflow_run\" — path to an API/UI workflow JSON file. REQUIRED.",
      "type": "string",
      "minLength": 1
    },
    "wait": {
      "description": "action:\"workflow_run\" — await outputs instead of returning after submission.",
      "type": "boolean"
    },
    "files": {
      "description": "action:\"transfer_upload\" — input files to upload. REQUIRED.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "outDir": {
      "description": "action:\"transfer_download\" — output directory.",
      "type": "string"
    },
    "overwrite": {
      "description": "action:\"transfer_upload\" — set false to pass --no-overwrite.",
      "type": "boolean"
    },
    "urlOnly": {
      "description": "action:\"transfer_download\" — print URLs instead of downloading files.",
      "type": "boolean"
    },
    "folder": {
      "description": "action:\"models_list_folder\" — the model folder to list (REQUIRED). action:\"models_show\" — optional folder to pick among duplicate basenames.",
      "type": "string"
    },
    "text": {
      "description": "action:\"models_search\" — search text.",
      "type": "string"
    },
    "type": {
      "description": "action:\"models_search\" — model type filter (checkpoint, lora, vae, …). action:\"models_show\" — same filter, to pick among duplicate basenames.",
      "type": "string"
    },
    "name": {
      "description": "actions \"models_show\"/\"skills_show\" — the model or skill name. For models_show a relative path (e.g. vae/qwen_image_vae.safetensors) selects among duplicate basenames.",
      "type": "string"
    },
    "url": {
      "description": "action:\"models_download\" — model URL to download. REQUIRED.",
      "type": "string",
      "format": "uri"
    },
    "relativePath": {
      "description": "actions \"models_download\"/\"models_remove\" — workspace-relative model directory (default models/checkpoints).",
      "type": "string"
    },
    "modelNames": {
      "description": "action:\"models_remove\" — model filenames to remove. REQUIRED.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "path": {
      "description": "action:\"skills_validate\" — path to the skill to validate. REQUIRED.",
      "type": "string"
    },
    "scope": {
      "description": "actions \"skills_install\"/\"skills_uninstall\"/\"skills_status\" — install scope.",
      "type": "string",
      "enum": [
        "user",
        "project"
      ]
    },
    "projectDir": {
      "description": "Working directory for project-scoped skill operations. Required when scope='project'.",
      "type": "string"
    },
    "targets": {
      "description": "actions \"skills_install\"/\"skills_uninstall\" — agent targets.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "skills": {
      "description": "actions \"skills_install\"/\"skills_uninstall\" — skill names.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "apply": {
      "default": false,
      "description": "actions \"skills_install\"/\"skills_uninstall\" — actually mutate; default false = dry-run.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
create_workflowAuthor and check ComfyUI workflow JSON. Driven by the `action` parameter: - action:"create" — Create a ready-to-run API-format workflow from a built-in template (txt2img, img2img, upscale, inpaint, controlnet, ip_adapter, ace_step_15, stable_audio_3, remove_background, ltx_video). Pure local generation — does not contact ComfyUI and has no side effects. Returns the complete workflow JSON; pass it to action:"validate" or enqueue_workflow. Unsupplied `params` fall back to template defaults, so the result may reference checkpoints/models that must exist on your ComfyUI server before it will execute. - action:"modify" — Apply modification `operations` to an existing workflow. Supports: set_input, add_node, remove_node, connect, insert_between. Returns the modified workflow JSON and IDs of any newly added nodes. - action:"validate" — Validate a workflow WITHOUT executing it. Checks for missing node types, broken connections, invalid output indices, missing models, and other issues. Returns a list of errors and warnings. - action:"node_info" — Query a running ComfyUI server's /object_info endpoint for installed node type definitions. Requires a reachable ComfyUI instance; results reflect that server's installed custom nodes. Use the `node_type` filter to inspect a specific node before composing or modifying a workflow. Default response is a STRUCTURAL summary: input/output names and type tags, with enum (dropdown) inputs collapsed to a value count — safe for context even on Loader nodes whose model dropdowns embed the entire local model list (hundreds of KB raw). Pass verbose=true (20 or fewer matches) for the complete raw definitions including every dropdown value. When more than 20 node types match, returns only a name/category list and asks you to narrow the filter.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "create",
        "modify",
        "validate",
        "node_info"
      ],
      "description": "Which authoring operation to perform. \"create\" requires `template` (optional `params`); \"modify\" requires `workflow` + `operations`; \"validate\" requires `workflow` (optional `health`); \"node_info\" takes no required parameters (optional `node_type`, `verbose`, `refresh`)."
    },
    "template": {
      "description": "action:\"create\" (REQUIRED) — Template name, one of: txt2img, img2img, upscale, inpaint, controlnet, ip_adapter, ace_step_15, stable_audio_3, remove_background, ltx_video",
      "type": "string",
      "enum": [
        "txt2img",
        "img2img",
        "upscale",
        "inpaint",
        "controlnet",
        "ip_adapter",
        "ace_step_15",
        "stable_audio_3",
        "remove_background",
        "ltx_video"
      ]
    },
    "params": {
      "default": {},
      "description": "action:\"create\" — Template parameters; recognized keys depend on the template. txt2img: checkpoint, positive_prompt, negative_prompt, width, height, steps, cfg, seed, sampler_name, scheduler. img2img/inpaint add image_path (and mask_path for inpaint) and denoise. upscale adds upscale_model. Unknown keys are ignored; omitted keys use template defaults.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "workflow": {
      "description": "ComfyUI workflow JSON (as a JSON string or object). REQUIRED for action:\"modify\" and action:\"validate\". action:\"validate\" accepts API format or a saved UI export (nodes[]/links[]).",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        }
      ]
    },
    "operations": {
      "description": "action:\"modify\" (REQUIRED) — Array of operations to apply in order. Each has an 'op' field: set_input, add_node, remove_node, connect, or insert_between",
      "type": "array",
      "items": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "op": {
                "type": "string",
                "const": "set_input"
              },
              "node_id": {
                "type": "string"
              },
              "input_name": {
                "type": "string"
              },
              "value": {}
            },
            "required": [
              "op",
              "node_id",
              "input_name",
              "value"
            ]
          },
          {
            "type": "object",
            "properties": {
              "op": {
                "type": "string",
                "const": "add_node"
              },
              "class_type": {
                "type": "string"
              },
              "inputs": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "id": {
                "type": "string"
              }
            },
            "required": [
              "op",
              "class_type"
            ]
          },
          {
            "type": "object",
            "properties": {
              "op": {
                "type": "string",
                "const": "remove_node"
              },
              "node_id": {
                "type": "string"
              }
            },
            "required": [
              "op",
              "node_id"
            ]
          },
          {
            "type": "object",
            "properties": {
              "op": {
                "type": "string",
                "const": "connect"
              },
              "source_id": {
                "type": "string"
              },
              "output_index": {
                "type": "number"
              },
              "target_id": {
                "type": "string"
              },
              "input_name": {
                "type": "string"
              }
            },
            "required": [
              "op",
              "source_id",
              "output_index",
              "target_id",
              "input_name"
            ]
          },
          {
            "type": "object",
            "properties": {
              "op": {
                "type": "string",
                "const": "insert_between"
              },
              "source_id": {
                "type": "string"
              },
              "output_index": {
                "type": "number"
              },
              "target_id": {
                "type": "string"
              },
              "input_name": {
                "type": "string"
              },
              "new_class_type": {
                "type": "string"
              },
              "new_inputs": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              }
            },
            "required": [
              "op",
              "source_id",
              "output_index",
              "target_id",
              "input_name",
              "new_class_type"
            ]
          }
        ]
      }
    },
    "health": {
      "default": true,
      "description": "action:\"validate\" — Include graph-health heuristics (disconnected nodes, duplicate model loads, orphaned branches, muted/bypassed nodes, a sampler running denoise below 1.0 on an empty latent, an image-edit graph whose sampled canvas is not derived from the reference) as info/warning issues plus a structured health section. Never affects `valid`.",
      "type": "boolean"
    },
    "node_type": {
      "description": "action:\"node_info\" — Filter by node class_type name (case-insensitive substring match). Omit to list all available nodes.",
      "type": "string"
    },
    "verbose": {
      "default": false,
      "description": "action:\"node_info\" — If true, return the full raw /object_info definitions including enum dropdown values (model lists etc.) — can be hundreds of KB per Loader node, so only use it when you need the actual enum values (e.g. exact model filenames) and the filter matches few nodes. Default false: structural summary with enum value counts.",
      "type": "boolean"
    },
    "refresh": {
      "default": false,
      "description": "action:\"node_info\" — If true, discard the memoized /object_info snapshot and refetch live from the connected server before answering. Use after the ComfyUI server was restarted EXTERNALLY (systemd/service manager) or new model files were added out-of-band — the cache is otherwise only invalidated by MCP-managed restarts, so loader dropdowns (model lists) would remain stale for the rest of the session (#499).",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
describe_toolGet the full description and JSON Schema of one tool from the catalog. Always call this before the first call_tool of a tool you haven't used in this session.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "name": {
      "description": "Exact tool name from list_tools.",
      "type": "string"
    },
    "tool_name": {
      "description": "Alias for name.",
      "type": "string"
    }
  }
}
download_modelFind model weights and get them onto the connected ComfyUI, and track the transfers. Driven by the `action` parameter: - action:"download" — Download a model file to the connected ComfyUI's models directory from a URL (HuggingFace, direct HTTP(S), s3://, or Azure Blob). Requires `url` + `target_subfolder`. PREFER this over a raw shell download (curl/wget) for model weights: it lands the file in the right models/ subfolder. LOCAL ComfyUI: streams to disk and surfaces live progress in the panel download tray. REMOTE ComfyUI: dispatches the fetch to the ComfyUI host via the ComfyUI-Manager install-model HTTP API (downloaded server-side; a per-request `auth` header can't be forwarded). This requires the host's Manager to run with network_mode=personal_cloud (or loopback) and a permissive security level — a stricter gate silently rejects the download, and Manager reports the queue task 'done' even on failure, so a remote dispatch does not guarantee the file landed. target_subfolder accepts any relative subfolder (incl. nested, e.g. 'loras/<subdir>'). Optional `model_root` is an absolute directory already listed by list_local_models action:"list_paths" (or the resolver's known extra/primary roots) — use it when the live server is unreachable so the file lands in a configured extra-model root instead of <COMFYUI_PATH>/models; invented paths are refused. - action:"status" — Check downloads started by action:"download" / action:"download_civitai". Reports each state, destination, and byte progress when available. Use this after a download reports it is still running — that means the transfer is in flight, NOT that it failed. Across an AGENT/sidebar reconnect, a local stream normally remains resolvable by `id` or `url`; an ORCHESTRATOR RESTART instead reports only that this MCP STOPPED WATCHING, not that the bytes stopped. READ THE ROUTE-AWARE NOTE ON THAT RECORD before acting: a local re-issue is authorized only when the writer persisted the exact staged partial path and matching identity proof, the observed partial state permits it, and any required credential is available. ComfyUI-Manager/host dispatches run on the ComfyUI host and have no local partial; re-issuing one can duplicate the server-side write and CORRUPT the destination. Manager, unknown/legacy, missing-proof, mismatched-proof, and unreadable/absent partial records are not resume authorization. NOT FOUND NEVER MEANS STOPPED: records and carry-over are best-effort. Omit `id` and `url` to list every tracked download. A stale-heartbeat NOTE explains how to cancel after the writer is proven gone; cancellation alone never proves resumability. Read-only. - action:"cancel" — Cancel ONE in-flight download by its `id` (from action:"status" or from the download that started it) — REQUIRED, and it must be the id of the download you mean, since a wrong id stops someone else's transfer. Aborts only that download's transfer; other downloads keep running. An id that names no tracked download is reported as such, not silently treated as success. Cancellation reports the exact route, persisted writer identity, and observed partial state when those facts are available; it never infers that bytes are resumable from `status === "cancelled"` alone. Idempotent: cancelling an already-finished, failed, or already-cancelled download just reports its current state. A download whose AbortController lives in ANOTHER live session cannot be aborted from here (stop it from the panel download tray) — but a download left 'downloading' by a session that is PROVEN gone (heartbeat stale AND its process no longer exists) CAN be closed as cancelled from here. Read the route-aware recovery note before any re-issue: Manager/unknown routes and missing or mismatched identity proof are not resume authorization, and a Manager host may still be fetching with no recall API. While the writer cannot be proven gone, the cancel refuses rather than risk two writers on one file. - action:"search" — Search HuggingFace Hub for models usable in ComfyUI (checkpoints, LoRAs, VAEs, ControlNets, etc.); `query` is required. Read-only and network-only: queries HuggingFace over HTTP, does NOT require a running ComfyUI or COMFYUI_PATH and does not download anything. Returns a ranked list with modelId, author, downloads, likes, and tags. Pick a result's download URL and pass it to action:"download". For CIVITAI searches ('find a Flux LoRA on Civitai') use action:"search_civitai" instead — it filters by type + base model and returns ids for action:"download_civitai". For packs of custom nodes (not models) use search_custom_nodes. - action:"search_civitai" — Search CivitAI by keyword for checkpoints, LoRAs, embeddings, VAEs, and ControlNets — THE action for 'find me a <base model> LoRA on Civitai'. Read-only and network-only (public CivitAI REST API; no token or running ComfyUI required; CIVITAI_API_TOKEN unlocks gated results). Filter by `types` (LORA, Checkpoint, TextualInversion, VAE, Controlnet, …) and `base_models` (CivitAI labels: 'Flux.1 D', 'SDXL 1.0', 'SD 1.5', 'Pony', 'Illustrious', 'Wan Video') — ALWAYS pass base_models when the user's checkpoint family is known, so results actually fit their setup. Each hit returns the model_id and version_id that action:"download_civitai" takes directly, plus trigger words to use in the prompt after installing. Flow: action:"search_civitai" → pick a hit → action:"download_civitai" {model_version_id, target_subfolder} → wire/prompt with the trained words. Pass `creator` (exact username, e.g. from action:"search_creators") to list ONE creator's models — with or without a `query`; at least one of the two is required. SFW-only by default. For HuggingFace search use action:"search". - action:"search_creators" — Find CivitAI CREATORS — THE action for 'who are the top creators on Civitai' and 'find creator <name>'. Read-only and network-only (no token or running ComfyUI required). Two modes: with NO `query` it returns the site's creator LEADERBOARD (civitai.com/leaderboard — rank, score, downloads, likes; pick a `board`: 'overall' [default], 'overall_90' [last 90 days], 'overall_nsfw' [mature], 'new_creators' [first model <30 days ago]); with a `query` it searches usernames (public /api/v1/creators; partial match, returns model counts, NOT ranked). Each hit's username feeds action:"search_civitai" {creator: <username>} directly. SCOPE CAVEAT: the /api/v1/creators index only lists creators who have published MODELS. A creator who posts only images/videos (no models) legitimately returns 0 hits here — that is a gap in this endpoint, NOT proof the creator doesn't exist. For a media-only creator, browse their images via the panel CivitAI browser (panel_open_civitai {creator}) or the logged-in browser session instead. - action:"download_civitai" — Download a model from CivitAI into the connected ComfyUI's models/ directory. Requires `target_subfolder` plus at least one of `model_id` / `model_version_id`. Resolves a CivitAI model id (latest version) or a model-version id to a download URL via the CivitAI REST API. LOCAL ComfyUI (COMFYUI_PATH set): streams the file to disk under <COMFYUI_PATH>/models/<target_subfolder>/ (or under optional `model_root` when that absolute directory is already listed by list_local_models action:"list_paths") and returns the saved absolute path. REMOTE ComfyUI: dispatches the download to the ComfyUI host via the ComfyUI-Manager install-model HTTP API (fetched server-side). Gated/early-access models require CIVITAI_API_TOKEN locally (sent as a bearer header, never in the URL) — or pass a per-request `auth`, which overrides the configured token for that download; remote Manager-side fetches rely on tokens configured on the ComfyUI host. NOTE (remote): the server-side install requires the host's ComfyUI-Manager to run with network_mode=personal_cloud (or loopback) and a permissive security level; a stricter gate silently rejects the download, and Manager reports the queue task 'done' even on failure — so a remote dispatch does not guarantee the file landed. - action:"resolve_missing" — Find the model files a `workflow` needs but this ComfyUI does NOT have, and search CivitAI + HuggingFace for installable candidates. THE action for 'this Template says a model is missing — go get it'. Detects by comparing each model widget against the option list the server actually publishes, so it covers checkpoints, LoRAs, VAEs, ControlNets, UNets, CLIP and custom-pack model types without any per-node mapping. Each candidate reports size, source, precision/quantisation (fp16 / fp8 / GGUF Q4_K_M …) and whether it FITS this GPU's VRAM — so when the exact file is too big you can see the quantised variant that isn't. Read-only: it downloads nothing. Pass a chosen candidate to action:"download" (url) or action:"download_civitai" (id), using the reported directory as target_subfolder. For missing custom NODE PACKS (not models) use list_packs (action:"install_deps") instead.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "download",
        "status",
        "cancel",
        "search",
        "search_civitai",
        "search_creators",
        "download_civitai",
        "resolve_missing"
      ],
      "description": "Which model operation to perform. \"download\" requires `url` + `target_subfolder`; \"status\" takes an optional `id`/`tray_id`/`url` (omit all three to list everything); \"cancel\" requires `id` (optional `tray_id`); \"search\" requires `query`; \"search_civitai\" requires `query` and/or `creator`; \"search_creators\" takes an optional `query` (omit for the leaderboard `board`); \"download_civitai\" requires `target_subfolder` plus `model_id` and/or `model_version_id`; \"resolve_missing\" requires `workflow`."
    },
    "url": {
      "description": "REQUIRED for action:\"download\" — the direct download URL for the model file. OPTIONAL for action:\"status\" — adopt an in-flight download by its source URL when you don't have the id (e.g. after a reconnect); reports the matching job without starting a duplicate.",
      "type": "string",
      "format": "uri"
    },
    "target_subfolder": {
      "type": "string",
      "minLength": 1,
      "description": "REQUIRED for action:\"download\" and action:\"download_civitai\". Target subfolder under ComfyUI models/. Standard names: checkpoints, loras, vae, upscale_models, controlnet, embeddings, clip, diffusers, diffusion_models, gligen, hypernetworks, photomaker, style_models, text_encoders, unet. Any other relative subfolder (incl. nested like 'loras/<subdir>') is allowed; absolute paths and '..' escapes are rejected."
    },
    "filename": {
      "description": "action:\"download\" — override filename (auto-detected from the URL if omitted). action:\"download_civitai\" — override the saved filename (defaults to the CivitAI file name, or the URL basename).",
      "type": "string"
    },
    "auth": {
      "description": "action:\"download\" / action:\"download_civitai\" — optional per-request authentication for private/gated model URLs. When provided it overrides built-in HuggingFace/CivitAI token handling.",
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "bearer"
            },
            "token": {
              "type": "string",
              "minLength": 1,
              "description": "Bearer token value"
            }
          },
          "required": [
            "type",
            "token"
          ]
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "basic"
            },
            "username": {
              "type": "string",
              "description": "Basic auth username"
            },
            "password": {
              "type": "string",
              "description": "Basic auth password"
            }
          },
          "required": [
            "type",
            "username",
            "password"
          ]
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "header"
            },
            "header_name": {
              "type": "string",
              "minLength": 1,
              "description": "HTTP header name"
            },
            "header_value": {
              "type": "string",
              "description": "HTTP header value"
            }
          },
          "required": [
            "type",
            "header_name",
            "header_value"
          ]
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "query"
            },
            "query_param": {
              "type": "string",
              "minLength": 1,
              "description": "Query parameter name"
            },
            "query_value": {
              "type": "string",
              "description": "Query parameter value"
            }
          },
          "required": [
            "type",
            "query_param",
            "query_value"
          ]
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "s3"
            },
            "access_key_id": {
              "type": "string",
              "minLength": 1,
              "description": "AWS/S3-compatible access key id"
            },
            "secret_access_key": {
              "type": "string",
              "minLength": 1,
              "description": "AWS/S3-compatible secret access key"
            },
            "session_token": {
              "description": "Optional temporary session token",
              "type": "string"
            },
            "region": {
              "description": "Optional AWS region override",
              "type": "string"
            },
            "endpoint": {
              "description": "Optional S3-compatible endpoint for R2-style storage",
              "type": "string",
              "format": "uri"
            }
          },
          "required": [
            "type",
            "access_key_id",
            "secret_access_key"
          ]
        }
      ]
    },
    "id": {
      "description": "The download id. REQUIRED for action:\"cancel\" — this is the handle that says WHICH transfer to stop, so take it from action:\"status\" (or from the reply that started the download) rather than guessing; an id that matches nothing is reported as not found. OPTIONAL for action:\"status\" — omit to list every tracked download (incl. in-flight ones from before a reconnect).",
      "type": "string"
    },
    "tray_id": {
      "description": "action:\"status\" / action:\"cancel\" — use this when two rows come back with the SAME `id`, so the id alone cannot say which one you mean. That happens when two different source URLs are downloading to the same destination file. Every row prints its own tray id as `(tray <tray_id>)` — pass that here, together with `id`, to report on (or stop) exactly one of them.",
      "type": "string"
    },
    "query": {
      "description": "REQUIRED for action:\"search\" — the HuggingFace search query (e.g. 'SDXL', 'flux', 'controlnet'). action:\"search_civitai\" — keyword search (e.g. 'detail enhancer', a character name); optional when `creator` is given (then it narrows that creator's models). action:\"search_creators\" — username search (partial match, e.g. 'alcait'); omit to get the top-creators leaderboard instead.",
      "type": "string"
    },
    "filter": {
      "description": "action:\"search\" — optional HuggingFace pipeline/library tag to narrow results, e.g. 'diffusers' or 'text-to-image'.",
      "type": "string"
    },
    "limit": {
      "description": "Max results (default 10, or 8 candidates per missing model for action:\"resolve_missing\"). Per-action ceilings, unchanged from the tools this folds in: \"search\" 50, \"search_civitai\" 25, \"search_creators\" 50, \"resolve_missing\" 20.",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    },
    "creator": {
      "description": "action:\"search_civitai\" — only models by this CivitAI creator (EXACT username — find it with action:\"search_creators\"). At least one of query/creator is required.",
      "type": "string",
      "minLength": 1
    },
    "types": {
      "description": "action:\"search_civitai\" — only these model types (e.g. ['LORA']).",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "Checkpoint",
          "LORA",
          "LoCon",
          "DoRA",
          "TextualInversion",
          "VAE",
          "Controlnet",
          "Upscaler",
          "MotionModule",
          "Workflows"
        ]
      }
    },
    "base_models": {
      "description": "action:\"search_civitai\" — only these base-model families, CivitAI labels: 'Flux.1 D', 'SDXL 1.0', 'SD 1.5', 'Pony', 'Illustrious', 'Wan Video', …",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "sort": {
      "description": "action:\"search_civitai\" — ranking (default 'Highest Rated').",
      "type": "string",
      "enum": [
        "Highest Rated",
        "Most Downloaded",
        "Newest"
      ]
    },
    "nsfw": {
      "description": "action:\"search_civitai\" — include NSFW results (default false).",
      "type": "boolean"
    },
    "board": {
      "description": "action:\"search_creators\" — leaderboard to rank by when no query is given (default 'overall'). Ignored with a query.",
      "type": "string",
      "enum": [
        "overall",
        "overall_90",
        "overall_nsfw",
        "new_creators"
      ]
    },
    "model_version_id": {
      "description": "action:\"download_civitai\" — CivitAI model-version id (from the URL ?modelVersionId=...). If both model_id and model_version_id are given, this selects the specific version of that model.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "model_id": {
      "description": "action:\"download_civitai\" — CivitAI model id. The latest version is used unless model_version_id is also provided.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "model_root": {
      "description": "action:\"download\" / action:\"download_civitai\" — optional absolute model-root directory already listed by list_local_models action:\"list_paths\" (or the resolver's known extra/primary roots). Use this when the live ComfyUI is unreachable so the file lands in a configured extra-model root instead of <COMFYUI_PATH>/models. Invented paths are refused.",
      "type": "string",
      "minLength": 1
    },
    "workflow": {
      "description": "REQUIRED for action:\"resolve_missing\" — the ComfyUI workflow in API format (JSON string or object).",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        }
      ]
    }
  },
  "required": [
    "action"
  ]
}
Annotations
{
  "destructiveHint": true,
  "openWorldHint": true,
  "idempotentHint": false
}
DestructiveNon-idempotentOpen world
enqueue_workflowSubmit work to the ComfyUI execution queue — the primary way an agent starts a render. Driven by the `action` parameter: - action:"enqueue" — Submit an API-format workflow you are already holding (one you built with create_workflow, loaded with get_workflow, or edited with create_workflow action:"modify"). Returns immediately with the prompt_id and queue position; does NOT wait for completion. Seed values in the workflow are used EXACTLY as supplied — they are NOT re-randomized, so a run is reproducible by resubmitting the same workflow (for a fresh-seed re-run of a past job, use action:"rerun"). `workflow` is required. Use queue (action:"status") to check progress later, or get_history (action:"list") to retrieve results and images after completion. - action:"rerun" — Re-run the workflow behind a PREVIOUS generation. Retrieves the prompt graph from execution history (by `prompt_id`, or the most recent run when omitted — chosen by ComfyUI's queue number, same logic as get_history) and re-enqueues it, optionally applying `inputs` overrides. Seeds are re-randomized (within each node's declared range) unless disable_random_seed is set or the seed is pinned via `inputs`. Returns the new prompt_id and the source prompt_id it came from. Clear error if no matching history exists. To re-run from a registered ASSET instead of history, use generate_image (action:"regenerate"). - action:"run_url" — Read (and optionally execute) a SHARED workflow from a URL. Fetches the workflow JSON, accepts API-format prompt graphs or UI-format exports (UI is auto-converted via the same converter as get_workflow), validates it, and summarizes it. Supports raw .json links and GitHub blob/raw URLs (blob is normalized to raw); other share hosts that need a site API return a clear 'paste the raw JSON URL' error. The fetch is bounded (http/https only, timeout + size cap, loopback/private/metadata IPs rejected to prevent SSRF). READ-ONLY unless run=true; when run=true it enqueues the workflow (applying optional `inputs` overrides) and returns the prompt_id. `url` is required. - action:"template_schema" — Get a template's OVERRIDABLE run-time parameters (its 'slots') BEFORE running it. Pass a bundled pack name (from list_packs action:"list") or a custom-node-contributed workflow template name (from list_packs action:"list_templates") as `template`. Returns `slots` — the meaningful knobs: positive/negative prompt, seed, steps, cfg, sampler/scheduler, width/height, checkpoint/LoRA/model files, denoise, batch_size, input image — plus `other_slots` (every remaining overridable widget), each with a stable key "<nodeId>.<widget_name>", semantic role, type, current value, and min/max/options where the node schema is known. Read-only. Feed the keys DIRECTLY into action:"run_template"'s `overrides` (same convention) for a schema→run round-trip. - action:"run_template" — ONE-SHOT: run a named workflow template (a bundled pack from list_packs) with optional `overrides`. Resolves the template's expert graph, applies overrides, and enqueues it — replacing the manual list_packs (action:"read_workflow") → create_workflow (action:"modify") → action:"enqueue" chain. Override keys are '<nodeId>.<widget_name>' (e.g. {'6.text': 'a cat', '3.seed': 42}) — the SAME keys action:"template_schema" reports (when available), so schema→run round-trips; only widget values can be overridden, never graph connections. By default returns {prompt_id} immediately; pass wait:true to block until the job completes and return its outputs (images etc.). Unresolvable template names return a clear error with near-matches. `template` is required.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "enqueue",
        "rerun",
        "run_url",
        "template_schema",
        "run_template"
      ],
      "description": "Which enqueue entry point to use. action:\"enqueue\" requires `workflow`; action:\"rerun\" takes an optional `prompt_id` (+ `inputs`); action:\"run_url\" requires `url` (+ `run`/`inputs`); action:\"template_schema\" and action:\"run_template\" require `template`; with action:\"run_template\" you may also pass `overrides`/`wait`/`timeout_s`."
    },
    "workflow": {
      "description": "action:\"enqueue\" — ComfyUI workflow in API format (node ID -> {class_type, inputs}). REQUIRED for that action.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "disable_random_seed": {
      "description": "If true, do not randomize seed values — applies to action:\"rerun\" and action:\"run_template\" (for action:\"rerun\", combine with inputs.seed to reproduce exactly). It is a NO-OP for action:\"enqueue\", whose seeds are always used exactly as supplied (issue #865).",
      "type": "boolean"
    },
    "prompt_id": {
      "description": "action:\"rerun\" — prompt ID of the generation to re-run. If omitted, uses the most recent execution.",
      "type": "string"
    },
    "inputs": {
      "description": "Overrides applied to every node with a matching input name (e.g. cfg, steps, sampler_name, seed, text). Used by action:\"rerun\", and by action:\"run_url\" only when run=true.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "url": {
      "description": "action:\"run_url\" — URL of the workflow JSON. Raw .json links and GitHub blob/raw URLs work directly. REQUIRED for that action.",
      "type": "string"
    },
    "run": {
      "default": false,
      "description": "action:\"run_url\" — if true, enqueue the fetched workflow for execution and return the prompt_id. Default false: only fetch, validate, and summarize (read-only).",
      "type": "boolean"
    },
    "template": {
      "description": "Template name/id: a bundled pack directory name (list_packs action:\"list\") or a custom-node-contributed workflow template name (list_packs action:\"list_templates\"). REQUIRED for action:\"template_schema\" and action:\"run_template\".",
      "type": "string",
      "minLength": 1
    },
    "overrides": {
      "description": "action:\"run_template\" — widget overrides keyed '<nodeId>.<widget_name>' (action:\"template_schema\"'s keys), e.g. {'6.text': 'a red fox', '3.steps': 20}.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "wait": {
      "description": "action:\"run_template\" — block until the job completes and return its outputs. Default false: return {prompt_id} immediately.",
      "type": "boolean"
    },
    "timeout_s": {
      "description": "action:\"run_template\" — max seconds to wait when wait:true (default 300). On timeout the job keeps running; poll queue (action:\"status\").",
      "type": "number",
      "exclusiveMinimum": 0
    }
  },
  "required": [
    "action"
  ]
}
generate_imageGenerate media from a prompt or an existing image — the high-level entry points that build the graph for you. Every action enqueues on the connected ComfyUI and returns the prompt_id immediately; the resulting asset_id arrives in the completion notification. Driven by the `action` parameter: - action:"image" — Text-to-image. Builds a txt2img workflow, filling any unspecified parameter from your configured defaults (get_defaults (action:"set") / COMFYUI_DEFAULT_* / config file), auto-selecting a local checkpoint when none is given — checkpoints known to lack a text encoder (e.g. video models) are skipped. `prompt` is required. For full control over the node graph, use create_workflow + enqueue_workflow instead. - action:"audio" — Text-to-audio, supporting the ACE Step 1.5 and Stable Audio 3 model families. Builds the appropriate workflow graph, filling unspecified parameters from your defaults and auto-selecting local models. `model_family`, `prompt` and `duration` are required. Requires a running ComfyUI with the corresponding model files installed. - action:"video" — Text-to-video, or image-to-video when `image` is given (animate a start frame). Composes an LTX-2.3 distilled workflow on your LOCAL GPU using the render-verified Comfy-Org node stack (gemma text encoder + abliterated/distilled LoRAs). Needs the LTX-2.3 models (~24-46GB): install with apply_manifest --path packs/ltx-2.3-txt2vid/manifest.yaml (or ltx-2.3-img2vid for i2v); returns an actionable error if the checkpoint is missing. `seconds` is converted to an 8n+1 frame count. For i2v, higher `strength` means MORE adherence to the start frame but LESS motion (1.0 can freeze the clip) — keep ~0.6. This minimal path omits the synchronized audio + stage-2 spatial upscale that the full ltx-2.3 packs ship. `prompt` is required. The video is written under output/video/ — find it with get_image (action:"list_outputs") (VHS/SaveVideo outputs may not appear in /history). - action:"3d" — Generate a 3D model (glb/obj/fbx) from a text prompt or an input image, using the connected ComfyUI's hosted partner 3D nodes (Tripo, Meshy, Rodin, Hunyuan3D — auto-detected from the server; these are paid API nodes needing a comfy.org API key/login on the server or COMFY_API_KEY here). `mode` is required ("text" needs `prompt`, "image" needs `image`). Poll queue (action:"status") / get_history (action:"list") for the resulting model file (saved to ComfyUI's output directory). If the server has no 3D-capable API nodes, returns an actionable error naming local-pack alternatives. - action:"controlnet" — Image conditioned by a ControlNet preprocessed image (pose skeleton, depth, canny, normal, etc.) plus a text prompt. Upload the control image first with upload_image (action:"image"), then pass its filename as `control_image`. `prompt` and `control_image` are required; `checkpoint` and `controlnet_model` auto-resolve from local models. control_image must ALREADY be a preprocessed map (this action does not run the preprocessor); requires a running ComfyUI with a matching controlnet model in models/controlnet/. - action:"ip_adapter" — Image guided by a reference image's style/subject via IP-Adapter, plus a text prompt. Requires the ComfyUI_IPAdapter_plus custom nodes. Upload the reference first with upload_image (action:"image"), then pass its filename as `reference_image`. `prompt` and `reference_image` are required; `checkpoint` auto-resolves. Requires a running ComfyUI with ComfyUI_IPAdapter_plus and a matching IP-Adapter model installed, or the workflow will fail at execution time. - action:"regenerate" — Re-enqueue the workflow that produced an EXISTING ASSET, optionally applying `overrides`. Overrides are applied to any node input matching the key name (e.g. cfg, steps, sampler_name, scheduler, seed, denoise, text). Seeds are re-randomized by default so each call yields a fresh image unless seed is explicitly passed in overrides. `asset_id` is required. To re-run from execution HISTORY rather than a registered asset, use enqueue_workflow (action:"rerun"). - action:"upscale" — Upscale an image with an ESRGAN super-resolution model. Builds an UpscaleModelLoader → ImageUpscaleWithModel workflow (scale=2 supersamples the 4x result back down for sharper output) and enqueues it on your LOCAL GPU. Upload the source first with upload_image (action:"image") (or stage a prior output with upload_image (action:"stage")), then pass its filename as `image`. Needs an upscale model in models/upscale_models/ (e.g. 4x-ClearRealityV1 / 4x_foolhardy_Remacri, provided by the anima/ernie packs or download_model); returns an actionable error if none is found. `image` is required. - action:"remove_background" — Remove an image's background, returning a transparent (RGBA) cutout. Builds a LoadImage → BiRefNetRMBG → SaveImage workflow using the ComfyUI-RMBG (BiRefNet) matting node and enqueues it on your LOCAL GPU. Upload the source first with upload_image (action:"image") (or stage a prior output with upload_image (action:"stage")), then pass its filename as `image`. Requires the ComfyUI-RMBG custom node (pack: wan-transparent, or install_custom_node 'comfyui-rmbg'); the BiRefNet model auto-downloads on first run. If the node isn't installed, returns an actionable error telling you how to install it. `image` is required.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "image",
        "audio",
        "video",
        "3d",
        "controlnet",
        "ip_adapter",
        "regenerate",
        "upscale",
        "remove_background"
      ],
      "description": "What to generate. action:\"image\"/action:\"video\" require `prompt`; action:\"audio\" requires `model_family`+`prompt`+`duration`; action:\"3d\" requires `mode` (+ `prompt` or `image`); action:\"controlnet\" requires `prompt`+`control_image`; action:\"ip_adapter\" requires `prompt`+`reference_image`; action:\"regenerate\" requires `asset_id`; action:\"upscale\" and action:\"remove_background\" require `image`."
    },
    "prompt": {
      "description": "Positive text prompt. REQUIRED for actions \"image\", \"audio\", \"video\", \"controlnet\" and \"ip_adapter\"; for action:\"3d\" it is required in mode \"text\" and optional (passed through only if the chosen node accepts it) in mode \"image\". Unused by action:\"regenerate\", action:\"upscale\" and action:\"remove_background\".",
      "type": "string"
    },
    "negative_prompt": {
      "description": "Negative prompt (default: empty / from defaults). Used by actions \"image\", \"video\", \"controlnet\", \"ip_adapter\" and — for the Stable Audio 3 family only — \"audio\".",
      "type": "string"
    },
    "width": {
      "description": "Image width in pixels. Actions \"image\", \"controlnet\", \"ip_adapter\".",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "height": {
      "description": "Image height in pixels. Actions \"image\", \"controlnet\", \"ip_adapter\".",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "steps": {
      "description": "Sampling steps. Actions \"image\", \"audio\", \"video\", \"controlnet\", \"ip_adapter\" (video defaults to 8 for the distilled model).",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "cfg": {
      "description": "CFG scale. Actions \"image\", \"audio\", \"video\", \"controlnet\", \"ip_adapter\" (video defaults to 1.0 for the distilled model).",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "sampler": {
      "description": "Sampler name (e.g. euler, dpmpp_2m). Actions \"image\", \"audio\", \"controlnet\", \"ip_adapter\".",
      "type": "string"
    },
    "scheduler": {
      "description": "Scheduler (e.g. normal, karras). Actions \"image\", \"audio\", \"controlnet\", \"ip_adapter\".",
      "type": "string"
    },
    "seed": {
      "description": "Seed (omit to randomize). Actions \"image\", \"audio\", \"video\", \"controlnet\", \"ip_adapter\".",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "checkpoint": {
      "description": "Checkpoint filename; auto-selected from local models if omitted. The relevant checkpoint differs per action: a diffusion checkpoint for \"image\"/\"controlnet\"/\"ip_adapter\", the LTX checkpoint for \"video\", the Stable Audio 3 checkpoint for \"audio\".",
      "type": "string"
    },
    "batch_size": {
      "description": "action:\"image\" — number of images to generate.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "filename_prefix": {
      "description": "Output filename prefix. action:\"audio\" (default audio/ace_step or audio/stable_audio_3), action:\"video\" (default 'video/ltx-2.3') and action:\"remove_background\" (default 'ComfyUI_cutout').",
      "type": "string"
    },
    "image": {
      "description": "Filename of an image in ComfyUI's input dir (upload it first with upload_image (action:\"image\"), or stage a prior output with upload_image (action:\"stage\")). REQUIRED for action:\"upscale\" and action:\"remove_background\"; the start frame for action:\"video\" image-to-video; the input image for action:\"3d\" in mode \"image\".",
      "type": "string"
    },
    "model": {
      "description": "Model file for the post-processing actions: action:\"upscale\" — an upscale model in models/upscale_models/ (auto-selected from local models if omitted); action:\"remove_background\" — the BiRefNet matting model (default 'BiRefNet_toonout'; auto-downloaded by ComfyUI-RMBG).",
      "type": "string"
    },
    "scale": {
      "description": "action:\"upscale\" — net upscale factor: 2 or 4 (default 4).",
      "anyOf": [
        {
          "type": "number",
          "const": 2
        },
        {
          "type": "number",
          "const": 4
        }
      ]
    },
    "control_image": {
      "description": "action:\"controlnet\" — filename of the (already-uploaded, already-preprocessed) control image in ComfyUI's input dir. REQUIRED for that action.",
      "type": "string"
    },
    "controlnet_model": {
      "description": "action:\"controlnet\" — ControlNet model file (in models/controlnet/); auto-selected if omitted.",
      "type": "string"
    },
    "reference_image": {
      "description": "action:\"ip_adapter\" — filename of the (already-uploaded) reference image in ComfyUI's input dir. REQUIRED for that action.",
      "type": "string"
    },
    "weight": {
      "description": "action:\"ip_adapter\" — IP-Adapter influence on the output, typically 0.0-1.0 (default 0.8); higher = closer to the reference.",
      "type": "number"
    },
    "preset": {
      "description": "action:\"ip_adapter\" — IPAdapterUnifiedLoader preset (default 'PLUS (high strength)').",
      "type": "string"
    },
    "weight_type": {
      "description": "action:\"ip_adapter\" — IPAdapter weight mode (default 'standard' — required by current IPAdapter_plus builds).",
      "type": "string",
      "enum": [
        "standard",
        "prompt is more important",
        "style transfer"
      ]
    },
    "strength": {
      "description": "Two DIFFERENT knobs sharing one field, each with its own range, checked when the action runs: action:\"video\" (i2v only) — adherence to the start frame, 0-1 inclusive (default 0.6; higher = LESS motion); action:\"controlnet\" — conditioning strength, must be > 0, typically 0.0-2.0 (default 1.0; higher = stronger adherence to the control image).",
      "type": "number"
    },
    "seconds": {
      "description": "action:\"video\" — clip length in seconds (default 4; ~10s max).",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "resolution": {
      "description": "action:\"video\" — 'WIDTHxHEIGHT' e.g. '768x512' (rounded to multiples of 32; default 768x512).",
      "type": "string"
    },
    "fps": {
      "description": "action:\"video\" — frames per second (default 25).",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "mode": {
      "description": "action:\"3d\" — \"text\" = text-to-3D from `prompt`; \"image\" = image-to-3D from an uploaded input `image`. REQUIRED for that action.",
      "type": "string",
      "enum": [
        "text",
        "image"
      ]
    },
    "node": {
      "description": "action:\"3d\" — explicit 3D API node class_type to use (e.g. \"MeshyTextToModelNode\"); auto-selected if omitted. Use list_api_nodes with filter \"3d\" to see options.",
      "type": "string"
    },
    "inputs": {
      "description": "action:\"3d\" — provider-specific extra inputs passed through to the node (e.g. style, texture, quality). Use list_api_nodes (action:\"schema\") on the chosen node for valid keys.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "model_family": {
      "description": "action:\"audio\" — audio model family; determines which workflow template and model loaders to use. REQUIRED for that action.",
      "type": "string",
      "enum": [
        "ace_step_1.5",
        "stable_audio_3"
      ]
    },
    "duration": {
      "description": "action:\"audio\" — audio duration in seconds. REQUIRED for that action.",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "unet": {
      "description": "action:\"audio\" — ACE UNet model filename (in models/diffusion_models/); auto-selected if omitted.",
      "type": "string"
    },
    "vae": {
      "description": "action:\"audio\" — ACE VAE model filename (in models/vae/); auto-selected if omitted.",
      "type": "string"
    },
    "clip_a": {
      "description": "action:\"audio\" — primary text encoder filename (in models/text_encoders/); auto-selected if omitted.",
      "type": "string"
    },
    "clip_b": {
      "description": "action:\"audio\" — secondary text encoder filename (in models/text_encoders/); auto-selected if omitted.",
      "type": "string"
    },
    "clip": {
      "description": "action:\"audio\" — Stable Audio CLIP encoder filename (in models/text_encoders/); auto-selected if omitted.",
      "type": "string"
    },
    "lyrics": {
      "description": "action:\"audio\" — lyrics or song structure description (ACE only — section-by-section breakdown).",
      "type": "string"
    },
    "language": {
      "description": "action:\"audio\" — language code for prompt (ACE only, default: 'en').",
      "type": "string"
    },
    "musical_key": {
      "description": "action:\"audio\" — target musical key (ACE only, e.g. 'C major', 'E minor'; default: 'C major').",
      "type": "string"
    },
    "shift": {
      "description": "action:\"audio\" — ModelSamplingAuraFlow shift parameter (ACE only, default: 3).",
      "type": "number"
    },
    "guidance_scale": {
      "description": "action:\"audio\" — TextEncodeAceStepAudio1.5 cfg_scale, the text encoder guidance scale (ACE only, default: 2).",
      "type": "number"
    },
    "bpm": {
      "description": "action:\"audio\" — TextEncodeAceStepAudio1.5 tempo in beats per minute (ACE only, 10-300, default: 120).",
      "type": "integer",
      "minimum": 10,
      "maximum": 300
    },
    "timesignature": {
      "description": "action:\"audio\" — TextEncodeAceStepAudio1.5 time signature (ACE only, one of '2'/'3'/'4'/'6', default: '4').",
      "type": "string",
      "enum": [
        "2",
        "3",
        "4",
        "6"
      ]
    },
    "temperature": {
      "description": "action:\"audio\" — TextEncodeAceStepAudio1.5 LLM sampling temperature (ACE only, 0-2, default: 0.85).",
      "type": "number",
      "minimum": 0,
      "maximum": 2
    },
    "top_p": {
      "description": "action:\"audio\" — TextEncodeAceStepAudio1.5 LLM top-p nucleus sampling (ACE only, 0-2000, default: 0.9).",
      "type": "number",
      "minimum": 0,
      "maximum": 2000
    },
    "top_k": {
      "description": "action:\"audio\" — TextEncodeAceStepAudio1.5 LLM top-k sampling (ACE only, 0-100, default: 0 = disabled).",
      "type": "integer",
      "minimum": 0,
      "maximum": 100
    },
    "min_p": {
      "description": "action:\"audio\" — TextEncodeAceStepAudio1.5 LLM min-p sampling (ACE only, 0-1, default: 0).",
      "type": "number",
      "minimum": 0,
      "maximum": 1
    },
    "generate_audio_codes": {
      "description": "action:\"audio\" — generate audio codes via the TextEncodeAceStepAudio1.5 LLM (ACE only, default: true).",
      "type": "boolean"
    },
    "audio_quality": {
      "description": "action:\"audio\" — SaveAudioMP3 bitrate/quality (ACE and stable_audio_3, one of 'V0'/'128k'/'320k', default: '320k').",
      "type": "string",
      "enum": [
        "V0",
        "128k",
        "320k"
      ]
    },
    "asset_id": {
      "description": "action:\"regenerate\" — asset id of the source generation. REQUIRED for that action.",
      "type": "string"
    },
    "overrides": {
      "description": "action:\"regenerate\" — map of input-name → new value applied to every node that already has that input. Common keys: cfg, steps, sampler_name, scheduler, seed, denoise, text.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "disable_random_seed": {
      "description": "action:\"regenerate\" and action:\"3d\" — if true, do not randomize seed fields. For action:\"regenerate\", combine with `overrides.seed` to reproduce the exact original image.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
get_defaultsRead and write settings — either OUR generation defaults or ComfyUI's own frontend UI settings. These are two SEPARATE stores and the `action` says which one you mean: - action:"get" — Return the merged view of OUR generation defaults with per-source attribution. Precedence (lowest → highest): config file → COMFYUI_DEFAULT_* env vars → runtime overrides via action:"set". Per-call MCP tool args always win over these defaults when consumed by a workflow-construction tool. Read-only, and works even with no ComfyUI running. - action:"set" — Update OUR generation defaults from `values`. By default updates the in-memory runtime layer (lost on restart); pass persist:true to also write the change into the config file (~/.config/comfyui-mcp/config.json by default). Use this to avoid repeating common values like width, height, steps, cfg, sampler, checkpoint. - action:"get_ui" — Read COMFYUI's OWN per-user frontend UI settings (the Comfy.* ids its Settings panel writes, served by the frontend user manager). This is a DIFFERENT store from action:"get" — nothing here feeds our generation defaults. Read-only. Provide `id` to read one setting's raw stored value; omit `id` to list all stored settings (optionally narrowed by `filter`). Known ids include Comfy.Validation.Workflows (boolean; its strictness rejects some custom-node workflows), Comfy.Execution.PreviewMethod (default|none|auto|latent2rgb|taesd), Comfy.LinkRenderMode (0 straight / 1 linear / 2 spline / 3 hidden), Comfy.UseNewMenu, and Comfy.Sidebar.Location. Ids are frontend-defined and stored verbatim; keys never written by the user are absent here and fall back to invisible frontend defaults. Values are surfaced with their raw stored type (no coercion). Requires a reachable local or remote ComfyUI; not available in Comfy Cloud mode. - action:"set_ui" — Modify one of COMFYUI's OWN persisted frontend UI settings by `id`. This writes ComfyUI's user settings store, NOT our generation defaults (that is action:"set"). The change is persisted immediately and takes effect on the next frontend load/refresh (an already-open UI tab keeps its old value until reloaded). The value is stored as-is: booleans/numbers are NOT coerced from strings, so pass true (not "true") and 2 (not "2"). Known ids: Comfy.Validation.Workflows (boolean; loosening it lets stricter custom-node workflows load), Comfy.Execution.PreviewMethod (default|none|auto|latent2rgb|taesd), Comfy.LinkRenderMode (0 straight / 1 linear / 2 spline / 3 hidden), Comfy.UseNewMenu, Comfy.Sidebar.Location. Ids are frontend-defined; an unknown id is stored verbatim and simply ignored by the UI. Returns { id, previous, value } — the prior value is read first so you can report and undo the change (previous is null when the key was unset).
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "get",
        "set",
        "get_ui",
        "set_ui"
      ],
      "description": "Which settings operation to perform, and on WHICH store. \"get\"/\"set\" are the MCP server's own generation defaults (width, steps, cfg, …); \"get_ui\"/\"set_ui\" are ComfyUI's separate frontend UI settings (the Comfy.* ids). \"get\" takes no other parameters; \"set\" requires `values` (optional `persist`); \"get_ui\" takes an optional `id` or `filter`; \"set_ui\" requires `id` + `value`."
    },
    "values": {
      "description": "action:\"set\" — REQUIRED. Key/value map of GENERATION defaults to set. Keys are typically lowercase (e.g. width, steps). Not for Comfy.* UI ids — those go through action:\"set_ui\".",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "persist": {
      "description": "action:\"set\" — if true, write to the config file in addition to runtime.",
      "type": "boolean"
    },
    "id": {
      "description": "ComfyUI UI setting id, e.g. 'Comfy.Validation.Workflows'. REQUIRED for action:\"set_ui\". OPTIONAL for action:\"get_ui\" — omit to list all stored settings.",
      "type": "string"
    },
    "filter": {
      "description": "action:\"get_ui\" — case-insensitive substring filter on setting ids when listing (e.g. 'preview'). Ignored when `id` is given.",
      "type": "string"
    },
    "value": {
      "description": "action:\"set_ui\" — REQUIRED. New value for the ComfyUI UI setting. Stored as-is; booleans/numbers are NOT coerced from strings (pass true, not \"true\").",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "number"
        },
        {
          "type": "boolean"
        },
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        },
        {
          "type": "array",
          "items": {}
        }
      ]
    }
  },
  "required": [
    "action"
  ]
}
get_historyRead what has already been generated on this machine — execution history, why a run failed, and the settings your past renders actually used. Driven by the `action` parameter: - action:"list" — Execution history for a ComfyUI prompt: status, timing, cached nodes, and output details (media filenames for get_image action:"get"). Also carries the raw error/traceback. To diagnose WHY a run FAILED or what is missing, prefer action:"diagnose" — it returns the same failure info PLUS missing models (with the file + widget) and missing node types, which this action does not. Use action:"list" when you need the run's OUTPUTS or timing for a specific prompt_id. - action:"diagnose" — WHY DID MY RENDER FAIL / WHAT IS MISSING? Explains a failed run in ONE call, without needing a canvas — the headless counterpart to the panel's panel_get_errors ("why is this red?"), so mobile/remote sessions get the same answer. Returns: the failed node (id, type) with its `exception_type` + message and a trimmed traceback; **missing_models** (the exact model file that is not installed and the widget holding it — feed the filename to download_model action:'search_civitai', then action:'download_civitai' — or action:'search' then action:'download' — to fix it); **missing_node_types** (node classes this install lacks — feed to search_custom_nodes, then install_custom_node); and any other per-input validation errors. Call this whenever a run fails, an enqueue is rejected, or the user asks what is missing — instead of guessing from raw logs. With no prompt_id it diagnoses the most recent FAILED run (falling back to the most recent run). Read-only. - action:"stats" — Statistics from this MCP server's LOCAL generation-history database (populated as you run workflows; NOT from ComfyUI, and not the same source as action:"list"): total generations, count of unique sampler/scheduler/steps/CFG combos, a per-model-family breakdown, and the most-reused settings. Read-only; works without a running ComfyUI. Returns empty stats until you have generated images. For concrete recommended settings rather than aggregate counts, use action:"suggest". - action:"suggest" — Recommend concrete, proven sampler/scheduler/steps/CFG (and denoise/shift/LoRA) settings derived from that same LOCAL generation-history database. Read-only and works without a running ComfyUI. Narrow results by `model_family`, `lora_hash`, or a name `search`; with no filter it returns the top settings across all history. Returns a ranked list with each combo's reuse count, or a "no history" message until you have generated images. Use this for ready-to-apply values; use action:"stats" for aggregate counts and breakdowns rather than specific suggestions.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "list",
        "diagnose",
        "stats",
        "suggest"
      ],
      "description": "Which history view to return. \"list\" and \"diagnose\" read ComfyUI's execution history and take an optional `prompt_id`; \"stats\" and \"suggest\" read this server's own local generation-settings database and take `model_family` (plus `lora_hash`/`search`/`limit` for \"suggest\"). No action requires any other field."
    },
    "prompt_id": {
      "description": "Actions \"list\" and \"diagnose\" — the prompt ID to look up (returned by enqueue_workflow). For action:\"list\", if omitted, returns the most recent COMMITTED execution (chosen by ComfyUI's queue number, not dict order); immediately after a run finishes it can briefly lag by one until ComfyUI commits the new entry, so pass the prompt_id from enqueue_workflow to get that exact run, and prefer the run-finished event for naming a just-produced output. For action:\"diagnose\", omit to diagnose the most recent FAILED run — preferred over a newer successful one — falling back to the most recent run if nothing failed.",
      "type": "string"
    },
    "model_family": {
      "description": "Actions \"stats\" and \"suggest\" — model-family key to scope to, e.g. 'sdxl', 'flux', 'qwen_image', 'illustrious'.",
      "type": "string"
    },
    "lora_hash": {
      "description": "action:\"suggest\" — AutoV2 hash (10 chars) of a specific LoRA to find settings for.",
      "type": "string"
    },
    "search": {
      "description": "action:\"suggest\" — full-text search on model/LoRA filenames (e.g. 'copax', 'lightning').",
      "type": "string"
    },
    "limit": {
      "description": "action:\"suggest\" — max results (default 10).",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    }
  },
  "required": [
    "action"
  ]
}
get_imageFetch, browse and inspect ComfyUI images and registered assets. Driven by the `action` parameter: - action:"get" — Fetch a generated image from ComfyUI by FILENAME and return it as an inline image. Video/audio outputs (e.g. a VHS_VideoCombine .mp4) and allowlisted mesh/material attachments (.obj, .glb, .gltf, .fbx, .ply, .stl, .mtl) are saved to save_dir with their original extension instead of being rendered inline. Works with remote ComfyUI instances — does not require COMFYUI_PATH. Use get_history (action:"list") first to obtain the filename; if it returns `subfolder/filename`, pass that relative path as-is and get_image will split it automatically. - action:"view" — Fetch a registered asset's bytes by ASSET ID and return them as an inline image so the agent can see the result. Use this after a render completes (asset_id is included in the completion notification) to inspect, critique, or compare generated images. Only supports image mime types (PNG/JPEG/WebP); audio/video assets must be saved to disk via action:"get". - action:"list_outputs" — List recently generated image AND video files from ComfyUI's output/ directory, newest-first, with each file's kind ('image' | 'video'), subfolder, size, and modification time. Covers stills (.png/.jpg/.jpeg/.bmp) and video/animation outputs (.mp4/.webm/.mov/.mkv/.m4v/.avi/.gif/.webp). LOCAL ComfyUI (COMFYUI_PATH set): a RECURSIVE filesystem scan of output/ (stills + video, including subfolders like video/ that VHS/SaveVideo write to) AND of temp/ for video files — VHS_VideoCombine with `save_output` unchecked writes the completed .mp4 (including the "-audio.mp4" a run completion names) there; those entries are tagged type:"temp" so action:"get" / upload_image (action:"stage") can fetch them. Reports size + modification time. Preview stills in temp/ (PreviewImage) are omitted. REMOTE ComfyUI: derives the list from /history over HTTP instead (size/modified are unavailable and omitted) and includes type:"temp" videos from history the same way. It does NOT return the media bytes themselves — fetch those with action:"get". USE THIS TO CONFIRM A VIDEO RENDER (e.g. VHS_VideoCombine / LTX / WAN output) when get_history (action:"list") shows the prompt done but lists no output: VHS-style video nodes write the file but often do NOT register in ComfyUI's /history, so the local filesystem scan is the reliable way to verify the .mp4 exists — then chain it with upload_image (action:"stage"). THAT GUARANTEE IS LOCAL-ONLY AND INVERTS ON A REMOTE TARGET: with no disk to scan, this falls back to /history, so a REMOTE listing can neither confirm nor deny a VHS video that never registered, and absence from it is NOT evidence the file is missing. Check a specific filename with action:"get" or upload_image (action:"stage") instead — both read /view. Every remote result says so in its own text. Read-only. - action:"convert" — Re-encode a generated image to PNG, JPEG, or WebP and return it inline as an image content block. Source can be a registered asset_id or a path under the local ComfyUI output directory. Optionally writes the converted image back under the output directory and reports source/output size plus bytes saved. - action:"analyze_color" — Measure the color of a rendered image (not by eye): returns black/white points, contrast (luma std), saturation, per-channel means + cast, and clipping — plus heuristic flags (washedOut, lowContrast, liftedBlacks, dimHighlights, lowSaturation, colorCast) and a one-line verdict. Source = asset_id, a ComfyUI output ref (filename/subfolder/type), or an image path. Pass reference_path to shot-match against a known-good frame (target−reference deltas). Set histogram:true to also get an overlaid R/G/B/luma histogram PNG. Use this to diagnose 'washed out' objectively and decide a color fix; for a video, extract a frame to PNG first. - action:"list_assets" — List recently generated assets, newest-first. Each call first reconciles ComfyUI's /history, so outputs are listed even when this session did not watch the render complete (e.g. queued via panel_run, by an earlier session, or before a server restart) — those are tagged source:'history-reconcile', versus source:'watched' for renders this server saw finish. Newly reconciled image refs are checked through /view before registration; stale or unavailable refs are omitted and disclosed in the response note. Returns count + assets (asset_id, prompt_id, filename, url, source, created_at). The registry is ephemeral and clears on server restart; records expire after COMFYUI_ASSET_TTL_HOURS (default 24h), and only the most recent completed runs are reconciled — use get_history (action:"list") / action:"get" by filename for anything older. - action:"asset_metadata" — Get full provenance for a registered asset including the workflow snapshot that produced it. Use this to inspect the parameters that generated an image before calling generate_image (action:"regenerate") with overrides.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "get",
        "view",
        "list_outputs",
        "convert",
        "analyze_color",
        "list_assets",
        "asset_metadata"
      ],
      "description": "Which image/asset operation to perform. \"get\" requires `filename`; \"view\" and \"asset_metadata\" require `asset_id`; \"convert\" requires `format` plus exactly one of `asset_id`/`path`; action:\"analyze_color\" takes one source (`asset_id`, `filename`, or `path`); \"list_outputs\" and action:\"list_assets\" take no required parameters."
    },
    "filename": {
      "description": "Output image filename or a relative `subfolder/filename` reference from get_history, e.g. PulID_Klein_00001_.png or out_F/PulID_Klein_00001_.png. REQUIRED for action:\"get\". Relative prefixes are split automatically; absolute paths, drive prefixes, and `..` segments are refused. OPTIONAL for action:\"analyze_color\", where it is one of the three ways to name a source (pair it with subfolder/type).",
      "type": "string"
    },
    "asset_id": {
      "description": "Asset id returned by action:\"list_assets\" or job completion. REQUIRED for actions \"view\" and \"asset_metadata\". OPTIONAL for \"convert\" (provide exactly one of asset_id or path) and action:\"analyze_color\" (one of asset_id, filename, or path).",
      "type": "string"
    },
    "type": {
      "description": "ComfyUI directory the file lives in: output (default), input, or temp. Used by action:\"get\" and by action:\"analyze_color\" when the source is a `filename`.",
      "type": "string",
      "enum": [
        "output",
        "input",
        "temp"
      ]
    },
    "subfolder": {
      "description": "Subfolder within the directory, if any (default empty). Used by action:\"get\" and by action:\"analyze_color\" when the source is a `filename`. If filename already includes a relative prefix, it is combined with this subfolder.",
      "type": "string"
    },
    "save_dir": {
      "description": "action:\"get\" — absolute local directory to save the file in. Defaults to a 'comfyui-images' folder inside the platform temp directory (os.tmpdir()), which is created if missing. A RELATIVE value is resolved against this MCP process's working directory, which is the client's choice and may not be writable. On Windows a drive-less path like \\out is resolved against this process's CURRENT DRIVE, not a drive you chose. Prefer a fully-qualified path (C:\\... or \\\\server\\share); the returned 'Saved to:' line always names the resolved absolute path.",
      "type": "string"
    },
    "max_preview_bytes": {
      "description": "action:\"get\" — ceiling on the base64 payload returned INLINE (default ~16MB). The file saved to disk is never affected. Lower it when your client rejects or truncates large tool results; the reply says when it downscaled and by how much.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "max_preview_dimension": {
      "description": "action:\"get\" — ceiling on the inline preview's longest side in pixels (default 4096). Applies even when the byte budget is satisfied, since some consumers reject by dimension — but only for an image this server can decode; an undecodable one under the byte budget is passed through as-is. Does not affect the saved file.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "path": {
      "description": "A source image path. action:\"convert\" — a path under COMFYUI_PATH/output (provide exactly one of asset_id or path). action:\"analyze_color\" — an absolute image path, or a path under the ComfyUI output dir (videos: extract a frame to PNG first).",
      "type": "string"
    },
    "limit": {
      "description": "action:\"list_outputs\" — max media files to return, 1..100 (default 20). action:\"list_assets\" — max records to return (default: all, no upper bound).",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "pattern": {
      "description": "action:\"list_outputs\" — filter by filename pattern (case-insensitive substring match).",
      "type": "string"
    },
    "format": {
      "description": "Two unrelated meanings, one per action — the enum is the union of both and each action accepts only its own half. action:\"list_outputs\" — RESPONSE SHAPE: \"markdown\" (default, human/agent-readable) or \"json\" ({images:[{filename,subfolder,kind,size,modified,type?}]} — type is \"temp\" for VHS videos written with save_output unchecked, omitted for output/). action:\"convert\" — REQUIRED target encoded image format: \"png\", \"jpeg\" or \"webp\".",
      "type": "string",
      "enum": [
        "markdown",
        "json",
        "png",
        "jpeg",
        "webp"
      ]
    },
    "quality": {
      "description": "action:\"convert\" — encoder quality, 1-100. Applies where supported by the selected format.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "progressive": {
      "description": "action:\"convert\" — JPEG only: write a progressive JPEG.",
      "type": "boolean"
    },
    "lossless": {
      "description": "action:\"convert\" — WebP only: write lossless WebP.",
      "type": "boolean"
    },
    "effort": {
      "description": "action:\"convert\" — WebP only: encoder effort, 0-6.",
      "type": "integer",
      "minimum": 0,
      "maximum": 6
    },
    "out_path": {
      "description": "action:\"convert\" — optional output path under COMFYUI_PATH/output where the converted image should be written.",
      "type": "string"
    },
    "reference_path": {
      "description": "action:\"analyze_color\" — optional reference image to shot-match against; returns target−reference deltas for contrast, black/white points, saturation, and per-channel means.",
      "type": "string"
    },
    "histogram": {
      "description": "action:\"analyze_color\" — also return an overlaid R/G/B/luma histogram PNG for visual confirmation (default false).",
      "type": "boolean"
    },
    "since": {
      "description": "action:\"list_assets\" — ISO timestamp; only return assets created at or after this time.",
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
    }
  },
  "required": [
    "action"
  ]
}
get_system_statsInspect the connected ComfyUI server: what it is running on, what it has logged, and whether it is healthy enough to dispatch work to. All three actions are READ-ONLY — nothing here mutates anything. Driven by the `action` parameter: - action:"stats" — Get system information from the connected ComfyUI server: GPU device(s), total/free VRAM, ComfyUI/Python/PyTorch versions, and OS details. Requires a running ComfyUI server (works against local or remote targets); read-only, takes no parameters. Returns the raw /system_stats JSON. Use to confirm connectivity and check available VRAM before enqueuing large workflows. Errors if the server is unreachable. - action:"logs" — Get ComfyUI server runtime logs. Useful for debugging execution errors, model loading issues, missing nodes, and Python tracebacks. `max_lines` tails the end (default 100), `keyword` filters case-insensitively. - action:"health" — Pre-flight diagnostic for the connected ComfyUI: one call that aggregates the signals an agent should check before dispatching a batch. Reports ComfyUI version/Python/PyTorch, GPU name + VRAM free/total, system RAM free, queue depth (running + pending), per-category /models populations (catches empty dropdowns from a misconfigured extra_model_paths.yaml), and recent errors from /internal/logs. Read-only — no mutation. Use this when a job fails for an unexpected reason, before a long batch run, or to confirm a remote ComfyUI is healthy. Originally contributed by github.com/joaolvivas.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "stats",
        "logs",
        "health"
      ],
      "description": "Which read to perform. \"stats\" takes no other parameters; \"logs\" takes `max_lines`/`keyword`; \"health\" takes `model_categories`/`recent_errors`. None of them is required."
    },
    "max_lines": {
      "description": "action:\"logs\" — maximum number of log lines to return from the end (default: 100).",
      "type": "integer",
      "minimum": 1,
      "maximum": 2000
    },
    "keyword": {
      "description": "action:\"logs\" — filter log lines containing this keyword (case-insensitive). Examples: 'error', 'warning', 'VRAM', a node name.",
      "type": "string"
    },
    "model_categories": {
      "description": "action:\"health\" — override the model categories to poll (defaults to checkpoints, diffusion_models, loras, vae, text_encoders, controlnet).",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "recent_errors": {
      "description": "action:\"health\" — how many recent error/traceback lines to include from /internal/logs (default 20, max 200).",
      "type": "integer",
      "minimum": 0,
      "maximum": 200
    }
  },
  "required": [
    "action"
  ]
}
get_workflowReturn, list, summarize or query a SAVED workflow FILE — files on disk, named from the library or given as a path/JSON — NOT the graph open on the user's canvas (that is panel_graph_outline). Every action here is READ-ONLY; saving and locking are save_workflow. Driven by the `action` parameter: - action:"get" — the full JSON of one saved workflow FILE named from the library. Defaults to converted API format; pass format:'ui' for the raw on-disk UI JSON. Use action:"analyze" instead if you just need to UNDERSTAND the workflow — it returns a structured summary without flooding context with JSON. Use action:"get" only when you need the actual JSON for enqueue_workflow, create_workflow (action:"modify"), or save_workflow. - action:"list" — the workflows saved in the connected ComfyUI server's user library (the same ones visible in the ComfyUI web UI), INCLUDING the ones filed in subfolders. Requires a running ComfyUI server. Takes no other parameters. Returns a numbered list of library names, each relative to the library root — a workflow in a folder appears as 'VIDEO/MiniMaxH3/clip.json', and that whole string is what `filename` takes. It never reports an absence it did not establish: a listing it could not read says so, and an EMPTY listing says the library could not be CONFIRMED empty (an answer with no names in it cannot show whether it covered subfolders) and tells you to check the ComfyUI sidebar rather than recreate anything. - action:"strip" — strip a workflow to a clean, flat API graph, resolving Get/Set buses, Reroutes, subgraph definitions, and bypassed/muted nodes into real connections (the 'de-getter-setter' pass). Unlike action:"get" this reads from ANY file path on disk when the connected ComfyUI is THIS machine (not just the workflow library); against a REMOTE or Cloud ComfyUI this process is not on that filesystem, so an absolute path is proxied only when it sits under user/default/workflows, user/workflows or models/workflows, and any other remote absolute path is refused rather than resolved against the local disk of this MCP process, so it loads ad-hoc / expert workflow files that action:"list" and panel_open_workflow can't resolve. Provide exactly one of: path, filename, or graph. Returns conversion warnings, a node-type summary, and the stripped graph (much smaller than the raw UI JSON). - action:"slice" — slice ONE pipeline out of a toggle-template workflow, the kind built with rgthree 'Fast Groups Bypasser/Muter' where one graph holds many pipelines and only one is active at a time. Seeds from the output/SaveImage nodes in the named `groups`, takes their backward dependency closure (through real links AND virtual Set/Get buses), un-bypasses the kept nodes (and the internals of any subgraph defs they use), and returns a STANDALONE, activated UI graph carrying only the subgraph defs it uses. Pair with action:"strip" afterward to flatten the Set/Get buses into real connections. - action:"from_image" — extract embedded ComfyUI workflow metadata from a PNG file. ComfyUI stores the full workflow (API format) and prompt data in PNG tEXt chunks. Use this to reverse-engineer how any ComfyUI image was generated. - action:"analyze" — SUMMARIZE a saved workflow file named from the library: sections, node settings, connections, and data flow. Returns a concise text summary (not raw JSON) optimized for AI reasoning. Prefer this over action:"get" unless you need the raw JSON for enqueue_workflow or create_workflow (action:"modify"). - action:"query" — filter, traverse, project, and aggregate over a saved workflow's nodes WITHOUT dumping the whole JSON (the missing middle between action:"analyze"'s fixed summary and action:"get"'s full dump; on 100+-node graphs this is the ONLY context-safe way to answer questions like 'which KSamplers run cfg>7', 'what feeds node 42', 'count nodes by type'). Provide exactly one of path/filename/graph, then combine: `types`, `title`, `where` widget predicates ANDed ('cfg>7', 'steps<=20', 'sampler_name=euler', 'text~sunset' — ops = != >= <= > < ~contains), `ids`, `upstream_of`/`downstream_of` + `depth`, `fields`, `group_by`, `limit`, `max_chars`. Output is TOKEN-BOUNDED and, when it truncates, the tail names WHICH of the two caps fired and the exact parameter to raise — read it and retry rather than concluding the graph can't be read. For the LIVE canvas this is panel_query_graph instead. - action:"prompt_director" — read Prompt Director's latest sanitized RUNTIME state after its nodes execute: each node id, node kind, resolved Model Explorer model/LoRA context, structured edit plan, source analysis, exact final prompt, warnings, or Result Critic verdict. Secrets and image tensors are redacted. Pair it with a live panel graph audit: graph inspection explains wiring and widget state, while this explains what the nodes actually resolved and compiled. Pass `node_id` to inspect one executed Prompt Director node.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "get",
        "list",
        "strip",
        "slice",
        "from_image",
        "analyze",
        "query",
        "prompt_director"
      ],
      "description": "Which read to perform. \"list\" and \"prompt_director\" take no required parameters; \"get\" and \"analyze\" require `filename`; \"strip\", \"slice\" and \"query\" require exactly one of `path`/`filename`/`graph` (and \"slice\" also requires `groups`); \"from_image\" requires `image_path`."
    },
    "filename": {
      "description": "Workflow library name, exactly as action:\"list\" reports it. A workflow filed in a folder keeps its folder in the name ('VIDEO/MiniMaxH3/clip.json') and that whole string goes here. An absolute path under the live ComfyUI workspace (e.g. a JSON in data/_downloads) is read from disk, not the user library. REQUIRED for action:\"get\" and action:\"analyze\"; one of the three sources for \"strip\", \"slice\" and \"query\".",
      "type": "string"
    },
    "format": {
      "default": "api",
      "description": "action:\"get\" — 'api' (default, recommended) converts to compact API format with named inputs, connection references, and _meta.mode flags for muted/bypassed nodes; 'ui' returns the raw UI format with layout positions and links arrays. action:\"strip\" — 'api' (default) strips to the flat resolved graph; 'raw' returns the file/graph unchanged. Each action accepts only its own two values (this field is the union of what the two tools it replaces accepted) and refuses the third rather than guessing at an alias.",
      "type": "string",
      "enum": [
        "ui",
        "api",
        "raw"
      ]
    },
    "path": {
      "description": "action:\"strip\" / \"slice\" / \"query\" — Absolute path to a workflow .json on the connected ComfyUI host (e.g. C:\\\\Users\\\\you\\\\ComfyUI\\\\user\\\\default\\\\workflows\\\\pusa_extend.json). LOCAL ComfyUI: read from this host's disk — no library lookup. If a userdata path is missing the `workflows` segment after `user/default`, it is retried with that segment restored, preserving the filename exactly. REMOTE ComfyUI: not opened on this MCP host (a POSIX path is not Win32-resolved). A path under user/default/workflows, user/workflows, or models/workflows is fetched from that server's userdata library; any other remote absolute path is refused.",
      "type": "string"
    },
    "graph": {
      "description": "action:\"strip\" / \"slice\" / \"query\" — Inline workflow JSON (UI format for \"strip\"/\"slice\"; UI or API for \"query\"), as an alternative to path/filename.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "groups": {
      "description": "action:\"slice\" (REQUIRED) — Group-title substrings (case-insensitive) whose output nodes seed the slice — CSV string or array, e.g. 'TEXT TO IMAGE,TXT' or ['extend','sampler']. Shared post-proc is pulled in via the closure.",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      ]
    },
    "image_path": {
      "description": "action:\"from_image\" (REQUIRED) — Absolute path to a ComfyUI-generated PNG file",
      "type": "string"
    },
    "view": {
      "default": "summary",
      "description": "action:\"analyze\" — summary (default): structured text with sections, node IDs, key settings, virtual wires, and full connection graph — best for AI understanding. overview: mermaid diagram showing sections as summary nodes with cross-section data flow. detail: mermaid diagram for one section (requires section parameter). list: text listing of all sections with data flow summary. flat: single mermaid flowchart of the entire workflow (best for small workflows). health: graph-health heuristics (disconnected nodes, duplicate model loads, orphaned branches, muted/bypassed, a sampler running denoise below 1.0 on an empty latent, an image-edit graph whose sampled canvas is not derived from the reference).",
      "type": "string",
      "enum": [
        "summary",
        "overview",
        "detail",
        "list",
        "flat",
        "health"
      ]
    },
    "section": {
      "description": "action:\"analyze\" — Section name for detail view. Use view='list' first to see available section names.",
      "type": "string"
    },
    "node_id": {
      "description": "action:\"prompt_director\" — Optional ComfyUI node id; omit to list all recent Prompt Director runtime states.",
      "type": "string"
    },
    "types": {
      "description": "action:\"query\" — Keep nodes whose class_type contains ANY of these (case-insensitive).",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "title": {
      "description": "action:\"query\" — Keep nodes whose title contains this.",
      "type": "string"
    },
    "where": {
      "description": "action:\"query\" — Widget predicates, ANDed: 'cfg>7', 'sampler_name=euler', 'text~sunset'.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "ids": {
      "description": "action:\"query\" — Keep exactly these node ids.",
      "type": "array",
      "items": {
        "type": [
          "string",
          "number"
        ]
      }
    },
    "upstream_of": {
      "description": "action:\"query\" — Scope to the dependency closure FEEDING this node id.",
      "type": [
        "string",
        "number"
      ]
    },
    "downstream_of": {
      "description": "action:\"query\" — Scope to the nodes CONSUMING this node id's outputs.",
      "type": [
        "string",
        "number"
      ]
    },
    "depth": {
      "description": "action:\"query\" — Max hops from the traversal seed (seed=0). Absent = full closure.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "fields": {
      "description": "action:\"query\" — Projection: compact one-liners (default), bare ids, or detail JSON rows.",
      "type": "string",
      "enum": [
        "ids",
        "compact",
        "detail"
      ]
    },
    "group_by": {
      "description": "action:\"query\" — Aggregate: counts per class_type instead of listing.",
      "type": "string",
      "enum": [
        "type"
      ]
    },
    "limit": {
      "description": "action:\"query\" — Max nodes listed (default 40, max 200).",
      "type": "integer",
      "minimum": 1,
      "maximum": 200
    },
    "max_chars": {
      "description": "action:\"query\" — Output character bound (default 12000, max 60000). Raise this — not `limit` — when the truncation tail says the char budget cut the result.",
      "type": "integer",
      "minimum": 500,
      "maximum": 60000
    }
  },
  "required": [
    "action"
  ]
}
install_comfyuiInstall, update and configure the local ComfyUI installation, its sidebar panel, and this MCP server itself. Driven by the `action` parameter: - action:"install" — Install ComfyUI locally: git-clone it into `target_path`, create a dedicated workspace virtualenv (<target>/.venv), and install Python requirements INTO that venv (never the Python running this MCP server) via pip or uv. ComfyUI-Manager is installed from manager_requirements.txt when present, else git-cloned as a fallback. Mirrors `comfy-cli install`. LOCAL, subprocess-only and independent of any remote --comfyui-url target; the target dir must be empty or non-existent (an existing install is never overwritten). Runs SYNCHRONOUSLY and can take several minutes (large git clone + full torch/dependency install); the call blocks until done. On success returns a JSON report { installed, targetPath, venvPath, comfyuiUrl, managerInstalled, managerVia, version, pythonInstaller, steps[] }. Does NOT start ComfyUI. `target_path` is REQUIRED. - action:"update" — Update the ComfyUI CORE install: runs `git pull` in the connected local server's observed checkout (falling back to COMFYUI_CODE_PATH, then COMFYUI_PATH) and reinstalls its Python requirements (auto-detecting uv vs pip). Returns a clear error when targeting a remote instance via --comfyui-url. The requirements install targets the running server's own interpreter (recorded when this server launched ComfyUI, or an explicit COMFYUI_PYTHON); when that interpreter cannot be verified the update refuses rather than install into a guessed environment — start ComfyUI or connect first. Does NOT touch custom nodes. - action:"update_all" — Update ALL installed CUSTOM NODES via the ComfyUI-Manager HTTP API. Mirrors `comfy-cli update all`. This does NOT update ComfyUI core — use action:"update" for that. Works against the connected instance (local or remote); updates run asynchronously and a ComfyUI restart may be required afterward. REFUSED while the comfyui-mcp sidebar panel is version-pinned, because 'all' would move the pinned panel too and ComfyUI-Manager cannot update everything-except-one-pack — clear the pin with action:"panel" + panel_action:"unpin", or update the other packs individually by id. - action:"panel" — Install, update, reinstall, sync, pin, unpin, unlock, or report status of the ComfyUI sidebar panel ('comfyui-agent-panel' on the Comfy Registry; repo comfyui-mcp-panel) in the LOCAL ComfyUI's custom_nodes, selected by `panel_action` (default "status"). Uses the same ComfyUI-Manager path as install_custom_node and always targets the 'nightly' (git-HEAD) channel. Local-only (no-op/refuses in remote/cloud mode) and NEVER modifies a dev install (a symlinked panel dir). After install/update/reinstall/sync, ComfyUI must be RESTARTED to load the new/updated node — this tool does not auto-restart. The panel is also auto-installed-if-missing when the MCP server loads. A version PIN (panel_action:"pin") holds the panel where it is: while a pin is set, install/update/reinstall/sync and the auto-install all refuse, and 'sync' only warns that a newer panel exists. Panel operations are serialized across orchestrator processes by a lock file. A lock whose recorded owner process is gone is reclaimed automatically; a living owner's lock is never stolen. panel_action:"unlock" RE-CHECKS and reports rather than forcing: it applies that same proof, so it clears the lock only when the recorded owner can be shown dead and REFUSES when ownership cannot be proven (an unreadable record, or a pid indistinguishable from a reused one) — in that case the recovery is manual, after confirming no orchestrator still runs. This is the SIDEBAR PANEL only; it never touches ComfyUI core or this npm package. - action:"self_update" — Check or apply a self-update of the comfyui-mcp NPM PACKAGE (this MCP server), selected by `self_update_action` (default "status"). The server also auto-checks on start (opt out with COMFYUI_MCP_AUTOUPDATE=0). Detects the install mode: a dev install (npm link / source checkout) is NEVER updated; global/local installs are updated via npm; npx fetches latest on next run. The running process cannot hot-swap its own code — after an update you must RECONNECT (/mcp) or restart the orchestrator to load the new version. This tool does not auto-restart. On Windows the running orchestrator holds its own sharp DLL locked, so an in-place npm replace fails (EBUSY); the update is then handed to a deferred helper that finishes it once the orchestrator has fully stopped, and the new version loads at the next start. A failed update reports npm's own error output. This updates comfyui-mcp ITSELF — not ComfyUI (action:"update"), not the sidebar panel (action:"panel"), and not custom nodes (install_comfyui (action:"update_all")). - action:"environment" — Report ComfyUI environment info (mirrors `comfy-cli env`): the running instance details from /system_stats (OS, Python, ComfyUI version, GPU/VRAM — works for remote targets) plus local probes when a workspace path is available (Python version, git revision, ComfyUI-Manager version, and key pip packages like torch/CUDA). Split installs report `local.workspace_path` for data/base state and `local.code_path` for the serving checkout; git follows the code path and Manager follows the data/base root (`custom_nodes`). The local python probe targets the interpreter the RUNNING server uses (its venv / embedded / standalone python, resolved from the live server), never a bare `python` on PATH. Degrades gracefully and NEVER guesses: when the correct interpreter can't be confirmed, `local.python_probe_trusted` is false, `local.packages` is omitted, and `local.python_probe_reason` says why — an absent package list means UNDETERMINED, never 'not installed'. READ-ONLY. - action:"configure_manager" — Configure ComfyUI-Manager settings, mirroring `comfy-cli manager` subcommands; `manager_setting` picks which setting and `value` its new value. Most settings use the ComfyUI-Manager HTTP API (works against remote ComfyUI); set_network_mode and set_security_level have no HTTP setter and are written to Manager's config.ini (requires a known local ComfyUI path; restart ComfyUI to apply).
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "install",
        "update",
        "update_all",
        "panel",
        "self_update",
        "environment",
        "configure_manager"
      ],
      "description": "Which install/environment operation to perform. action:\"update\", action:\"update_all\" and action:\"environment\" take no other parameters; action:\"install\" requires `target_path`; action:\"panel\" takes `panel_action` (+ `version`/`reason` for a pin); action:\"self_update\" takes `self_update_action`; action:\"configure_manager\" requires `manager_setting` (+ `value`)."
    },
    "target_path": {
      "description": "action:\"install\" — REQUIRED absolute path to the workspace directory to install ComfyUI into. Must be empty or non-existent.",
      "type": "string",
      "minLength": 1
    },
    "skip_manager": {
      "description": "action:\"install\" — if true, do not clone/install ComfyUI-Manager. Default false (Manager is installed).",
      "type": "boolean"
    },
    "use_uv": {
      "description": "action:\"install\" — if true, prefer `uv pip install` over plain pip when uv is available on PATH. Falls back to pip if uv is missing. Default false.",
      "type": "boolean"
    },
    "version": {
      "description": "action:\"install\" — ComfyUI version to install (comfy-cli semantics): \"nightly\" (default-branch HEAD), \"latest\" (newest release tag), or a semantic version like \"0.3.40\" (checked out as tag v0.3.40). Raw git refs/branches are rejected. Omit to track the default branch HEAD. ALSO used by action:\"panel\" + panel_action:\"pin\", where it is the PANEL version to hold at, e.g. '0.11.20' (take it from the installedVersion that panel_action:\"status\" reports).",
      "type": "string"
    },
    "panel_action": {
      "default": "status",
      "description": "action:\"panel\" — which sidebar-panel operation to run. status: report installed/version/dev-symlink/pin plus a sync assessment (never errors). sync: bring the panel up to what this orchestrator needs — no-ops when already current, WARNS ONLY when pinned, and reports the version re-read from disk afterwards. install: add the panel (nightly). update: pull the latest nightly. Works on either install shape — a git checkout is fast-forwarded, and a Comfy Registry ZIP install (which has no .git) is replaced with a verified fresh clone, keeping the previous copy outside custom_nodes. Success is always re-read from disk. reinstall: uninstall + reinstall (nightly). pin: hold the panel at a version (requires `version`). unpin: clear the pin so a sync can proceed. unlock: recover from a crashed/killed orchestrator's leftover panel operation lock — reclaims it ONLY when it is provably abandoned (older than the stale threshold AND its recorded owner process is dead), and refuses with the observed state otherwise. install/update/reinstall/sync refuse on a dev symlink or an active pin, and require a local workspace (COMFYUI_PATH or the saved default workspace).",
      "type": "string",
      "enum": [
        "status",
        "install",
        "update",
        "reinstall",
        "sync",
        "pin",
        "unpin",
        "unlock"
      ]
    },
    "reason": {
      "description": "action:\"panel\" + panel_action:\"pin\" only: why the user is pinning (stored with the pin).",
      "type": "string"
    },
    "self_update_action": {
      "default": "status",
      "description": "action:\"self_update\" — status: report install mode + current vs latest version + dev-link note (never errors). update: update to the latest published version (refuses on a dev link; no-op when already up to date or for npx).",
      "type": "string",
      "enum": [
        "status",
        "update"
      ]
    },
    "manager_setting": {
      "description": "action:\"configure_manager\" — REQUIRED. Which ComfyUI-Manager setting to change. HTTP API: set_preview_method, set_db_mode, set_component_policy, set_update_policy, set_channel, reset_queue. config.ini fallback: set_network_mode, set_security_level.",
      "type": "string",
      "enum": [
        "set_preview_method",
        "set_db_mode",
        "set_component_policy",
        "set_update_policy",
        "set_channel",
        "reset_queue",
        "set_network_mode",
        "set_security_level"
      ]
    },
    "value": {
      "description": "action:\"configure_manager\" — value for the chosen `manager_setting` (omit only for reset_queue). Allowed values per setting — set_preview_method: auto | latent2rgb | taesd | none; set_db_mode: local | cache | remote; set_component_policy: workflow | higher | mine; set_update_policy: stable-comfyui | nightly-comfyui; set_channel: a channel name (e.g. default); set_network_mode: public | private | offline; set_security_level: strong | normal | normal- | weak. HTTP-API settings take effect live; the config.ini ones (set_network_mode, set_security_level) apply only after a ComfyUI restart.",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
Annotations
{
  "destructiveHint": true,
  "openWorldHint": true,
  "idempotentHint": false
}
DestructiveNon-idempotentOpen world
install_custom_nodeInstall, repair, enable/disable and remove ComfyUI custom node packs on this ComfyUI. To FIND a pack in the public registry first, use search_custom_nodes. Driven by the `action` parameter: - action:"install" — Install a pack by registry id, git URL, or name. Local installs prefer official comfy-cli when available; remote or CLI-unavailable installs use the ComfyUI-Manager HTTP API. A ComfyUI restart may be required. Targeting the comfyui-mcp sidebar panel pack ('comfyui-agent-panel' / 'comfyui-mcp-panel') is routed through the verified install_comfyui(action:'panel') path (the version is re-read from disk afterwards) and is REFUSED while the panel is version-pinned. - action:"update" — Update an installed pack, or pass id:'all' to update every installed pack. Local operations prefer official comfy-cli; remote operations use Manager HTTP. Targeting the sidebar panel pack is routed through the verified install_comfyui(action:'panel') path. While the panel is version-pinned, BOTH a direct panel target and 'all' are REFUSED — 'all' would move the pinned panel too; clear the pin with install_comfyui(action:'panel', panel_action:'unpin') or update other packs individually. - action:"reinstall" — Reinstall a pack. Local operations prefer official comfy-cli; remote operations use Manager HTTP. A ComfyUI restart may be required. A panel target is routed through the verified install_comfyui(action:'panel') path and is REFUSED while the panel is version-pinned. - action:"fix" — Repair a pack's install and Python dependencies, or pass id:'all' to repair every pack. Local operations prefer official comfy-cli; remote single-pack repairs use Manager HTTP. REFUSES the sidebar panel pack — 'fix' has no verified on-disk check, so use install_comfyui(action:'panel') for the panel — and refuses 'all' while the panel is version-pinned. - action:"uninstall" — Uninstall a pack (removes it). IRREVERSIBLE through this tool — for a cleanup audit prefer action:"disable", which is reversible. The pack must be one ComfyUI-Manager tracks: an id that resolves nowhere is REFUSED before anything is queued (a drained queue would otherwise read exactly like a success), and a pack that is on disk but unmanaged is named so you can remove its directory yourself. After the queue drains the installed-pack list is re-read and the pack must be GONE before anything claims 'uninstalled'. A ComfyUI restart is required to unload it fully. REFUSES the sidebar panel pack. - action:"disable" — Disable an installed pack WITHOUT removing it — the reversible first step of a cleanup (re-enable with action:"enable"; action:"uninstall" removes a pack outright). Uses the ComfyUI-Manager HTTP API (works against remote instances) or official comfy-cli locally, and re-reads the installed-pack list afterwards so a Manager no-op is reported as NOT disabled rather than as success. A ComfyUI restart is required for the change to take effect. REFUSES the sidebar panel pack. - action:"enable" — Re-enable a pack previously disabled with action:"disable". Same Manager/comfy-cli mechanics and the same post-op re-read, so a Manager no-op is reported as NOT enabled rather than as success. A ComfyUI restart is required for the change to take effect. REFUSES the sidebar panel pack. - action:"list" — List installed packs with their version and enabled/disabled state. Uses the ComfyUI-Manager HTTP API (works against remote instances); useCmCli:true uses cm-cli when its installed version is supported, otherwise falls back to Manager HTTP, while the cm-cli path returns names only. Read-only. - action:"sync_deps" — Reconcile the Python dependencies of ALL installed packs through official `comfy node restore-dependencies`. Requires a local ComfyUI install and comfy-cli; takes no other parameters.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "install",
        "update",
        "reinstall",
        "fix",
        "uninstall",
        "enable",
        "disable",
        "list",
        "sync_deps"
      ],
      "description": "Which custom-node operation to perform. \"install\", \"update\", \"reinstall\", \"fix\", \"uninstall\", \"enable\" and \"disable\" require `id`; \"list\" and \"sync_deps\" take no required parameters."
    },
    "id": {
      "description": "The pack to act on. REQUIRED for actions \"install\", \"update\", \"reinstall\", \"fix\", \"uninstall\", \"enable\" and \"disable\". For \"install\" this is a registry id, git URL, or node-pack name (find one with search_custom_nodes); for \"update\"/\"fix\" it may also be 'all' (every installed pack); for \"update\"/\"reinstall\"/\"fix\"/\"uninstall\"/\"enable\"/\"disable\" it is a registry id / module name of an INSTALLED pack.",
      "type": "string"
    },
    "source": {
      "description": "action:\"install\" — how to interpret `id` (default 'auto', which detects git URLs vs registry ids).",
      "type": "string",
      "enum": [
        "registry",
        "git",
        "auto"
      ]
    },
    "version": {
      "description": "Version to install. action:\"install\" — e.g. 'latest', 'nightly', or a semver; for git installs this is treated as a git ref unless `ref` is also provided, and registry installs default to 'latest'. action:\"reinstall\" — version to reinstall (default 'latest').",
      "type": "string"
    },
    "ref": {
      "description": "action:\"install\" — git ref (commit SHA, branch, or tag) to pin when installing a git URL. Overrides any ref parsed from the URL and any `version` value. Ignored for registry-id installs.",
      "type": "string"
    },
    "mode": {
      "description": "Two distinct meanings, one per action group. For actions \"install\"/\"update\"/\"reinstall\"/\"fix\": the ComfyUI-Manager data source (default 'remote'); 'remote' fetches the live node list, 'local'/'cache' use bundled/cached data. For action:\"list\": 'default' lists the installed packs as they are on disk NOW; 'imported' returns that SAME custom_nodes/ scan frozen at ComfyUI startup, so the only difference is packs installed or removed since the server booted. 'imported' is NOT an import-success filter: it includes DISABLED packs and cannot tell you whether the Python of a pack actually loaded — for that use node_pack action:\"verify\", which checks the node class_types of the pack against /object_info on the running server. Passing a value from the wrong group is refused, naming the ones the action accepts.",
      "type": "string",
      "enum": [
        "remote",
        "local",
        "cache",
        "default",
        "imported"
      ]
    },
    "channel": {
      "description": "ComfyUI-Manager channel name (default 'default').",
      "type": "string"
    },
    "useCmCli": {
      "description": "Prefer the official comfy-cli subprocess instead of the ComfyUI-Manager HTTP API. Local operations use comfy-cli by default; set false to force Manager HTTP. Requires a local ComfyUI install — for actions \"install\"/\"disable\"/\"enable\"/\"uninstall\"/\"list\", an unavailable or unsupported CLI falls back to Manager HTTP automatically (disclosed in the result); \"update\"/\"reinstall\"/\"fix\" do not fall back.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
kitchenSee what comfy-kitchen can do on this GPU, find where a graph is leaving it on the table, and apply the faster path. Driven by `action`: - action:"status" — kitchen version, backends (hip/cuda/triton/eager), INT8 attention, GPU fp8/NVFP4/MXFP8, launch flags (--use-ck-attention, --enable-triton-backend, --fast fp8_matrix_mult). Local gets log + /system_stats + an import probe when COMFYUI_PATH is set; remote gets log + /system_stats only and reports model.quant / the probe as unknown. A failed probe is unknown, never a no. - action:"assess" — walk the workflow JSON's UNETLoaders and emit a recommendation only when every fact it needs is known: (1) weight_dtype default on a bf16 UNETLoader + GPU fp8 + kitchen present → fp8_e4m3fn_fast (widget, no restart); (2) no --use-sage-attention, sageattention not installed, kitchen INT8 available → --use-ck-attention (restart, confirm); (3) Blackwell + local NVFP4 sibling → model swap; (4) ROCm + triton ≥ 3.7 + kitchen, triton backend off → --enable-triton-backend. Pass `workflow` (API or UI JSON). For the open canvas use panel_kitchen. - action:"apply" — apply one recommendation_id from assess. Widget edits are reversible and do not need confirm. Flags and downloads need `confirm: true`. Flag apply names the launch flag; restart_comfyui replays the previous argv and does not inject a new one. Proof (before/after s/it, peak VRAM, output not black) is the panel_kitchen apply path.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "status",
        "assess",
        "apply"
      ],
      "description": "Which kitchen operation. \"status\" takes no other parameters; \"assess\" takes `workflow`; \"apply\" takes `recommendation_id` and `confirm` for restarts/downloads."
    },
    "workflow": {
      "description": "action:\"assess\" / \"apply\" — workflow JSON (API-format {id:{class_type,inputs}} or UI-format {nodes,links}), as a string or object.",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        }
      ]
    },
    "recommendation_id": {
      "description": "action:\"apply\" — id from assess (e.g. \"fp8_unet_fast:12\" or \"ck_attention\").",
      "type": "string"
    },
    "confirm": {
      "description": "action:\"apply\" — required true for anything that restarts or downloads. Widget edits do not need it.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
list_api_nodesDiscover and run hosted partner/API nodes on the connected ComfyUI (e.g. Flux/BFL, Ideogram, Kling, Stability). These call external image/video providers and run server-side, requiring a Comfy account/API key configured on the ComfyUI server — they spend PAID api credits, unlike a local-GPU render. Driven by the `action` parameter: - action:"list" — List the API/partner nodes available on the connected ComfyUI, optionally narrowed by `filter`. Returns an empty list if the server has no API nodes (or they are disabled). Start here to find a class_type. - action:"schema" — Return the input schema for one API/partner node (`class_type`) from the connected ComfyUI's /object_info. Lists visible inputs (with types/defaults/options), hidden inputs (server-filled auth), and outputs. Use action:"list" first to find a class_type. - action:"generate" — Build a minimal single-node workflow that runs a chosen API/partner node (`class_type`) with the provided `inputs` and enqueue it. Returns immediately with the prompt_id (use queue (action:"status") / get_history for results). Do NOT pass auth credentials in inputs — the ComfyUI server injects those from its logged-in session. Use action:"schema" to discover valid inputs.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "list",
        "schema",
        "generate"
      ],
      "description": "Which API-node operation to perform. \"list\" takes an optional `filter`; \"schema\" requires `class_type`; \"generate\" requires `class_type` + `inputs` (optional `disable_random_seed`)."
    },
    "filter": {
      "description": "action:\"list\" — case-insensitive substring to narrow results, matched against class_type, display name, or category (e.g. \"image\", \"video\", \"kling\").",
      "type": "string"
    },
    "class_type": {
      "description": "The node class_type, e.g. \"FluxProImageNode\". REQUIRED for action:\"schema\" and action:\"generate\"; find one with action:\"list\".",
      "type": "string"
    },
    "inputs": {
      "description": "action:\"generate\" — REQUIRED. Input values keyed by input name, per the node's schema (action:\"schema\").",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "disable_random_seed": {
      "description": "action:\"generate\" — if true, do not randomize seed/noise_seed inputs.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
list_local_modelsInspect what models this ComfyUI has installed, and where it looks for them. Driven by the `action` parameter: - action:"list" — List model files available to the connected ComfyUI, grouped by type. Read-only. Queries ComfyUI's /models REST endpoint first (works with remote ComfyUI and respects extra_model_paths.yaml — symlinked / mounted dirs the install-path filesystem scan would miss). LOCAL ComfyUI then falls back to a filesystem scan of COMFYUI_PATH/models/ when the REST endpoint is unavailable; REMOTE ComfyUI never scans this MCP host's local path, so an unavailable remote listing is returned as undetermined rather than another machine's inventory. Size and modified time are only available on the local filesystem fallback path. Use to see which models are already available before generating or downloading; use download_model action:"search" to discover new models on HuggingFace, then action:"download" to fetch them. For models fetched via download_model action:"download_civitai", any CivitAI trigger/activation words and base model are shown inline (read from the `<file>.civitai.json` sidecar) — apply those trigger words in your prompt when generating with that model. A `civitai:` line under an entry is that model's CivitAI page URL (modelId + INSTALLED modelVersionId, from the same sidecar) — use it to reference the source or check for newer versions. - action:"remove" — DELETES a model FILE from the local ComfyUI models directories. `path` is REQUIRED and is a file path relative to models/. THIS IS DESTRUCTIVE AND HAS NO UNDO: the file is unlinked, not moved to a recycle bin, and a large checkpoint can take hours to re-download — confirm the exact path with the user (action:"list" shows it) before calling. Removal searches only roots the connected local server itself names or whose extra-path config is proven to have been loaded unchanged since launch (server-named/launch-state-proven roots). A root shown by action:"list_paths" may be visible but unproven, so removal can refuse it; read-only list/list_paths lookups can show configured roots. The path must stay within a known root (path traversal and absolute escapes are rejected), and a directory is refused. LOCAL-ONLY: deletes from the local filesystem, so it is not supported against a remote ComfyUI (remove the file on the host). Do NOT confuse this with action:"remove_path", which edits a config file and deletes nothing. - action:"embeddings" — List textual-inversion embeddings installed on the connected ComfyUI server (read from its /api/embeddings endpoint, i.e. the models/embeddings folder). Requires a running, reachable ComfyUI (local or remote); takes no other parameters. Returns the embedding names; reference them in positive or negative prompts as embedding:name (e.g. embedding:easynegative). Read-only. - action:"list_paths" — View ComfyUI extra search-path config for standalone/manual installs and ComfyUI Desktop. Read-only. Resolves LIVE-FIRST: the file the running ComfyUI actually reads (its --extra-model-paths-config, else the extra_model_paths.yaml beside its main.py), falling back to the local heuristic only when no server is reachable — <ComfyUI root>/extra_model_paths.yaml (COMFYUI_PATH, else the saved default workspace from workspace action:"set_default") or the Desktop app-data extra_models_config.yaml. Reports generic categories, so model categories and custom_nodes entries are both visible when present. Because it is read-only it never refuses a reachable LOCAL server just because its argv does not prove which file it reads: it shows the server-named config when that file exists here, else the local auto-selected one, always labelled as unconfirmed rather than presented as the live server's. action:"add_path"/action:"remove_path" still refuse in that state — a write to an unproven file would be a silent no-op. - action:"add_path" — Add a directory to a ComfyUI extra search-path YAML config; `category` + `path` are REQUIRED. Use this for model categories such as checkpoints/loras/vae and, on ComfyUI builds that support it, custom_nodes. Writes the config file and returns the updated view; restart ComfyUI to apply. - action:"remove_path" — Remove a directory from a ComfyUI extra search-path YAML config; `category` + `path` are REQUIRED. Matches the stored path exactly. This edits the YAML only — it deletes NO model files and frees no disk space (that is action:"remove"). Restart ComfyUI after removing an active path.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "list",
        "remove",
        "embeddings",
        "list_paths",
        "add_path",
        "remove_path"
      ],
      "description": "Which inventory operation to perform. \"list\" (optional `model_type`), \"embeddings\" and \"list_paths\" are READ-ONLY. \"remove\" DELETES the model file named by `path` — required, and destructive. \"add_path\"/\"remove_path\" edit the extra-search-path YAML and require `category` + `path`; they never touch model files."
    },
    "model_type": {
      "description": "action:\"list\" — filter by model type (e.g. 'checkpoints', 'loras'). Lists all types if omitted.",
      "type": "string",
      "enum": [
        "checkpoints",
        "loras",
        "vae",
        "upscale_models",
        "controlnet",
        "embeddings",
        "clip",
        "diffusers",
        "diffusion_models",
        "gligen",
        "hypernetworks",
        "photomaker",
        "style_models",
        "text_encoders",
        "unet"
      ]
    },
    "path": {
      "description": "REQUIRED by three actions, and it means two DIFFERENT things — read this before calling. action:\"remove\": the MODEL FILE to DELETE, relative to the ComfyUI models/ directory (e.g. 'checkpoints/sd_xl_base_1.0.safetensors'); the leading segment is the category used to locate the file in extra roots too. action:\"add_path\" / action:\"remove_path\": a DIRECTORY to add to / remove from the extra-search-path YAML for `category` (absolute paths are safest; relative paths are resolved by ComfyUI) — no file is deleted.",
      "type": "string",
      "minLength": 1
    },
    "target": {
      "description": "action:\"list_paths\" / \"add_path\" / \"remove_path\" — config target. auto (default) is LIVE-FIRST: the running ComfyUI's own --extra-model-paths-config, else the extra_model_paths.yaml next to its main.py. When no server is reachable, auto shows the Desktop config if one exists, otherwise standalone. When a reachable LOCAL server does not expose main.py, listing degrades to the server-named config (if it exists here) or the local auto-selected one, explicitly marked as an unconfirmed display fallback; mutations refuse instead. standalone forces <ComfyUI root>/extra_model_paths.yaml, where the root is COMFYUI_PATH (or an auto-detected install) and falls back to the saved default workspace; desktop forces the OS app-data extra_models_config.yaml. Use standalone/desktop (or config_path) to deliberately target a file the running server does not read.",
      "type": "string",
      "enum": [
        "auto",
        "standalone",
        "desktop"
      ]
    },
    "config_path": {
      "description": "action:\"list_paths\" / \"add_path\" / \"remove_path\" — explicit YAML config path override, mainly for advanced/manual installs.",
      "type": "string"
    },
    "group": {
      "description": "action:\"add_path\" / \"remove_path\" — top-level YAML group to edit. Defaults to comfyui_mcp.",
      "type": "string"
    },
    "category": {
      "description": "REQUIRED for action:\"add_path\" / action:\"remove_path\" — the ComfyUI search-path category, e.g. checkpoints, loras, vae, diffusion_models, unet_gguf, or custom_nodes.",
      "type": "string",
      "minLength": 1
    },
    "is_default": {
      "description": "action:\"add_path\" — set is_default on a newly-created group. Existing groups are not overwritten.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
list_packsBundled ComfyUI knowledge — installer packs, model-family skills, workflow templates — plus the two workflow-readiness checks. Driven by the `action` parameter: - action:"list" — List the bundled installer packs under packs/: one-command setups for a model family (custom nodes + model weights via manifest.yaml) PLUS a ready workflow.json graph. Each entry reports its family/kind, its runtime (these packs are LOCAL-GPU / FREE — they run on the user's own GPU and never spend paid API credits), whether it has a ready workflow + manifest, and the manifest path for install_comfyui apply_manifest. When asked to "set up / build a <model-family> workflow", PREFER applying the matching pack and loading its ready workflow (panel_load_workflow pack:<name>) over building a generic graph from scratch. Read the ready graph with action:"read_workflow", and inspect its install manifest with action:"read_manifest". - action:"read_workflow" — Return a bundled pack's ready workflow.json graph by pack name (`name`; discover names + which packs have a workflow with action:"list"). This is the EXPERT graph for that model family — use it as the source of truth when setting up the family on the user's canvas: recreate it node-by-node with the panel_* tools (panel_add_node / panel_connect / panel_set_widget) so it lands on their live canvas, or enqueue it headlessly. Prefer this over inventing a graph from scratch. Names are validated (no path traversal) and must match an existing pack directory. - action:"read_manifest" — Return a bundled pack's install manifest (its manifest.yaml — the custom nodes + model weights apply_manifest would install) by pack name (`name`; discover names + which packs have a manifest with action:"list"). READ-ONLY — the way to INSPECT what a pack will install BEFORE calling the mutating apply_manifest. Names are validated (no path traversal) and must match an existing pack directory. - action:"list_templates" — List CUSTOM-NODE-contributed ComfyUI workflow templates on the connected ComfyUI, grouped by source (each pack's own example_workflows/*.json). Hits the live server's /api/workflow_templates index. SCOPE LIMIT: this endpoint does NOT include ComfyUI's own core bundled templates from the comfyui-workflow-templates package (e.g. "Flux.1 Inpaint") — those are served to the frontend as static assets via a separate code path this action cannot see, so a small/empty result here does NOT mean no official template exists, only that no custom-node pack contributed one. When asked to "set up / build a <model-family> workflow", check here for a custom-node-contributed starter AFTER checking the bundled skills + installer packs (action:"skill_list" / action:"list"), and also tell the user to check the ComfyUI frontend's own Templates browser directly for core templates, since this action cannot enumerate those. NOTE: this lists what's available; loading a template onto the canvas is done in the ComfyUI frontend's Templates browser (the panel agent cannot load a template graph headlessly yet) — surface the matching template name to the user. - action:"check_runtime" — Determine whether a workflow runs on the user's OWN GPU (LOCAL — free) or uses hosted API NODES (PAID api credits). Pass `pack` (a bundled pack name — always local/free) OR `graph` (a UI or API/prompt workflow JSON, as object or string). It scans the workflow's node class_types against the connected ComfyUI's API-node set (the same signal list_api_nodes uses) and returns { runtime: 'local'|'api'|'mixed'|'unknown', usesApiNodes, apiNodes[], externalApiNodes[], unknownNodes[] } — 'unknown' means some nodes couldn't be classified (could be paid), so treat it (and 'api'/'mixed') as POSSIBLY PAID; only 'local' is confirmed free. `externalApiNodes` is the THIRD-PARTY paid kind (a fal.ai-style pack, or any node taking a service credential): those are INSTALLED LOCALLY yet still cost money, billed by that provider on the user's own account with them rather than out of Comfy api credits — so when you ask the user, name the provider, not "Comfy credits" (`externalProviders` names it when recognised — e.g. ["fal.ai"]; it is absent when the node was flagged only by taking a service credential, which proves it authenticates somewhere but not to whom). ALWAYS call this before building OR loading a non-pack/ad-hoc workflow so you can ASK the user before spending paid API credits — never silently use API nodes. - action:"extract_deps" — Analyze a ComfyUI workflow (`workflow`, API JSON) and determine which custom node packs it requires. Maps each node class_type to its owning node pack using ComfyUI-Manager mappings and the server's installed node definitions, reporting which packs are installed vs missing. READ-ONLY — it installs nothing. Works remotely (HTTP only) — mirrors `comfy-cli node deps-in-workflow`. - action:"install_deps" — MUTATING: this is the ONE action on this tool that INSTALLS anything. Resolve and INSTALL the custom node packs a ComfyUI workflow (`workflow`) requires, via ComfyUI-Manager: it determines the missing packs, resets the Manager queue, QUEUES THE INSTALLS, starts the worker, and reports what was installed/already-present/unresolved. Installing a pack downloads and runs third-party code (and may pull large files) on the connected ComfyUI host — local OR remote --comfyui-url — and a ComfyUI restart is typically needed before new nodes load. Use action:"extract_deps" first if you only want to SEE what is missing. Mirrors `comfy-cli node install-deps`. - action:"skill_list" — List the bundled ComfyUI model-family + workflow skills shipped with comfyui-mcp (name + description for each). These encode per-family expertise (e.g. krea2-txt2img: native krea2 CLIPLoader, Qwen3-VL encoder, 8-step turbo settings) and the installer-packs system. Call this BEFORE hand-building a <model-family> workflow from scratch — if a matching skill exists, read its full guidance with action:"skill_read" and prefer a ready installer pack (action:"list") over a generic graph. Claude loads these natively; this action gives the SAME knowledge to any MCP client (e.g. the Codex backend). - action:"skill_read" — Return the full body of a bundled skill's SKILL.md by name (`name`; discover names with action:"skill_list"). Gives you the family's complete expertise on demand — model slots, node graph, recommended settings, and gotchas — so you can build the right workflow instead of guessing. Names are validated (no path traversal) and must match an existing skill directory. - action:"generate_skill" — MUTATING: it WRITES to the read-through skill cache on every cache miss, and when `install_in` is set it ALSO creates that directory and overwrites any SKILL.md in it. Generate a Claude skill (SKILL.md) documenting a ComfyUI custom node pack: its nodes, inputs/outputs, and example workflows. `source` accepts a ComfyUI Registry ID (resolved via api.comfy.org) or a GitHub repository URL. Uses a read-through cache under ~/.comfyui-mcp/skill-cache (override COMFYUI_SKILL_CACHE_DIR); set refresh:true to bypass it. On cache miss, fetches the repo README and scans its Python NODE_CLASS_MAPPINGS and example workflows over the network (uses GITHUB_TOKEN if set to avoid rate limits), so internet access is required. If a ComfyUI server is reachable it enriches node input/output types from /object_info, but the server is optional. Returns the SKILL.md markdown with structured cache metadata; if install_in is set, also creates that directory (recursively) and writes SKILL.md there, overwriting any existing file.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "list",
        "read_workflow",
        "read_manifest",
        "list_templates",
        "check_runtime",
        "extract_deps",
        "install_deps",
        "skill_list",
        "skill_read",
        "generate_skill"
      ],
      "description": "Which knowledge operation to perform. \"list\", \"list_templates\" and \"skill_list\" take no other parameters; \"read_workflow\", \"read_manifest\" and \"skill_read\" require `name`; \"check_runtime\" takes `pack` OR `graph`; \"extract_deps\" and \"install_deps\" require `workflow` (and \"install_deps\" INSTALLS custom nodes — the only action here that installs, though \"generate_skill\" also WRITES to disk: its skill cache on every miss, plus `install_in` when set); \"generate_skill\" requires `source` (optional `install_in`/`refresh`)."
    },
    "name": {
      "description": "REQUIRED for action:\"read_workflow\" and action:\"read_manifest\" — the pack name (a directory under packs/, e.g. 'krea2-txt2img-manual'), from action:\"list\". REQUIRED for action:\"skill_read\" — the skill name (a directory under plugin/skills/, e.g. 'krea2-txt2img'), from action:\"skill_list\".",
      "type": "string",
      "minLength": 1
    },
    "pack": {
      "description": "action:\"check_runtime\" — a bundled pack name (from action:\"list\"). Packs are local/free; this confirms it from the actual graph.",
      "type": "string"
    },
    "graph": {
      "description": "action:\"check_runtime\" — a workflow graph to classify (UI or API/prompt format), as an object or a JSON string. Use this for ad-hoc/generated workflows.",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        }
      ]
    },
    "workflow": {
      "description": "REQUIRED for action:\"extract_deps\" and action:\"install_deps\" — a ComfyUI workflow in API format (JSON string or object).",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        }
      ]
    },
    "source": {
      "description": "REQUIRED for action:\"generate_skill\" — a ComfyUI Registry node ID (e.g. 'comfyui-impact-pack') or a GitHub repository URL.",
      "type": "string"
    },
    "install_in": {
      "description": "action:\"generate_skill\" — optional directory to write the generated SKILL.md into. Created recursively if missing; an existing SKILL.md is overwritten. Omit to only return the markdown without touching disk.",
      "type": "string"
    },
    "refresh": {
      "description": "action:\"generate_skill\" — bypass the read-through cache and rebuild the SKILL.md, overwriting the cached entry.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
list_toolsList every comfyui-mcp capability as a token-light catalog: tool names with one-line summaries, grouped by category. Start here. Then use describe_tool to get a tool's parameters and call_tool to run it.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "category": {
      "description": "Only list this category (as shown in the catalog headings).",
      "type": "string"
    },
    "search": {
      "description": "Case-insensitive substring filter over tool names and descriptions.",
      "type": "string"
    }
  }
}
model_metadataCurate a model file's embedded .safetensors metadata (Model Explorer). Driven by the `action` parameter: - action:"read" — Read a model file's CURRENT embedded metadata + evidence, for curating it. Returns classify (asset_type/base/precision/rank), the current model_card and prompt_director namespaces, read-only modelspec, top training tags (ss_tag_frequency), the Civitai description, and example prompts. Call this FIRST when the user wants to improve/curate a model's embedded .safetensors metadata, so you propose from real data. NOTE: this is the embedded-in-the-tensor metadata (model_card/prompt_director/modelspec/ss_*) — NOT the separate lora_catalog. `category` = ComfyUI model folder ('loras','checkpoints','vae',…); `name` = filename incl. .safetensors — BOTH required for read/propose, e.g. {action:"read", category:"loras", name:"my_model.safetensors"}. DEPENDENCY: the curated read proxies the OPTIONAL 'comfyui-model-explorer' custom node. When that node is absent but the model file is reachable on the LOCAL filesystem, the tool does NOT hard-fail — it degrades to a structured 'model_explorer: unavailable' result with local evidence (file stat, the download_model action:"download_civitai" sidecar, and the raw embedded safetensors metadata). Without local filesystem access, it still returns the same structured unavailable result, but without file evidence. - action:"propose" — PROPOSE cleaned embedded metadata into the user's diff-review window. This does NOT write the file — the user sees your proposed fields vs current, edits/discusses, and their Confirm does the write. Call whenever you have a proposal OR the user asks you to revise one; each call REPLACES the live proposal, so send the FULL field set you're proposing. Include only fields you're confident about. Keys: display_name, description_clean, semantic_intent, prompt_guidance, preservation_guidance, trigger_tokens[] (EXACT tokens — never invent), activation_phrases[], negative_tokens[], tags[], compatible_families[], default_strength_model, default_strength_clip, strength_min, strength_max. NEVER write metadata directly. - action:"fetch_civitai" — READ-ONLY: pull this model's data from Civitai (civitai.com) — the rich description, trainedWords, example prompts (with the prompt text used in the sample images), tags, nsfw flag, and source_url — WITHOUT writing anything. Call this when the embedded metadata is thin (empty model_card/prompt_director, no ss_tag_frequency) or to flesh out details before proposing. Treat the result as RAW input: distill the (often marketing-heavy) description, and MINE THE EXAMPLE PROMPTS for the real trigger — the trigger is frequently ONLY in the sample prompts even when trainedWords is EMPTY (e.g. every prompt starting with 'photo in the style of X' means X is the trigger). Adult models (civitai.red) resolve through this same API. Then clean it up and call action:"propose". DEPENDENCY: automatic by-hash lookup uses the OPTIONAL 'comfyui-model-explorer' custom node. If that node isn't installed, pass 'version_id' (the CivitAI modelVersionId) and this action degrades to CivitAI's public REST API directly — no node, no auth. Without both the node AND a version_id it returns a clear 'optional feature unavailable' message rather than enriching.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "read",
        "propose",
        "fetch_civitai"
      ],
      "description": "Which metadata operation to perform. All three actions require `category` + `name`; \"propose\" also requires `fields` (optional `note`); \"fetch_civitai\" takes an optional `version_id`."
    },
    "category": {
      "description": "ComfyUI model folder, e.g. 'loras'. REQUIRED for all three actions.",
      "type": "string"
    },
    "name": {
      "description": "model filename incl. .safetensors. REQUIRED for all three actions.",
      "type": "string"
    },
    "fields": {
      "description": "action:\"propose\" — REQUIRED proposed field map (see description).",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "note": {
      "description": "action:\"propose\" — optional one-line note about this revision.",
      "type": "string"
    },
    "version_id": {
      "description": "action:\"fetch_civitai\" — force a specific Civitai modelVersionId if hash lookup misses.",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "action"
  ]
}
node_packAuthor, edit, test and publish YOUR OWN ComfyUI custom-node pack under the custom_nodes/ directory the running ComfyUI actually scans. LOCAL-ONLY: it acts on the local filesystem and is meaningless for a remote --comfyui-url target. Every file-touching action (list_files, read, search, write, patch, git) is jailed to custom_nodes/ under the directory the running ComfyUI actually scans — the server's own --base-directory when it reports one (on ComfyUI Desktop that is NOT the code install root), else the live main.py checkout on a split install that has no --base-directory (the data workspace is not scanned unless the flag said so), else COMFYUI_PATH, else the saved default workspace, else the running LOCAL server's own install root; the one exception is action:"publish", which also accepts an explicit `path` to a pack directory ANYWHERE on this machine and therefore works without COMFYUI_PATH. To INSTALL or update someone else's pack use install_custom_node instead. Driven by the `action` parameter: - action:"scaffold" — Generate a new pack from a template into the local ComfyUI's scanned custom_nodes/<name>/ (the install base resolves from the running server's --base-directory when it reports one, else the live main.py checkout on a split install, else COMFYUI_PATH, else the saved default workspace, else the running LOCAL server this session is connected to). Writes pyproject.toml (with the [tool.comfy] PublisherId/DisplayName/Icon table the Comfy Registry requires), __init__.py exporting NODE_CLASS_MAPPINGS / NODE_DISPLAY_NAME_MAPPINGS, and src/nodes.py containing a runnable sample node (INPUT_TYPES/RETURN_TYPES/FUNCTION/CATEGORY), plus .comfyignore and .gitignore. Optionally emits a web/js frontend stub (wiring WEB_DIRECTORY) and a GitHub Actions publish workflow (with_ci). This is the FIRST step of the author loop: scaffold here, then restart_comfyui to load it, test it, and finally action:"publish". Names must be a safe lowercase slug and cannot escape custom_nodes/; an existing non-empty directory is left untouched unless overwrite is true. Requires `name` and `display_name`. - action:"verify" — Test that a pack actually LOADS in ComfyUI — the middle step of the author loop. Restarts the local ComfyUI and waits for it to become ready, then checks that the pack's node class_types appear in /object_info. A node that fails to import (a missing dependency or a syntax error) simply never registers, so any missing class_types pinpoint a broken pack. Provide `class_types` explicitly, or a pack `name` whose __init__.py declares NODE_CLASS_MAPPINGS (the keys are inferred). Needs a managed local ComfyUI. Set restart:false to check the already-running server without restarting it. - action:"publish" — Publish a local pack to the public Comfy Registry (registry.comfy.org) by running `comfy node publish` inside the pack directory. First validates the pack's pyproject.toml has the required [project].name, [project].version and [tool.comfy].PublisherId (refusing the scaffold placeholder), then publishes using the API key from the REGISTRY_ACCESS_TOKEN environment variable (passed to comfy-cli via the environment, never via logged arguments). This is the LAST step of the author loop and an IRREVERSIBLE, EXTERNAL action: it creates/updates a PUBLIC registry version that this tool cannot undo. Requires comfy-cli installed and REGISTRY_ACCESS_TOKEN set. Give `name` (a folder under custom_nodes/) or `path` (an explicit pack directory). - action:"list_files" — List the files in one installed pack under custom_nodes/<pack>/ (read-only). Skips .git/, __pycache__/ and node_modules/. Use this to orient before action:"read" / action:"search" when diagnosing or editing a pack you found via bisect or install_custom_node (action:"fix"). Requires `pack`. - action:"read" — Read a slice of ONE file inside a pack (read-only), with bounded output so a huge file can't flood the context. Returns the requested line range with a truncation notice when clipped; long lines are chunked. Pair with action:"search" to locate the line, then action:"patch" or action:"write" to change it. Requires `path`. - action:"search" — Regex-search custom-node source under custom_nodes/ (read-only). Uses ripgrep when it's on PATH, otherwise a bounded built-in scanner (skips dot-dirs, __pycache__/node_modules, binary and >1 MiB files). Returns file/line/text matches with per-line and result caps. Use this to find where a node class, import, or error string lives before reading or patching. Requires `query`. - action:"write" — Create or overwrite ONE file inside a pack. Refuses to clobber an existing file unless overwrite is true, and creates parent directories by default. Use for whole-file edits or new files; for surgical edits prefer action:"patch". After writing, run action:"verify" and restart_comfyui to load the change. Requires `path` and `content`. - action:"patch" — Apply a unified diff (---/+++ headers) or an apply-patch / simplified diff (`*** Begin Patch` / `*** Update File`) to custom-node source under custom_nodes/. Every touched path is jail-checked BEFORE any git call, then the patch is validated with `git apply --check` and only applied if the check passes (two-phase; never uses --unsafe-paths). Paths are relative to custom_nodes/ and may carry a/ b/ prefixes; works on non-repo packs too. Ideal for surgical edits located via action:"search". Requires `patch`. - action:"git" — Run a git operation inside one pack, selected by `git_action` (status/diff/log/commit/push). Reads (status/diff/log) are always allowed. Writes (commit/push) require the environment flag COMFYUI_MCP_ALLOW_GIT_WRITES=1 (default OFF) and otherwise return a structured DISABLED_BY_CONFIG refusal so you can self-correct. `paths` entries are pack-relative paths to stage/scope, resolved against the selected pack root (not custom_nodes/); an absolute path is accepted only when it remains inside both the selected pack and custom_nodes/ jails. commit requires a `message` and stages either the given `paths` or all pack changes. This is the final step of the author loop after scaffold → write/patch → verify → restart_comfyui, before action:"publish". Requires `pack` and `git_action`.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "scaffold",
        "verify",
        "publish",
        "list_files",
        "read",
        "search",
        "write",
        "patch",
        "git"
      ],
      "description": "Which node-pack operation to perform. \"scaffold\" requires `name` + `display_name`; \"list_files\" requires `pack`; \"read\" requires `path`; \"search\" requires `query`; \"write\" requires `path` + `content`; \"patch\" requires `patch`; \"git\" requires `pack` + `git_action`. \"verify\" and \"publish\" have no required parameters — \"verify\" resolves the pack from `name` (or checks `class_types` directly), \"publish\" from `name` or `path`."
    },
    "name": {
      "description": "Pack folder name under <COMFYUI_PATH>/custom_nodes/. REQUIRED for action:\"scaffold\" — a safe lowercase slug (letters, digits, hyphens, underscores), e.g. 'my-cool-nodes', which becomes the directory under custom_nodes/ and the pyproject [project].name. For action:\"verify\", the pack whose __init__.py NODE_CLASS_MAPPINGS keys are inferred and checked when `class_types` is omitted. For action:\"publish\", the pack folder to publish (give this or `path`).",
      "type": "string"
    },
    "display_name": {
      "description": "action:\"scaffold\" — REQUIRED. Human-readable name shown in the ComfyUI node menu and the registry listing.",
      "type": "string"
    },
    "category": {
      "description": "action:\"scaffold\" — node menu category for the sample node (default 'custom').",
      "type": "string"
    },
    "description": {
      "description": "action:\"scaffold\" — short description written to pyproject [project].description.",
      "type": "string"
    },
    "publisher_id": {
      "description": "action:\"scaffold\" — your Comfy Registry publisher id, stamped into [tool.comfy].PublisherId. If omitted a placeholder is written that you must replace before publishing.",
      "type": "string"
    },
    "with_frontend": {
      "description": "action:\"scaffold\" — if true, also generate a web/js/<name>.js extension stub and set WEB_DIRECTORY (default false).",
      "type": "boolean"
    },
    "with_ci": {
      "description": "action:\"scaffold\" — if true, also generate .github/workflows/publish_action.yml (Comfy-Org/publish-node-action; needs the REGISTRY_ACCESS_TOKEN repo secret) so pushing a pyproject.toml version bump auto-publishes (default false).",
      "type": "boolean"
    },
    "overwrite": {
      "description": "action:\"scaffold\" — overwrite template files in an existing pack directory instead of refusing (default false). action:\"write\" — overwrite an existing file instead of refusing (default false).",
      "type": "boolean"
    },
    "class_types": {
      "description": "action:\"verify\" — explicit NODE_CLASS_MAPPINGS keys to confirm are registered in /object_info. Takes precedence over inferring from `name`.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "restart": {
      "description": "action:\"verify\" — restart ComfyUI before checking so newly-added packs load (default true). Set false to check the live server as-is.",
      "type": "boolean"
    },
    "pack": {
      "description": "Pack folder name under custom_nodes/ (e.g. 'ComfyUI-Manager'). REQUIRED for action:\"list_files\" and action:\"git\".",
      "type": "string"
    },
    "path": {
      "description": "REQUIRED for action:\"read\" and action:\"write\": a pack-relative path under custom_nodes/, e.g. 'MyPack/nodes.py'. For action:\"search\", the pack-relative directory to search, or '.' for all packs (default '.'). For action:\"publish\" ONLY, this is instead an explicit absolute path to the pack directory to publish, and it overrides `name` when both are given.",
      "type": "string"
    },
    "content": {
      "description": "action:\"write\" — REQUIRED. Full file contents to write.",
      "type": "string"
    },
    "create_dirs": {
      "description": "action:\"write\" — create missing parent directories (default true).",
      "type": "boolean"
    },
    "patch": {
      "description": "action:\"patch\" — REQUIRED. A unified diff (---/+++ headers) or an apply-patch / simplified diff (`*** Begin Patch` / `*** Update File: path`). File headers are read to determine touched paths, which must resolve inside custom_nodes/ (e.g. 'a/MyPack/nodes.py' or 'MyPack/nodes.py').",
      "type": "string"
    },
    "query": {
      "description": "action:\"search\" — REQUIRED. Regular expression to search for.",
      "type": "string"
    },
    "glob": {
      "description": "action:\"list_files\" — optional glob to filter entries (supports *, **, ?), matched against pack-relative paths. action:\"search\" — optional glob to restrict which files are searched (e.g. '**/*.py').",
      "type": "string"
    },
    "max_entries": {
      "description": "action:\"list_files\" — maximum entries to return (default 500, max 2000 — a hard clamp). The walk STOPS at this many, so a capped result is not the pack's full file list.",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "start_line": {
      "description": "action:\"read\" — 1-based line to start at (default 1).",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "line_count": {
      "description": "action:\"read\" — number of lines to return (default 240, max 800).",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "max_chars": {
      "description": "action:\"read\" — maximum characters to return (default 12000, min 500, max 24000 — hard clamps; values outside are silently pulled into range). action:\"git\" — maximum characters of git output to return (default 12000, min 500, max 24000 — hard clamps the runtime applies; values outside are silently pulled into range).",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "max_results": {
      "description": "action:\"search\" — maximum matches to return (default 50, max 100). The scan STOPS at this many, so a capped result is not a complete match set.",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "case_sensitive": {
      "description": "action:\"search\" — match case-sensitively (default false).",
      "type": "boolean"
    },
    "git_action": {
      "description": "action:\"git\" — REQUIRED. Which git operation to run: status/diff/log are read-only; commit/push require COMFYUI_MCP_ALLOW_GIT_WRITES=1. Named `git_action` rather than `action` only because `action` is this tool's dispatch field; the git operation itself is unchanged.",
      "type": "string",
      "enum": [
        "status",
        "diff",
        "log",
        "commit",
        "push"
      ]
    },
    "message": {
      "description": "action:\"git\" — commit message (required for git_action 'commit').",
      "type": "string"
    },
    "paths": {
      "description": "action:\"git\" — pack-relative paths to stage/scope (jail-checked). Defaults to all pack changes.",
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "action"
  ]
}
node_snapshotCustom-node snapshots via ComfyUI-Manager (mirrors `comfy node save-snapshot` / `restore-snapshot`). Driven by the `action` parameter: - action:"list" — List the snapshots ComfyUI-Manager knows about. No other parameters. Read-only. - action:"save" — Save the current custom-node and version state. With no `name`, Manager assigns a timestamped snapshot (works against remote instances). Providing `name` writes a custom-named snapshot file, which requires a local ComfyUI install root (COMFYUI_PATH or a saved default workspace — see the workspace tool) and is unavailable against a genuinely remote ComfyUI. - action:"restore" — Restore a previously saved snapshot by `name` (required). ComfyUI-Manager applies the custom-node changes on the next ComfyUI restart; use action:"list" to find available names.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "list",
        "save",
        "restore"
      ],
      "description": "Which snapshot operation to perform. \"list\" takes no other parameters; \"save\" takes an optional `name`; \"restore\" requires `name`."
    },
    "name": {
      "description": "Snapshot name (no extension, no path separators). REQUIRED for action:\"restore\" (as shown by action:\"list\"). OPTIONAL for action:\"save\" — omit to let ComfyUI-Manager assign a timestamped name. Ignored by action:\"list\".",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
queueInspect and manage the ComfyUI execution queue. Driven by the `action` parameter: - action:"list" — The job running now plus all pending jobs, each with its prompt_id and position. Read-only; requires a reachable ComfyUI server (works against local or remote --comfyui-url). Omits queued workflow payloads by default to keep output small; set include_workflows:true when you need to inspect or edit the exact pending payload. Use this before action:"cancel" (running), action:"cancel_queued"/action:"clear" (pending), action:"move", or action:"edit". - action:"status" — Check ONE job by its prompt_id (the id returned by enqueue_workflow). Queries the connected ComfyUI server; requires it to be running. Returns JSON with running, pending, and done booleans, plus optional status_str, error details, and execution_stats from ComfyUI history once the job is done. If the prompt is neither running nor queued AND /history has no record of it (a restart wipes both), it returns done:false + found:false with an explanatory message — a prompt ComfyUI never executed is NOT a completion, so do not wait for its outputs. Also returns text_outputs when the workflow contained text-preview nodes (Preview as Text, ShowText, …) — those produce no image file, so this is the ONLY way to read their result; report that text back to the user. Use action:"list" to see the whole queue at once, and get_history for full output filenames. - action:"get_workflow" — The full workflow payload for one PENDING queue item by prompt_id. Read-only. Does not work for the currently running job because ComfyUI cannot safely edit a job after execution starts. - action:"move" — Move a PENDING queue item to the front or back by removing it and re-enqueuing its saved workflow payload; `position` ("front"|"back") is required. The job receives a NEW prompt_id; the old prompt_id is removed. Running jobs cannot be moved. - action:"edit" — Edit a PENDING queue item by removing it and re-enqueuing an updated workflow. Provide either a complete replacement `workflow` or `node_inputs` patches keyed by node id; `position` selects where to requeue (default back). The job receives a NEW prompt_id; the old prompt_id is removed. Running jobs cannot be edited. - action:"cancel" — Stop the CURRENTLY RUNNING job ROBUSTLY. Sends an interrupt, then WAITS and verifies the job actually stopped — ComfyUI only honors interrupts BETWEEN steps, so a long single step (e.g. a high-res video sampler) can ignore a plain cancel. If the interrupt isn't honored it escalates to freeing VRAM (POST /free) and re-checks; if it STILL won't die it reports the job as WEDGED and tells you to restart_comfyui (an HTTP cancel cannot kill a stuck step). Set clear_pending:true to also drop ALL pending jobs in the same call — the correct "reset the queue" action, since cancelling alone leaves pending jobs that would run next. The partial result is discarded. With `prompt_id` given, only interrupts the running job when its prompt_id matches; omit to interrupt whatever is currently running. Use action:"cancel_queued" to remove one specific PENDING job instead. - action:"cancel_queued" — Remove one specific PENDING job from the queue by prompt_id, then VERIFY the removal against a live /queue read on both sides of it. Only PENDING jobs can be removed this way: ComfyUI silently ignores the request for a job it has already started, so if the job won the race and is now RUNNING this reports isError and tells you the outputs will still be delivered — use action:"cancel" to interrupt that. Also reports isError when the prompt_id was not in the queue at all (it already finished, or was never queued) rather than calling that a removal. - action:"clear" — Clear ALL pending jobs from the queue. Does not affect the currently running job.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "list",
        "status",
        "get_workflow",
        "move",
        "edit",
        "cancel",
        "cancel_queued",
        "clear"
      ],
      "description": "Which queue operation to perform. \"list\" and \"clear\" take no other parameters; \"status\", \"get_workflow\" and \"cancel_queued\" require `prompt_id`; \"move\" requires `prompt_id` + `position`; \"edit\" requires `prompt_id` (optional `workflow`/`node_inputs`/`position`); \"cancel\" takes an optional `prompt_id` and `clear_pending`."
    },
    "prompt_id": {
      "description": "The prompt_id of a job (the id returned by enqueue_workflow). REQUIRED for actions \"status\", \"get_workflow\", \"move\", \"edit\" and \"cancel_queued\" (a PENDING queue item for all but \"status\"). OPTIONAL for action:\"cancel\" — if given, only interrupts the running job when its prompt_id matches; omit to interrupt whatever is currently running.",
      "type": "string"
    },
    "include_workflows": {
      "description": "action:\"list\" — include each running/pending job's workflow payload and extra_data. Can be large.",
      "type": "boolean"
    },
    "position": {
      "description": "Where to requeue the job. REQUIRED for action:\"move\". OPTIONAL for action:\"edit\" — defaults to back.",
      "type": "string",
      "enum": [
        "front",
        "back"
      ]
    },
    "workflow": {
      "description": "action:\"edit\" — optional complete replacement API-format workflow. If omitted, the existing queued workflow is patched with `node_inputs`.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "node_inputs": {
      "description": "action:\"edit\" — optional input patches keyed by node id, e.g. {\"3\":{\"steps\":30,\"cfg\":7}}.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      }
    },
    "clear_pending": {
      "description": "action:\"cancel\" — also clear ALL pending jobs (recommended when resetting after a stuck/slow render, so a re-queue doesn't stack behind a backlog). Default false.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
report_issueFile or triage a GitHub issue for a bug/problem you hit (ComfyUI, a workflow, a model, custom nodes, or comfyui-mcp/its panel). For OUR repos (artokun/comfyui-mcp, artokun/comfyui-mcp-panel) it sends the report to the AI triage worker, which searches existing OPEN and CLOSED issues, version-matches, and either files a new issue, adds context to an existing one, or — if the problem was already FIXED in a newer version than the user runs — answers with the fixing PR + fixed-in version and a recommendation to upgrade (no new issue). It returns that triage result plus an instant check of whether the user is on the latest versions. TIMING: this call BLOCKS while the triage runs — typically a few minutes — and that wait is normal, not a hang. It always returns eventually (every request is time-capped and the poll budget is bounded); on a failing network the caps make that wait longer, but never indefinite. Do not abort a slow call just to retry it: once the worker has accepted the report it keeps triaging on its own — filing, deduping into an existing issue, advising an upgrade, or (rarely) reporting that it could not file — so a blind retry can double-file. If triage outlasts the polling budget the call still returns, with pending:true (and a job_id when the worker gave one — an accepted submit whose acknowledgement was unreadable returns pending without it). If the worker is unreachable it falls back to a prefilled GitHub 'new issue' URL. For third-party repos it returns a prefilled URL to SHARE (it does not auto-file). ALWAYS pass mcp_version and panel_version from the known environment (the env line in your context, e.g. 'mcp=… panel=…') so the worker can tell the user if simply upgrading fixes it — the single most common resolution. Surface the worker's agent_message / upgrade advice to the user.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1,
      "description": "Short, specific issue title."
    },
    "body": {
      "type": "string",
      "minLength": 1,
      "description": "Issue body: what happened, steps to reproduce, the exact error text, and environment (GPU/VRAM, ComfyUI version, ComfyUI FRONTEND version, OS) if known. The FRONTEND version is a SEPARATE package from ComfyUI and they move independently — get_system_stats (action:\"health\") prints both, and for any panel/UI bug it is often the deciding variable. Scrub secrets first."
    },
    "repo": {
      "description": "owner/repo (default 'artokun/comfyui-mcp'; use 'artokun/comfyui-mcp-panel' for the sidebar panel).",
      "type": "string"
    },
    "labels": {
      "description": "Optional GitHub label names to prefill.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "mcp_version": {
      "description": "The running comfyui-mcp version (from the env line in your context). Auto-detected if omitted.",
      "type": "string"
    },
    "panel_version": {
      "description": "The running comfyui-mcp-panel (sidebar) version, from the env line in your context, if known.",
      "type": "string"
    },
    "no_file": {
      "description": "Force the prefilled-URL path even for our repos (skip the Worker). Rarely needed.",
      "type": "boolean"
    }
  },
  "required": [
    "title",
    "body"
  ]
}
restart_comfyuiControl the lifecycle of the ComfyUI server process. Driven by the `action` parameter: - action:"restart" — Restart ComfyUI: stops the running process (capturing its config), waits for the port to free, relaunches with the same arguments, and polls the API for bounded readiness. Also works against a REMOTE/tunnelled ComfyUI (via --comfyui-url) by rebooting through ComfyUI-Manager over HTTP and polling for it to come back (requires ComfyUI-Manager present and its security level permitting the reboot). When COMFYUI_RESTART_COMMAND is set, a LOCAL restart runs that command instead of kill+relaunch — the recovery path for an externally managed install (a container, a systemd unit, a launcher) whose launch path cannot be proven from here. This is the normal way to reload newly installed custom nodes, and the escalation when queue (action:"cancel") reports a job WEDGED. - action:"start" — Start ComfyUI using process info saved from a previous action:"stop" call. Supports both Desktop app and manual Python installs. Polls the API for bounded readiness before reporting ready. Local installs only. - action:"stop" — Stop the running ComfyUI process. Captures process info so it can be restarted with action:"start". Kills the process tree and resets the WebSocket client. Local installs only. Anything queued or rendering is lost.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "restart",
        "start",
        "stop"
      ],
      "description": "Which process operation to perform. None of them takes any other parameter. \"restart\" stops and relaunches in one call (and is the only action that also works against a remote/tunnelled ComfyUI); \"start\" relaunches from the info a previous \"stop\" saved; \"stop\" kills the running process tree."
    }
  },
  "required": [
    "action"
  ]
}
runpodDeploy, start, stop, inspect and connect to RunPod cloud GPU pods, and switch rendering between your local machine and a pod. Driven by the `action` parameter. SPENDS MONEY: action:"create" and action:"start" put a pod into a billing state; action:"stop" ends GPU billing. Confirm with the user before creating or starting a pod, and stop pods when the work is done. - action:"create" — Deploy a BRAND-NEW RunPod pod from our comfyui-mcp template (image with the panel + Manager + our nodes preinstalled), then it can be started/connected like any pod. One-tap alternative to the console deploy link for a user who already has a RunPod account + API key. Because our template is used, the agent can install the user's exact custom nodes/LoRAs + download models on it → full canvas parity. Tries several GPU types until one has capacity (on-demand availability fluctuates). NOTE: this bills GPU-time as soon as the pod boots — confirm with the user first, and stop it (action:"stop") when done. Created pods carry a DEAD-MAN SWITCH: if comfyui-mcp stops minding the pod (crash/offline), the pod STOPS ITSELF after a grace period so it can't bill forever — it uses the pod-scoped key RunPod auto-injects, so your account key never leaves this machine (disable with deadman:false). For onboarding a NEW RunPod user, prefer action:"deploy_link" so their signup credits our referral. - action:"start" — Start (resume) a stopped/exited RunPod pod by ID — RunPod re-attaches a GPU and boots the container (billing resumes). Returns immediately once RunPod accepts the resume; the pod then takes ~30-90s to become reachable, so follow with action:"status" (or action:"connect", which verifies readiness) rather than assuming it's instantly up. If RunPod can't allocate the requested GPU it errors — try a different gpu_count or GPU type in the console. - action:"stop" — Stop a running RunPod pod by ID — releases the GPU and stops GPU-time billing while KEEPING the pod and its disk (so you can start it again later). Use when the user is done rendering. Does NOT terminate/delete the pod (that's a console action). Confirm with the user before stopping a pod that has work in progress. - action:"status" — Get the live state of a pod by ID: its desired status (RUNNING / EXITED / TERMINATED), GPU, uptime, $/hr cost, GPU/VRAM utilization, and — when it's running and exposes ComfyUI — the proxy URL to connect to. Call this first to see what state a pod is in before starting/stopping/connecting. Read-only. - action:"list" — List all RunPod pods on the account (id, name, status, GPU, cost). Use when the user hasn't given a pod ID, or to find the one they mean. If the account has no pods, tell the user to create one and share action:"deploy_link". Read-only. - action:"connect" — Connect comfyui-mcp to a pod's ComfyUI so ALL the other comfyui tools (generate, workflows, models, panel, …) run against that pod. Give it a pod ID: it verifies the pod is RUNNING with ComfyUI reachable, resolves the pod's proxy URL, and retargets this orchestrator's ComfyUI client to it. If the pod isn't ready it tells you what's missing (run action:"start" / runpod_watch action:"troubleshoot" first). This is the 'live connection' — after it succeeds, the rest of the session talks to the pod. - action:"use_local" — Switch comfyui-mcp back to the LOCAL ComfyUI on this machine (the 'Local' half of the local⇄pod switch) — retargets rendering to loopback so generate/workflows run on the local GPU again. Stops broadcasting the pod's status but does NOT stop the pod itself (use action:"stop" to end billing). Use when the user wants to render locally again after working on a pod. - action:"deploy_link" — Get the RunPod DEPLOY link for spinning up a NEW comfyui-mcp pod. Share this with the user whenever they have no pod, or want to create one — it opens RunPod pre-configured with our template AND carries our referral code, so their signup/spend credits us. Prefer handing over THIS link for pod creation (rather than describing the console steps), so the referral attaches. Read-only.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "create",
        "start",
        "stop",
        "status",
        "list",
        "connect",
        "use_local",
        "deploy_link"
      ],
      "description": "Which RunPod operation to perform. \"start\", \"stop\", \"status\" and \"connect\" require `pod_id`; \"create\" takes the optional deploy parameters (name/gpu_type/cloud_type/connect/deadman); \"list\", \"use_local\" and \"deploy_link\" take no other parameters. \"create\" and \"start\" BILL; \"stop\" ends billing."
    },
    "pod_id": {
      "description": "The RunPod pod ID (from console.runpod.io, or action:\"list\"). REQUIRED for actions \"start\", \"stop\", \"status\" and \"connect\". Ignored by \"create\", \"list\", \"use_local\" and \"deploy_link\".",
      "type": "string"
    },
    "gpu_count": {
      "description": "action:\"start\" — GPUs to attach on resume (default 1).",
      "type": "integer",
      "minimum": 1,
      "maximum": 8
    },
    "name": {
      "description": "action:\"create\" — pod name (default 'comfyui-mcp').",
      "type": "string"
    },
    "gpu_type": {
      "description": "action:\"create\" — GPU type to prefer, e.g. \"NVIDIA GeForce RTX 4090\". Default tries: NVIDIA GeForce RTX 4090, NVIDIA RTX A6000, NVIDIA RTX PRO 4500 Blackwell, NVIDIA A40, NVIDIA RTX A5000.",
      "type": "string"
    },
    "cloud_type": {
      "description": "action:\"create\" — COMMUNITY (cheaper, default) or SECURE.",
      "type": "string",
      "enum": [
        "COMMUNITY",
        "SECURE"
      ]
    },
    "connect": {
      "description": "action:\"create\" — auto-connect when booted: the ORCHESTRATOR waits for ComfyUI to answer (1-3min), then retargets + watches — this call returns immediately (default false: deploy only; connect later with action:\"connect\"). This is the create-time flag, NOT the action of the same name.",
      "type": "boolean"
    },
    "deadman": {
      "description": "action:\"create\" — arm the pod-side dead-man watchdog (default true for OUR stock template): the pod STOPS ITSELF if comfyui-mcp's heartbeats stop (process crash/offline — boot grace ~45min, then ~20min without beats). Uses the pod-scoped API key RunPod auto-injects into every pod — your account key never leaves this machine. false deploys without the watchdog. With a custom template (RUNPOD_TEMPLATE_ID) the default is OFF — pass true only if that image ships our watchdog.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
Annotations
{
  "destructiveHint": true,
  "openWorldHint": true,
  "idempotentHint": false
}
DestructiveNon-idempotentOpen world
runpod_watchWatch a RunPod pod's live status in the control panel, stop watching it, or diagnose why it isn't usable. Driven by the `action` parameter. None of these actions DEPLOYS or resumes a pod — the runpod tool does that. One of them CAN stop one, though: action:"watch" arms the idle auto-stop, so a watched pod whose ComfyUI sits idle past the configured timeout is stopped to save cost. Do not watch a pod that is deliberately idle but must stay up. - action:"watch" — Start broadcasting a pod's LIVE status to the control panel (desktop + mobile) — status, GPU/VRAM utilization, uptime, $/hr, and an idle-auto-stop countdown — refreshed every ~15s. runpod action:"connect" already starts this for the pod it connects to; call this to watch a pod WITHOUT retargeting comfyui-mcp at it (e.g. monitor a pod that's still booting). While watched, if the pod's ComfyUI sits idle past the configured timeout it is auto-stopped to save cost. - action:"unwatch" — Stop broadcasting a pod's live status to the control panel (does NOT stop the pod itself — use runpod action:"stop" for that). Also disables idle auto-stop for it. - action:"troubleshoot" — Diagnose why a RunPod pod isn't usable — call this when the pod 'won't connect', ComfyUI is unreachable, or a render can't reach the pod. Checks: does the pod exist, is it RUNNING (vs stopped/exited — then start it), is a GPU attached, is ComfyUI's port exposed as an HTTP proxy port, and does ComfyUI actually ANSWER at its proxy URL (probes /system_stats). Returns the specific blocker and the next step. Read-only.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "watch",
        "unwatch",
        "troubleshoot"
      ],
      "description": "Which watch operation to perform. \"watch\" and \"troubleshoot\" require `pod_id`; \"unwatch\" takes no other parameters (it clears whichever pod is currently watched)."
    },
    "pod_id": {
      "description": "The RunPod pod ID. REQUIRED for actions \"watch\" and \"troubleshoot\". Ignored by \"unwatch\", which clears the single currently watched pod.",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
save_workflowWRITE to the ComfyUI user library: persist a workflow, or capture/verify its provenance lock. This is the only tool here that writes — reading is get_workflow. Driven by the `action` parameter: - action:"save" — Save a workflow JSON to the connected ComfyUI server's user library so it appears in the ComfyUI web UI. Requires a running ComfyUI server; this writes to that server's userdata and OVERWRITES any existing file with the same filename without confirmation. Web-UI-format JSON ({ nodes: [], links: [] }) is saved as-is and is the preferred input — when re-saving an existing workflow, load it with get_workflow (action:"get", format='ui') and modify THAT. API-format graphs ({ '1': { class_type, inputs } }) are AUTO-CONVERTED to Web UI format with a generated layout so the saved file always opens in the ComfyUI canvas (the canvas cannot open raw API format). Returns a confirmation message (noting the conversion and any warnings), or the HTTP status and error text on failure. - action:"lock" — Capture a provenance lock for a saved workflow so it can be exactly reproduced later. Walks the workflow's model loaders (CheckpointLoaderSimple, UNETLoader, VAELoader, LoraLoader, ControlNetLoader, etc.), SHA-256s every referenced model file, records the git commit currently checked out for every custom node pack the workflow's class_types come from, and captures ComfyUI's reported version. WRITES `<filename>.lock.json` next to the workflow in ComfyUI's user library. Requires local filesystem access: models resolve from the data/model roots, and pack commits inspect custom_nodes on the live --base-directory / COMFYUI_PATH data root (not COMFYUI_CODE_PATH). Pair with action:"verify_lock" later to detect drift. - action:"verify_lock" — Compare a saved workflow's lock file against the current state of the local install and report drift. Loads `<filename>.lock.json`, re-computes a current lock from the same workflow, and diffs: which models have a different SHA-256, which custom node packs are on a different commit, whether ComfyUI's version changed. Use before re-running an important workflow days or weeks later to confirm it'll behave the same. Supports split local installs (models and packs stay on the data/base root). Read-only; returns a structured drift report (empty arrays everywhere mean perfect parity).
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "save",
        "lock",
        "verify_lock"
      ],
      "description": "Which write/provenance operation to perform. All three require `filename`; \"save\" also requires `workflow`."
    },
    "filename": {
      "description": "Workflow filename in the ComfyUI user library (e.g. 'my_workflow.json'). REQUIRED for every action. A missing `.json` suffix is appended before use; extension case and forward-slash subfolders are preserved. The name must be a safe relative path. For action:\"save\" this OVERWRITES an existing file of the same canonical name; for \"lock\"/\"verify_lock\" the lock is read/written as '<filename>.lock.json' alongside it.",
      "type": "string"
    },
    "workflow": {
      "description": "action:\"save\" (REQUIRED) — Workflow JSON to save. Web UI format ({ nodes: [], links: [] }) is stored verbatim; API format ({ '1': { class_type, inputs } }) is auto-converted to Web UI format (generated layout) so it stays openable in ComfyUI's canvas. Not validated against the server before saving.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    }
  },
  "required": [
    "action"
  ]
}
search_custom_nodesDiscover ComfyUI custom node PACKS in the public ComfyUI Registry (registry.comfy.org). Read-only and network-only: queries the hosted registry over HTTP and does NOT require a running ComfyUI or COMFYUI_PATH. This searches node PACKS, not models (use download_model action:"search") and not local installs (use list_local_models action:"list"). To actually install what you find, or to manage packs already installed, use install_custom_node. Driven by the `action` parameter: - action:"search" — Search by keyword; `query` required. Returns a ranked list of packs with id, name, author, install count, and latest version. The keyword search ranks a fixed window of packs client-side, so when it matches nothing the query is also tried as an exact registry id automatically (e.g. 'comfyui kjnodes' → 'comfyui-kjnodes'). Pass a returned id to action:"details" for full info, or to install_custom_node (action:"install"). - action:"details" — Full details for ONE pack by its exact registry id: description, author, license, repository, install count, latest version, the node types it provides, and recent version changelogs. Look up the id via action:"search" first.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "search",
        "details"
      ],
      "description": "Which registry lookup to perform. \"search\" requires `query` (and takes optional `limit`/`page`); \"details\" requires `id`."
    },
    "query": {
      "description": "action:\"search\" — REQUIRED. Keyword(s) to match against pack name/description, e.g. 'impact', 'controlnet aux'.",
      "type": "string"
    },
    "limit": {
      "description": "action:\"search\" — max results to return (default 10).",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    },
    "page": {
      "description": "action:\"search\" — page number for pagination (default 1).",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "id": {
      "description": "action:\"details\" — REQUIRED. Exact registry pack id (the 'id' field from action:\"search\"), e.g. 'comfyui-impact-pack'.",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
train_doctorPreflight and set up the TRAINER ITSELF — the docker/GPU/venv machinery every training job needs. Touches no dataset and no job. Driven by the `action` parameter: - action:"doctor" — Preflight the local trainer: docker daemon reachable, `--gpus all` GPU passthrough working (NVIDIA Container Toolkit), trainer image built. Read-only, takes no other parameters. Returns per-check booleans + setup hints. Also reports the training data root and whether HF_TOKEN is set (needed to download FLUX.1-dev on first run), the native (dockerless) bootstrap status, and the connected pod. Run this first when a training start fails. - action:"bootstrap" — Set up the NATIVE (dockerless) trainer on this machine (`target` 'local', the default) or on a pod (`target` 'pod', optional `pod_id`): clone ai-toolkit at the pinned commit, create its venv, install torch + requirements. One-time per machine/pod (~10 min fresh, idempotent; a pod's /workspace persists it across restarts). Needed before a target 'pod' train_start on a fresh pod (no docker there). Long-running. - action:"build_image" — Build the headless GPU trainer image (comfyui-mcp-trainer:latest) from docker/trainer/Dockerfile — one-time, several minutes (CUDA + torch + ai-toolkit). Requires a reachable docker daemon. `aiToolkitRef` pins the ai-toolkit commit/tag for reproducibility. The docker alternative to action:"bootstrap".
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "doctor",
        "bootstrap",
        "build_image"
      ],
      "description": "Which trainer-setup operation to perform. \"doctor\" is read-only and takes no other parameters; \"bootstrap\" takes `target` (+ `pod_id` for target 'pod'); \"build_image\" takes an optional `aiToolkitRef`. None of them touches a dataset or a job."
    },
    "target": {
      "default": "local",
      "description": "action:\"bootstrap\" — where to install the native trainer. Default local.",
      "type": "string",
      "enum": [
        "local",
        "pod"
      ]
    },
    "pod_id": {
      "description": "action:\"bootstrap\" — pod to bootstrap (target 'pod'). Default: the connected pod.",
      "type": "string"
    },
    "aiToolkitRef": {
      "description": "action:\"build_image\" — ai-toolkit git ref (commit/tag) to build against. Default: the Dockerfile's pinned ref.",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
train_prepare_datasetStage and curate the training DATASETS a LoRA run consumes — the images and their captions. Datasets are keyed by `name`; the jobs that train on them live in the separate `train_start` tool and are keyed by `id`. Driven by the `action` parameter: - action:"prepare" — Stage training images + captions into a dataset dir the trainer consumes. Each item is an image (absolute `path`, OR a ComfyUI `ref` {filename,subfolder?,type?} resolved against the connected ComfyUI's output/input dirs — how phone/panel pickers hand over selections) with an optional caption (a missing caption falls back to defaultCaption — typically the trigger word). Requires `name` + `items`. Returns the datasetPath to pass to train_start (action:"start"). Character LoRA guidance: 10-30 varied images; caption what changes between images, keep the trigger word constant. - action:"list" — List staged datasets, newest-first, with image/caption counts. Read-only, takes no other parameters. Pair with action:"detail" to see one dataset's images + captions. - action:"detail" — Show ONE staged dataset by `name`: its dir (datasetPath — reusable as train_start's datasetPath) and every image with its caption (null when uncaptioned). Images render via action:"file". Read-only. - action:"update" — Edit a staged dataset by `name`: set/replace per-image captions (`setCaptions`) and/or delete individual images with their caption files (`deleteImages`). Refuses while a running/queued job trains from it. Returns per-file warnings for unknown files. This is the SURGICAL edit — it removes only the filenames you list, leaving the dataset itself in place. - action:"delete" — DESTROY a whole staged DATASET by `name`: every image and every caption under it. Irreversible, and the images are typically hand-curated and unrecoverable — confirm with the user first. Refuses while a running/queued job trains from it. THIS DELETES A DATASET, NOT A TRAINING JOB: to delete a finished job's record and checkpoints use the separate `train_start` tool with action:"delete", which is keyed by `id` rather than `name`. To remove only SOME images, use action:"update" with `deleteImages`. - action:"file" — Fetch an image under the training root (dataset image, job sample) by absolute `path` as an inline image — the tunnel-safe way for a phone/panel to render training files it can't reach over /view. Bounded: image files only, ≤ 2MB. - action:"caption_image" — Caption ONE image by absolute `path` with the user's own Claude subscription (one vision turn through the Agent SDK — not a paid API). Returns the bare caption and does NOT write it — review, then save with action:"update", or use action:"caption_dataset" to write directly. Optional `guide` steers the style; optional `trigger` is prepended by the model. - action:"caption_dataset" — Caption a whole staged dataset by `name` (or the `only` subset) with the user's own Claude subscription and WRITE the captions into its .txt files (one vision turn per image, sequential). Captioning ALWAYS runs through Claude (Agent SDK) regardless of the panel's active backend, so it needs a logged-in Claude Code session (or ANTHROPIC_API_KEY). Use after gathering images, before train_start (action:"start"). Per-file transient failures are reported without stopping the batch, but a persistent auth/credential failure stops immediately with an actionable error rather than failing every image. Optional `guide` steers all captions; optional `trigger` is prepended to each.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "prepare",
        "list",
        "detail",
        "update",
        "delete",
        "file",
        "caption_image",
        "caption_dataset"
      ],
      "description": "Which dataset operation to perform. \"list\" takes no other parameters; \"prepare\" requires `name` + `items`; \"detail\", \"update\", \"delete\" and \"caption_dataset\" require `name`; \"file\" and \"caption_image\" require `path`. NOTE \"delete\" here destroys a DATASET (images + captions) — deleting a training JOB is train_start action:\"delete\"."
    },
    "name": {
      "description": "Dataset name — the staging dir name. REQUIRED for actions \"prepare\" (it is created), \"detail\", \"update\", \"delete\" and \"caption_dataset\" (from action:\"list\"). This is a DATASET name, never a training job id.",
      "type": "string",
      "minLength": 1
    },
    "items": {
      "description": "action:\"prepare\" — the images to stage. REQUIRED for that action.",
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "path": {
            "description": "Absolute path to a source image (png/jpg/jpeg/webp).",
            "type": "string",
            "minLength": 1
          },
          "ref": {
            "description": "ComfyUI file ref (e.g. from get_image (action:\"list_outputs\") format:json) — resolved server-side with containment checks. Give path OR ref.",
            "type": "object",
            "properties": {
              "filename": {
                "type": "string",
                "minLength": 1,
                "description": "Basename only — no path separators."
              },
              "subfolder": {
                "description": "Subfolder under the root (default top level).",
                "type": "string"
              },
              "type": {
                "default": "output",
                "description": "Which ComfyUI dir to resolve against (default output).",
                "type": "string",
                "enum": [
                  "output",
                  "input"
                ]
              }
            },
            "required": [
              "filename"
            ]
          },
          "caption": {
            "description": "Caption for this image.",
            "type": "string"
          }
        }
      }
    },
    "defaultCaption": {
      "description": "action:\"prepare\" — fallback caption for items without one; usually the trigger word.",
      "type": "string"
    },
    "setCaptions": {
      "description": "action:\"update\" — {filename: caption} pairs to write (replaces existing captions).",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "deleteImages": {
      "description": "action:\"update\" — image filenames to delete from the dataset (caption files go too). Removes only these files; action:\"delete\" removes the whole dataset.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "path": {
      "description": "Absolute path of a file under the training root. REQUIRED for action:\"file\" (a dataset image or job sample, from action:\"detail\"'s datasetPath or train_start action:\"status\"'s samples) and for action:\"caption_image\" (the image to caption).",
      "type": "string",
      "minLength": 1
    },
    "guide": {
      "description": "actions \"caption_image\"/\"caption_dataset\" — extra style guidance for the captioner (e.g. 'focus on outfits and backgrounds').",
      "type": "string"
    },
    "trigger": {
      "description": "actions \"caption_image\"/\"caption_dataset\" — trigger word to prepend to the caption(s).",
      "type": "string"
    },
    "only": {
      "description": "action:\"caption_dataset\" — subset of filenames to caption (default: all images).",
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "action"
  ]
}
train_startRun and inspect LoRA training JOBS — launch a run, poll it, stop it, delete it, and read back the settings behind it. Jobs are keyed by `id`; the datasets they train on live in the separate `train_prepare_dataset` tool and are keyed by `name`. Driven by the `action` parameter: - action:"start" — Start a LoRA training job: target 'local' builds the config and launches the GPU trainer container (docker run --gpus all); target 'pod' ssh-drives pod-native training on a connected RunPod pod (pod_id, or the connector's currently connected pod). Requires `name` + `datasetPath`. Returns a job id for action:"status"/action:"cancel". Long-running — returns immediately; poll action:"status". On completion the LoRA is delivered per deliverTo (pod/local/both) and cataloged when local. Run train_doctor first if unsure the image/docker/GPU (local) or bootstrap (pod) are ready. - action:"status" — Check training progress: pass an `id` for one job (step/total, loss, recent samples, log tail, result paths when done) or OMIT `id` for all jobs newest-first. Read-only. - action:"cancel" — STOP a RUNNING job (docker stop) by `id` and mark it cancelled. Nothing is erased: checkpoints already saved stay in the job's output dir; no LoRA is handed off to models/loras, so the run can be inspected afterwards. Returns ok:false when the container could not be confirmed stopped (the job reverts to running). This is the RECOVERABLE stop — use action:"delete" only when you also want the artifacts gone. - action:"delete" — DESTROY a finished job by `id`: its record AND its output dir with checkpoints/samples, unless keep_outputs is true. Irreversible — confirm with the user first. The delivered LoRA in models/loras is NOT removed. Running/queued jobs must be cancelled first (action:"cancel"). THIS DELETES A JOB, NOT A DATASET: to delete the staged images and captions a run consumed use the separate `train_prepare_dataset` tool with action:"delete", which is keyed by `name` rather than `id`. - action:"list_flows" — List the LoRA training flows and base models the local trainer supports (phase 1: character LoRA on FLUX.1-dev), with the default training params. Read-only, takes no other parameters — call this first to see what action:"start" accepts. - action:"job_config" — Show the effective settings a job ran with by `id` (steps/lr/rank/resolution/batch/saveEvery/sampleEvery/quantize), read back from the ai-toolkit config.yml it consumed, plus flow/model/trigger/datasetPath — everything needed to run the job again with tweaks. Read-only. - action:"preview_config" — Show the RAW ai-toolkit config.yml action:"start" WOULD write for these settings (the ostris-UI 'raw config' view) — no side effects, nothing is written or started. Requires `name` + `datasetPath`. Use it to review a run before launching; pass the same params to action:"start" to execute.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "start",
        "status",
        "cancel",
        "delete",
        "list_flows",
        "job_config",
        "preview_config"
      ],
      "description": "Which training-job operation to perform. \"list_flows\" takes no other parameters; \"status\" takes an OPTIONAL `id` (omit for all jobs); \"cancel\", \"delete\" and \"job_config\" require `id`; \"start\" and \"preview_config\" require `name` + `datasetPath`. NOTE \"delete\" here destroys a training JOB — deleting a staged DATASET is train_prepare_dataset action:\"delete\"."
    },
    "id": {
      "description": "Training job id, as returned by action:\"start\" (e.g. \"t8f3k2ab\") — NEVER a dataset name. REQUIRED and must be non-empty for actions \"cancel\", \"delete\" and \"job_config\". OPTIONAL for action:\"status\": omit it (or pass an empty string) to list every job newest-first. Unused by \"start\", \"list_flows\" and \"preview_config\".",
      "type": "string"
    },
    "name": {
      "description": "Job name — becomes the output .safetensors basename (e.g. 'aria_character'). REQUIRED for actions \"start\" and \"preview_config\". This names the RUN, not the dataset it reads.",
      "type": "string",
      "minLength": 1
    },
    "flow": {
      "default": "character",
      "description": "action:\"start\" — training flow (see action:\"list_flows\").",
      "type": "string",
      "enum": [
        "character"
      ]
    },
    "model": {
      "default": "flux1-dev",
      "description": "action:\"start\" — base model (see action:\"list_flows\").",
      "type": "string",
      "enum": [
        "flux1-dev"
      ]
    },
    "datasetPath": {
      "description": "Dataset dir from train_prepare_dataset (images + same-basename .txt captions). REQUIRED for actions \"start\" and \"preview_config\".",
      "type": "string",
      "minLength": 1
    },
    "trigger": {
      "description": "Unique trigger word (e.g. 'ohwx person') — injected as trigger_word and usable in prompts.",
      "type": "string"
    },
    "params": {
      "description": "Training param overrides for actions \"start\" and \"preview_config\" (steps/lr/rank/resolution/batchSize/saveEvery/sampleEvery/quantize). Omitted keys fall back to the defaults from action:\"list_flows\". action:\"preview_config\" enforces the SAME bounds action:\"start\" does, so a preview always reflects a run that could actually launch.",
      "type": "object",
      "properties": {
        "steps": {
          "description": "Total training steps (200 = smoke test, 1500-3000 real; max 100000).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100000
        },
        "lr": {
          "description": "Learning rate (max 1).",
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 1
        },
        "rank": {
          "description": "LoRA rank (16 simple, 16-32 detailed; max 1024).",
          "type": "integer",
          "minimum": 1,
          "maximum": 1024
        },
        "resolution": {
          "description": "Resolution buckets, e.g. [512,768,1024] (each 64-4096).",
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "integer",
            "minimum": 64,
            "maximum": 4096
          }
        },
        "batchSize": {
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "saveEvery": {
          "description": "Checkpoint cadence (steps).",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "sampleEvery": {
          "description": "Sample-image cadence (steps).",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "quantize": {
          "description": "8-bit mixed precision — needed to fit Flux on 24GB.",
          "type": "boolean"
        }
      }
    },
    "device": {
      "description": "action:\"start\" — GPU selector, default cuda:0.",
      "type": "string"
    },
    "target": {
      "default": "local",
      "description": "action:\"start\" — 'local' = docker on this rig; 'pod' = pod-native over ssh on a RunPod pod.",
      "type": "string",
      "enum": [
        "local",
        "pod"
      ]
    },
    "pod_id": {
      "description": "action:\"start\" — RunPod pod to train on (target 'pod'). Default: the connector's currently connected/watched pod.",
      "type": "string"
    },
    "deliverTo": {
      "default": "both",
      "description": "action:\"start\", pod jobs only: where the finished LoRA lands.",
      "type": "string",
      "enum": [
        "pod",
        "local",
        "both"
      ]
    },
    "model_path": {
      "description": "action:\"start\" — override the base model path AS THE TRAINER SEES IT (pod path for target 'pod', container path for 'local') — e.g. a pre-uploaded local HF snapshot dir when the default HF repo id is gated/unreachable.",
      "type": "string"
    },
    "keep_outputs": {
      "description": "action:\"delete\" — keep the job's output dir (checkpoints/samples) and delete only the record.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
upload_imagePut a file where ComfyUI (or cloud storage) can read it. Driven by the `action` parameter: - action:"image" — Upload a local image file to the connected ComfyUI's input/ directory via the HTTP /upload/image endpoint so it can be referenced in LoadImage nodes. Works for both local and remote ComfyUI. Nested filenames that LoadImage does not enumerate are re-registered at the input root; the returned filename is the one a LoadImage combo can select. - action:"video" — Upload a local video file (.mp4, .mov, .webm, .avi, .mkv, .m4v) to the connected ComfyUI's input/ directory via the HTTP /upload/image endpoint for use in video-loading nodes such as VHS_LoadVideo (ComfyUI-VideoHelperSuite). Works for both local and remote ComfyUI. Returns the stored filename. - action:"audio" — Upload a local audio file (.wav, .mp3, .flac, .ogg, .m4a, .aac) to the connected ComfyUI's input/ directory via the HTTP /upload/image endpoint for use in audio-conditioned workflows (e.g. LoadAudio). Works for both local and remote ComfyUI. Returns the stored filename. - action:"stage" — Stage an EXISTING ComfyUI output (or temp/preview) as an INPUT so the next stage's loader (LoadImage / VHS_LoadVideo / LoadAudio) can read it. This is the CORRECT way to chain a multi-stage pipeline (e.g. Krea2 image → LTX video → WAN extend): it fetches the output's bytes from the server via /view and re-registers them as an input via /upload/image — the same endpoints get_image and the uploads above use. Because it goes entirely through the server API, it works even when ComfyUI was launched with a CUSTOM input/output directory. Do NOT instead copy the output file or guess a filesystem `input/` path — the server's input dir may be custom and it will reject the file ("Invalid image file"), wasting the render. Pass an existing output reference ({ filename, subfolder?, type? }); the media kind (image/video/audio) is inferred from the extension unless you set `kind`. Nested video as_filename values are staged at the input root because VHS_LoadVideo lists only top-level files. Returns { filename, subfolder, type: "input", kind } — drop `filename` into LoadImage / VHS_LoadVideo / LoadAudio combo widgets. VHS_LoadVideoPath needs the returned filesystem path, not that combo filename ("Invalid file path" otherwise). - action:"output" — Upload a generated ComfyUI output to CLOUD storage (this is the only action that sends bytes off the machine). Source can be asset_id or a local path under COMFYUI_PATH/output. Destination can be S3, Azure Blob, HTTP PUT, or HuggingFace via the hf CLI.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "image",
        "video",
        "audio",
        "output",
        "stage"
      ],
      "description": "What to upload and where. \"image\"/\"video\"/\"audio\" send a LOCAL file (`source_path`) to ComfyUI's input/ directory; \"stage\" re-registers an EXISTING server-side output (`filename`) as an input; \"output\" ships a generated output to cloud storage (`destination`)."
    },
    "source_path": {
      "description": "Absolute path to the local file to upload. REQUIRED for actions \"image\", \"video\" and \"audio\".",
      "type": "string"
    },
    "filename": {
      "description": "Two meanings, one per action. actions \"image\"/\"video\"/\"audio\" — OPTIONAL override for the filename in ComfyUI's input/ directory (auto-detected from source_path if omitted). A path prefix (e.g. assets/clip.mp4) places the upload in that SUBFOLDER of input/ — \"..\" is refused — and the returned filename reference includes the subfolder when the loader enumerates it. action:\"image\" verifies the name against LoadImage /object_info and, if a nested path is stored but not listed, returns a verified root filename instead. action:\"stage\" — REQUIRED filename of the EXISTING output/temp asset to re-register (from get_history or get_image action:\"list_outputs\"), e.g. LTX_video_00001.mp4; its destination name override is `as_filename`, not this field.",
      "type": "string"
    },
    "subfolder": {
      "description": "action:\"stage\" — subfolder the source asset currently lives in, if any.",
      "type": "string"
    },
    "type": {
      "description": "action:\"stage\" — source directory the asset lives in: output (default) or temp (previews).",
      "type": "string",
      "enum": [
        "output",
        "temp"
      ]
    },
    "kind": {
      "description": "action:\"stage\" — force the media kind instead of inferring it from the file extension.",
      "type": "string",
      "enum": [
        "image",
        "video",
        "audio"
      ]
    },
    "as_filename": {
      "description": "action:\"stage\" — override the filename it is registered under in the input/ directory (defaults to the source filename).",
      "type": "string"
    },
    "asset_id": {
      "description": "action:\"output\" — registered asset id from a completed job. Provide exactly one of asset_id or path.",
      "type": "string"
    },
    "path": {
      "description": "action:\"output\" — path to a generated output under COMFYUI_PATH/output. Provide exactly one of asset_id or path.",
      "type": "string"
    },
    "destination": {
      "description": "action:\"output\" — REQUIRED. Exactly one upload destination.",
      "type": "object",
      "properties": {
        "s3": {
          "type": "object",
          "properties": {
            "bucket": {
              "type": "string",
              "minLength": 1,
              "description": "Destination S3 bucket"
            },
            "prefix": {
              "description": "Optional object key prefix",
              "type": "string"
            },
            "async": {
              "description": "Accepted for API compatibility; uploads complete before the tool returns.",
              "type": "boolean"
            }
          },
          "required": [
            "bucket"
          ]
        },
        "azure": {
          "type": "object",
          "properties": {
            "container": {
              "type": "string",
              "minLength": 1,
              "description": "Destination Azure Blob container"
            },
            "blob_prefix": {
              "description": "Optional blob name prefix",
              "type": "string"
            }
          },
          "required": [
            "container"
          ]
        },
        "http": {
          "type": "object",
          "properties": {
            "url": {
              "type": "string",
              "format": "uri",
              "description": "HTTP(S) URL to PUT the output file to"
            }
          },
          "required": [
            "url"
          ]
        },
        "hf": {
          "type": "object",
          "properties": {
            "repo": {
              "type": "string",
              "minLength": 1,
              "description": "HuggingFace repo in owner/name format"
            },
            "repo_type": {
              "description": "Repo type; defaults to model",
              "type": "string",
              "enum": [
                "model",
                "dataset",
                "space"
              ]
            },
            "path": {
              "description": "Optional path prefix inside the repo",
              "type": "string"
            }
          },
          "required": [
            "repo"
          ]
        }
      }
    }
  },
  "required": [
    "action"
  ]
}
visualize_workflowDRAW a diagram of, or convert, workflow JSON you PASS IN (a JSON string or object) — it does NOT read the user's live canvas, so for 'show me what's on the canvas' / the CURRENTLY-OPEN graph use panel_graph_outline instead. Driven by the `action` parameter: - action:"render" — Mermaid flowchart of the whole graph: nodes grouped by category, connections labeled by data type. - action:"render_hierarchical" — the same graph SECTIONED rather than flat, which is what you want past ~20 nodes. `view` picks a compact overview, one section in detail, a text listing, or an AI-oriented structured summary. - action:"mermaid" — the INVERSE of render: a Mermaid flowchart back into executable API-format workflow JSON, wired from /object_info schemas. - action:"to_dsl" — API-format JSON into the compact, human/LLM-readable authoring DSL: `key <- nodeId.outputIndex` for connections, `key = <JSON>` for literals. Round-trips losslessly. (Experimental.) - action:"from_dsl" — that DSL back into executable JSON, plus advisory wiring warnings when ComfyUI is reachable (the conversion succeeds either way). (Experimental.)
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "render",
        "render_hierarchical",
        "mermaid",
        "to_dsl",
        "from_dsl"
      ],
      "description": "Which rendering/conversion to perform. \"render\", \"render_hierarchical\" and \"to_dsl\" require `workflow`; \"mermaid\" requires `mermaid`; \"from_dsl\" requires `dsl`."
    },
    "workflow": {
      "description": "ComfyUI workflow JSON (as a JSON string or object; API or UI format is auto-detected). REQUIRED for action:\"render\", action:\"render_hierarchical\" and action:\"to_dsl\" — \"to_dsl\" expects API format (node ID -> {class_type, inputs}).",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        }
      ]
    },
    "show_values": {
      "default": true,
      "description": "action:\"render\" / action:\"render_hierarchical\" — Include widget values (seed, steps, cfg, etc.) in node labels (detail view only, for the hierarchical action).",
      "type": "boolean"
    },
    "direction": {
      "description": "action:\"render\" / action:\"render_hierarchical\" — Flowchart direction: LR (left-to-right) or TB (top-to-bottom). Default LR for \"render\" and for the hierarchical detail view, TB for the hierarchical overview.",
      "type": "string",
      "enum": [
        "LR",
        "TB"
      ]
    },
    "view": {
      "default": "overview",
      "description": "action:\"render_hierarchical\" — overview: compact diagram with sections as summary nodes; detail: full diagram for one section; list: text summary of all sections; summary: structured text optimized for AI ingestion with node IDs, key settings, virtual wires, and full connection graph",
      "type": "string",
      "enum": [
        "overview",
        "detail",
        "list",
        "summary"
      ]
    },
    "section": {
      "description": "action:\"render_hierarchical\" — Section name to show in detail view (required when view=detail). Use view=list to see available section names.",
      "type": "string"
    },
    "mermaid": {
      "description": "action:\"mermaid\" (REQUIRED) — Mermaid flowchart text (with or without ```mermaid code fence). Nodes should use ComfyUI class_type names as labels. Connections should be labeled with data types (e.g., -->|MODEL|).",
      "type": "string"
    },
    "dsl": {
      "description": "action:\"from_dsl\" (REQUIRED) — Workflow DSL text",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
workspaceInspect and manage ComfyUI workspaces (local installs). Driven by the `action` parameter: - action:"get" — Report the active ComfyUI workspace (mirrors `comfy-cli which`): the local installation path being used (from COMFYUI_PATH or auto-detection), the source of that path, any persisted default workspace, and the resolved API target the MCP server talks to. - action:"set_default" — Persist a default ComfyUI workspace path to the MCP config file (mirrors `comfy-cli set-default`). The value is stored under the OS config dir (e.g. ~/.config/comfyui-mcp/workspace.json) and reported by action:"get"/action:"list". Does NOT change the live API target. `path` is REQUIRED, e.g. {action:"set_default", path:"/opt/ComfyUI"}. - action:"list" — List known/auto-detected ComfyUI installations on this machine. Scans common install locations across macOS, Linux, and Windows and marks which one is active and which is the saved default.
Input schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "get",
        "set_default",
        "list"
      ],
      "description": "Which workspace operation to perform. \"get\" and \"list\" take no other parameters; \"set_default\" requires `path`."
    },
    "path": {
      "description": "action:\"set_default\" — REQUIRED absolute path to a ComfyUI installation directory to remember as the default workspace.",
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "action"
  ]
}

Resources 0

  • None observed.

Resource templates 0

  • None observed.

Prompts 0

  • None observed.

Remote endpoints

EndpointTransportAuthenticationHealthObserved
No verified remote endpoint is linked.

ComfyUI MCP Server questions

How do I install ComfyUI MCP Server?

Install the selected package version with: npm install --save-exact comfyui-mcp@0.52.198

What tools does ComfyUI MCP Server provide?

ComfyUI MCP Server exposed 41 tools during independent protocol observation, including apply_manifest, apps, batch, bisect, calculate, call_tool, clear_vram, comfy_cli, and others.

Is ComfyUI MCP Server secure?

The selected current version does not yet have completed public verification. Unknown does not mean clean or vulnerable.

Explore related MCP server guides

Curated product and capability guides containing this catalog record.

Official vs Community MCP Servers

Let’s talk about MCP security.

Share your details and our security team will contact you.