Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
COMFYUI_LANGNoLanguage for windows and installer. Leave empty to auto-detect.
COMFYUI_ROOTYesRoot folder of the ComfyUI installation.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
comfy_statusA

Check whether ComfyUI is running and report GPU/VRAM and queue state.

comfy_startA

Launch the portable ComfyUI instance.

Args: wait: block until the HTTP API answers (up to COMFYUI_STARTUP_TIMEOUT).

comfy_stopA

Stop the ComfyUI process that this server started, and everything under it.

A ComfyUI you launched yourself is not touched - ownership is the test, not whether something is answering the port.

This reaches a ComfyUI started before the server was last restarted as well: ownership is recorded on disk and taken back at startup, so an instance orphaned by a crash is still stoppable here.

restart_comfyA

Restart ComfyUI and wait for it to answer again.

What a newly installed or edited node needs: nodes.py imports every pack once at startup and nothing re-reads them, so a pack added while ComfyUI runs is simply absent from /object_info. Changed extension JavaScript is the other case and needs only reload_workspace - a restart there is minutes spent on a page reload.

Two mechanisms, picked by who owns the process. One this server started is stopped and relaunched through COMFYUI_LAUNCH_SCRIPT. One you started yourself is asked to re-exec itself, through the bridge node - which is the only way to reach it at all, and has the advantage of coming back on the exact command line and environment it already had, rather than on this server's guess at them. That path therefore needs the node installed.

Schemas and model directories are dropped, since a restart is precisely what makes them stale. A tab that was connected reconnects on its own and is waited for; the workflow on its canvas survives, being the browser's, not ComfyUI's.

Args: wait: seconds to wait for ComfyUI to come back. force: restart even with jobs running or queued, throwing that work away.

get_comfy_logA

Read ComfyUI's console - the Python side, where import and dependency failures land.

This is the terminal ComfyUI is running in, not the browser. It is where a custom node says it could not import, where a missing package is named, and where a traceback from inside a node ends up. Nothing else in this server can see any of it: a node that failed to load simply does not appear in /object_info, which looks identical to a node that was never installed.

Args: lines: how many of the most recent matching lines to return; 0 for all of them. level: keep only this severity and above (DEBUG/INFO/WARNING/ERROR/CRITICAL). Untagged output - third-party packs printing directly - is dropped by this. search: keep only lines containing this text, case-insensitive. regex: treat search as a regular expression instead.

open_workspaceA

Open ComfyUI in a browser, so the workspace tools have a tab to talk to.

The bridge needs a page open - with none, every workspace_* tool fails with no_workspace and the only fix is a human opening one. This is that fix.

It waits for the tab to register itself rather than firing and returning, because the caller's next move is a workspace tool: coming back before the page has loaded its JavaScript would just fail again, for a reason that no longer has anything to do with what went wrong.

A tab that is already connected is left alone and reported - a second one would work, but it becomes the preferred client and the user's own tab silently stops being the one that gets edited. Pass force to open anyway.

Args: wait: seconds to wait for the tab to connect. 0 returns as soon as the browser has been handed the URL. force: open a tab even when one is already connected.

workspace_statusA

Check whether the live ComfyUI workspace can be reached.

The workspace is the workflow open in the browser. Reaching it needs two things beyond a running ComfyUI - the bridge node installed and a tab connected - and this reports which one is missing. Call it before the other workspace_* tools when they fail, and to find client_id values when several tabs are open.

get_console_logA

Read the browser console - the frontend half, where extension failures land.

The companion to get_comfy_log, and it answers a different question. A node has two halves: a Python class ComfyUI imports, and often a JavaScript extension that gives it its widgets and menus. When the JavaScript half fails, the Python half still registers, so the node appears in /object_info and on the canvas and merely behaves wrongly - which is the one failure nothing else here can see.

failed_extensions is the direct answer to that: the frontend catches an extension's import error and only console.errors it, so this is the only place it is recorded at all.

Needs a connected tab, and only sees what was logged since that tab loaded - a reload starts the record over.

Args: lines: how many of the most recent matching entries to return; 0 for all. level: keep only this severity and above (DEBUG/INFO/WARNING/ERROR/CRITICAL). search: keep only entries containing this text, case-insensitive. regex: treat search as a regular expression instead. client_id: which tab to ask, when several are open.

get_workspace_graphA

Read the workflow currently open in the browser, unsaved edits included.

This is the one view of a workflow that no file and no HTTP endpoint can give: what the user is actually looking at. Use it to answer questions about the graph on screen; use describe_workflow for the files in the workflows directory.

The summary reports selected - the nodes and groups the user has clicked on. Treat it as them pointing: when a request says "this one" or "these", that is which ones, and it beats guessing from a title. set_workspace_selection points back the other way.

A large workflow does not fit in one answer at any detail that includes its wiring, so the report steps down a level at a time until it does and says so in reduced. When that happens the next move is only - outline the whole graph, then ask again about the handful of nodes that matter. Each node in a subset also carries feeds, the nodes reading from it, so a subset can be walked downstream as well as up.

Args: format: "summary" for a structured report - nodes, links, groups and a list of issues (missing node types, muted or bypassed nodes, unconnected required inputs). "ui" for the raw graph as a Save would write it. "api" for the API-format prompt, the same JSON that Export (API) and run_workflow use - only the frontend can produce it, which is why it is available here and nowhere else in this server. scope: "root" for the top level of the workflow, "active" for the subgraph on screen, "all" to descend into every subgraph. On a workflow built from subgraphs "root" is a handful of boxes and everything inside them is invisible, so reach for "all" when the question is about the whole thing. Nested nodes come back with path ids - 98:12 is node 12 inside subgraph node 98 - the same shape the API format and progress events use. Only "summary" descends; "ui" and "api" already cover the lot. detail: how much to say about each node. "full" is everything including widget values; "links" drops the widgets but keeps the wiring and positions; "outline" is one line per node - type, title, and how many links go in and out. Only a ceiling: a report over COMFYUI_GRAPH_MAX_CHARS is reduced further whatever was asked for. only: report just these node ids, at the detail asked for, however big the graph is. Pass "selected" for whatever the user has clicked on. client_id: which tab to ask; defaults to the most recently focused one. workspace_status lists them.

save_workspaceA

Write the workflow open in the browser to a file.

This is how to get at a graph too large to fit in one answer: the reply is a path and a few numbers, and the file can then be read in slices or searched like any other. It is also how an unsaved canvas becomes something that survives the tab being closed.

Args: name: file name, with or without .json. format: "ui" writes exactly what ComfyUI's own Save writes - positions, groups, titles, collapsed state - into the export directory, and it can be opened in ComfyUI again. "api" writes the API-format prompt into the workflows directory, where run_workflow and describe_workflow find it. The two are not interchangeable: UI format keeps the layout and cannot be run, API format is runnable and has no layout at all. scope: "root" for the whole workflow, "active" for the subgraph on screen. overwrite: replace the file if it already exists. client_id: which tab to ask; defaults to the most recently focused one.

ask_workspaceA

Ask the person at the ComfyUI screen a question, and wait for their answer.

This is the one tool whose answer comes from a human rather than from the graph. Reach for it when the next step turns on something only they know - which of two results they preferred, what the subject of a prompt should be, whether a value looks right - instead of guessing and building on the guess.

The question goes in a panel below the canvas, not over it. That matters for the question most worth asking - "look at this and tell me" - because a modal dialog hides the canvas and closes on a click beside it, so looking is what cancels it. In the panel the graph stays visible and usable, and they can pan around before answering. modal=True puts it back in a dialog for a question that should interrupt rather than wait to be noticed.

It costs them an interruption either way, so it is worth one question rather than three: only one can be waiting at a time, on either surface, and a second call while one is open is refused rather than queued.

choices turns it into a pick, which is the cheaper question to answer. One click instead of typing, and the answer comes back as one of the strings you offered rather than something to parse - with choice_index beside it, because two options can read alike once phrased and an index cannot. Up to six are drawn as buttons and more become a dropdown. Choices need the panel: a dialog has no way to offer them, so modal=True with choices is refused.

allow_other adds a free-text box beside the buttons, for when the list may not cover it; the answer then comes back with choice_index: -1. Leave it off when the options really are exhaustive - it is one more thing on screen.

Three outcomes, and they mean different things. answer is what they typed or picked. dismissed means they closed the box without answering - easy to do by accident, since clicking beside it counts, so it is not a "no" and not permission to carry on regardless. timed_out means the wait ran out with the question still on screen; they may yet answer it, and until they do, another question cannot be asked.

Args: question: what to ask. Shown as the body of the dialog. title: heading above it. Defaults to "Question". default: text the input starts with, for when there is an obvious answer. placeholder: grey hint inside an empty input, or in the allow_other box. choices: options to offer instead of a text box. Each is a button (or an entry in a dropdown past six of them) and answering picks one. allow_other: add a free-text box beside the choices, answering with choice_index: -1. Ignored when there are no choices. modal: ask in a dialog over the canvas instead of the panel below it. Harder to miss, but it hides the graph and a stray click dismisses it, so it suits a question whose answer needs nothing on screen. seconds: how long to wait, 1 to 600. Keep it well under the per-call timeout your MCP client enforces, or the client gives up first. client_id: which tab to ask; defaults to the most recently focused one.

confirm_workspaceA

Ask the person at the ComfyUI screen a yes/no question, and wait.

The same interruption as ask_workspace, for when the answer is a decision rather than a value - before something slow, or something with no undo.

Asked in the panel below the canvas by default, so the graph stays visible; modal=True puts it in a dialog over it. In the panel all three answers always have a button. In a dialog they do not:

confirmed has three values, and which are reachable depends on kind. True is agreement and null is dismissal - they closed the box, which says only that they did not engage with the question, and must not be read as either answer. A plain false needs a deny button, and ComfyUI draws one for dirtyClose and nothing else: every other kind offers Cancel and Confirm, so Cancel answers null and false never arrives. Ask for dirtyClose when telling "no" from "never mind" actually matters.

Args: question: what to ask. title: heading above it. Defaults to "Confirm". hint: smaller helper text under the question. deny_label: caption for the deny button. Only kind="dirtyClose" has one, so this is refused with any other kind rather than silently ignored. modal: ask in a dialog over the canvas instead of the panel below it. kind and deny_label describe ComfyUI's dialog and apply only then; the panel always offers Yes, No and Dismiss. kind: which of ComfyUI's confirmation dialogs to use - "default", "delete" and "overwrite" style the confirming button for a destructive act, "dirtyClose" is the three-way one, "reinstall" is ComfyUI's own. Only the wording and the buttons differ; nothing here acts on the answer. seconds: how long to wait, 1 to 600. client_id: which tab to ask; defaults to the most recently focused one.

undo_workspaceA

Undo the last edit in the browser, exactly as Ctrl+Z would.

Every workspace edit is bracketed so that one call is one undo step - a batch of three values set together comes back in one press. This is that press, which is what makes a wrong edit cheap to take back rather than something to reconstruct by hand.

The history is the user's, not this server's. Their own edits sit on the same stack, interleaved with ours in the order they happened, and nothing distinguishes them. One step back is almost always ours; several steps back is taking back whatever was there, theirs included. Undo one, look, undo again.

Args: steps: how many to take back, 1 to 50. Pass 0 to read the depth without moving - the only way to tell an undo that will do something from one that will silently do nothing, since an empty history is not an error. redo: step forward instead. A redo history exists only until the next edit, which clears it. client_id: which tab; defaults to the most recently focused one.

reload_workspaceA

Reload the ComfyUI browser tab, and wait for it to come back.

This is the fix for exactly one thing: changed extension JavaScript. The bridge's own JS, and every node pack's, is served from disk on each page load and never re-read otherwise, so editing it takes a reload and nothing else - restarting ComfyUI would cost minutes and achieve the same thing by accident. Installing or upgrading a node is the opposite case and needs restart_comfy, because Python packs are imported once at startup.

It waits for the tab to register again rather than firing and returning, for the reason open_workspace does: a page part-way through loading fails exactly like no page at all.

The browser can refuse. ComfyUI asks "Leave site?" when a workflow has unsaved edits, and that dialog waits for a human - nothing here can dismiss it. So a tab with edits is refused by default; force reloads anyway, having first written the canvas to the export directory so nothing can be lost. Opening a second tab is the other way round the problem: a new tab loads the current JavaScript without disturbing this one.

Args: wait: seconds to wait for the tab to reconnect. 0 returns immediately. force: reload even though the browser may put a confirmation on screen. client_id: which tab, when several are open. Defaults to the focused one.

load_workspaceA

Open a saved workflow in the browser, as a tab in the ComfyUI window.

The other half of save_workspace, and the way to put a workflow file under the workspace tools: load it, edit it with them, save it back. Without this they can only reach whatever the user happened to have open.

Both formats work and neither has to be named - a file in workflows/ is API format, one in exports/ is UI format, and this reads which it is. UI format keeps the layout it was saved with; API format has none, so ComfyUI lays it out itself and the result is tidy rather than familiar.

It opens a tab rather than replacing the one on screen. ComfyUI reuses a tab only when its own workflow store already knows the name, and that store does not sync from this server's workflows directory - so a file usually arrives in a new tab marked unsaved, with the workflow that was on screen still open in its own. Loading that same file again reuses the tab it made the first time, and that one is replaced. The reply reports the tabs and whether one was added; switch_workspace_tab moves between them afterwards.

A name ComfyUI already knows is refused. It resolves the name against its own saved workflows rather than against the directory the file came from, so a file whose name matches one of the user's workflows would fill that workflow's tab instead of opening one - leaving it looking edited, one Ctrl+S from overwriting their work. Rename the file, or pass force when replacing that workflow is the actual intent.

This is not undoable. ComfyUI resets the undo history when a workflow is loaded, so Ctrl+Z will not bring the previous canvas back. That is what the backup is for, and it is the reused-tab case it covers: the canvas is written to the export directory first and the reply names the file.

Args: name: the file, with or without .json. Looked for in the workflows directory first, then the export directory; an absolute path also works. backup: write the current canvas to the export directory before replacing it. Leave it on unless the canvas is known to be worth nothing. force: load even when ComfyUI already has a saved workflow of this name, taking over its tab. Only when replacing that workflow is meant. client_id: which tab to load into; defaults to the most recently focused one.

screenshot_workspaceA

Photograph the ComfyUI canvas: what the workflow looks like, not what it says.

This answers the questions a graph dump cannot - whether the layout reads as a mess, which boxes overlap, where a link crosses the whole screen, what the user means by "that one over there". After a layout change it is the only way to check the result rather than assume it.

It is a picture of the canvas, and that has a hard edge: prompts, image previews, markdown notes and audio players are HTML drawn over the canvas and are not in it. An empty-looking prompt box in the picture says nothing about the prompt - get_workspace_graph is what reads values. The report says how many such widgets the graph has.

Always the graph on screen. To photograph a subgraph, navigate_workspace into it first; the report names which graph it is.

Args: fit: what to frame. "graph" for the whole workflow, "view" for the viewport exactly as the user has it, "selected" for what they have clicked on, or a list of node ids to frame those. A whole large graph is legible only as a shape - for reading titles, frame a handful of nodes. max_edge: longest edge in pixels, 256 to 4096. Bigger reads better and costs more; it does not make a wide graph legible, only a small subset. format: "png" keeps the text crisp and is the right answer for a diagram. "jpeg" and "webp" are smaller and blur it. client_id: which tab to ask; defaults to the most recently focused one.

set_workspace_valuesA

Set widget values, node properties and on-screen labels in the open workflow.

This edits the live canvas, exactly as if the values had been typed in. The change lands on the frontend's undo stack as a single step, so one Ctrl+Z in the browser takes back the whole call - which is the reason to pass several values at once rather than one per call, and why properties live here rather than in a tool of their own.

Nothing is written unless every key validates, so a rejected call leaves the graph as it was. Numeric ranges are enforced; a combo value outside the listed options is reported as a note and written anyway, because a node's option list is not a whitelist - ComfyUI decides.

Args: values: {"<node_id>.<widget>": value}, e.g. {"37.megapixels": 1.5}. Node ids and widget names come from get_workspace_graph. properties: {"<node_id>.<property>": value} - the second, separate set of settings a node carries, the ones ComfyUI edits through the Properties Panel on its context menu. Some nodes keep their whole configuration there and their widget values mean nothing without it. A separate argument because the two namespaces can collide: one node here has a delimiter in both. Written through the node's own setProperty, so a pack that rebuilds its widgets in response gets the chance to. labels: {"<node_id>": {"title": ..., "inputs": {...}, "outputs": {...}, "widgets": {...}}} - the text drawn on a node rather than in it. This is what makes a workflow readable in another language: a graph written in Chinese keeps its headings in node titles, slot labels and widget rows, and no widget value reaches any of them. Addressed by the stable name from get_workspace_graph, and null or "" clears an override so the name shows through again - which is how a translation is taken back off. widgets is separate from inputs because a converted widget has both, and only the widget's own label changes the row on screen - but that one is not saved with the workflow and lasts until the page reloads, which the change log says each time. Keyed by node id alone, because a node has one title and several sets of names, and "1.title" would collide with a widget called title. Do not try to write localized_name: that one is ComfyUI's own translation for the current locale and is regenerated on load. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

set_workspace_node_modesA

Mute or bypass nodes in the workflow open in the browser.

Muted ("never") stops a node producing anything; bypassed passes its inputs straight through to whatever it feeds, which is how a branch is taken out without unwiring it. Both are how a workflow gets narrowed to the part being worked on.

Like set_workspace_values, the whole call is one undo step and nothing changes unless every entry is valid.

Args: modes: {"<node_id>": "always" | "muted" | "bypassed"}. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

navigate_workspaceA

Move the ComfyUI canvas into a subgraph, back out one level, or to the top.

The other workspace tools take scope="active", which means whatever graph is on screen - so this is how to point them inside a subgraph. For reading alone it is usually unnecessary: get_workspace_graph(scope="all") descends without moving the user's view.

This changes what the user is looking at and nothing about the workflow, so it is not on the undo stack - Ctrl+Z would otherwise mean two different things.

Args: to: "root" for the top level, "up" for one level out, or the id of a subgraph node in the graph currently on screen to go into it. client_id: which tab to move; defaults to the most recently focused one.

switch_workspace_tabA

List the workflow tabs open in the ComfyUI window, and switch between them.

These are the tabs along the top of ComfyUI: open workflows, exactly one of which is on screen. They are not browser tabs - one browser tab holds all of them, and client_id plus workspace_status is where that sense of the word lives.

With no to it reports and moves nothing, which is also how to find out what is open before naming one. Every other workspace tool acts on the workflow that is on screen, so this is what points them at a different one.

Switching does what clicking the tab does, and no more: the canvas is reloaded from that workflow's own stored state. Unsaved edits in the tab being left behind are not lost - they belong to that workflow, which is why a tab can report modified while a different one is on screen.

"new" opens a blank workflow and switches to it, which is the same command the + button on the tab bar runs. That is the way to start something from nothing without disturbing what the user already has open.

Args: to: which tab - an index from a previous call, its path or its filename, or "next", "previous", "recent" for the one active before this, or "new" for a fresh blank workflow. Empty reports without moving. A name matching two open tabs is refused rather than guessed. force: reload the tab already on screen instead of reporting that it is already there. client_id: which browser tab to ask; defaults to the most recently focused.

close_workspace_tabA

Close one of the workflow tabs open in the ComfyUI window.

The counterpart to switch_workspace_tab, and the way to tidy up after a run of loads - each one opens its own tab, and they stay until something closes them.

Nothing here is written to disk and nothing can be undone. A closed workflow is out of the tab bar; if it had unsaved changes, they are gone. So a tab with unsaved changes is refused unless force says otherwise, and only ComfyUI's own Save in the browser clears that flag - save_workspace writes a copy to disk and leaves the workflow just as modified.

Closing the tab on screen moves to a neighbour first, so the canvas never ends up showing a workflow that is no longer open. The last remaining tab is refused: closing it would leave the window with nothing.

Args: tab: which one - an index from switch_workspace_tab, its path, or its filename. Empty closes the tab currently on screen. A name matching two open tabs is refused rather than guessed. force: close even when the tab has unsaved changes, losing them. client_id: which browser tab to ask; defaults to the most recently focused.

promote_workspace_inputsA

Expose an inner node's inputs on the face of the subgraph that holds it.

A subgraph with nothing promoted is a sealed box: the values that drive it can only be reached by going inside, and nothing outside can be wired to it. This is what ComfyUI offers as "Promote widget" on a widget's context menu, and it is the difference between a subgraph that is usable from the outside and one that merely hides its contents.

Both kinds of input take the same path. A widget row (steps, cfg) becomes a widget on the subgraph node's face; a plain socket (image, model) becomes a socket that can be wired to. Name them the same way either way.

The id names both ends. 98:12 is node 12 inside subgraph node 98, and the input is exposed on 98. There is no separate "which subgraph" argument and no need to navigate inside first. To carry something further out, ask again with the shorter id - each level is its own step, and its own entry in the reply.

One call is one Ctrl+Z. Nothing is written unless every named input resolves, so a typo refuses the batch rather than half-applying it.

Args: promote: which inputs to expose, as {":": [names]}. An input already exposed is reported in skipped, not an error. An input already wired to something inside the subgraph is refused, since promoting it would replace that link. demote: which to take off again, same shape. A boundary slot with something wired into it from outside is disconnected rather than removed, so the outer link survives. client_id: which browser tab to act in; defaults to the most recently focused one.

pack_workspace_subgraphA

Wrap nodes and groups into a new subgraph, or dissolve one back out.

ComfyUI's "Convert to Subgraph" and its Unpack counterpart. Folding a stage into a subgraph is how a workflow stops being a wall of nodes: the box keeps its own wiring, and promote_workspace_inputs decides which of its values show on the outside.

It acts on the graph on screen, so packing while inside a subgraph nests one. Ids are the plain local ones a read of that graph reports, not the 98:12 path form - a selection only ever belongs to one graph. Use navigate_workspace first when the nodes are a level down.

A group can be named instead of listing its nodes: it goes in along with everything inside it, which is what selecting one on the canvas amounts to.

One call is one Ctrl+Z, and unpacking happens before packing - so a single call can dissolve a subgraph and re-wrap its parts differently.

Ids do not survive either direction. Packing replaces the nodes with one new node; unpacking hands the contents fresh ids rather than the ones they went in with - measured, 144 and 145 came back out as 158 and 159. Read the graph again afterwards rather than reusing ids from before.

This is not the same as moving a node into a subgraph that already exists. ComfyUI has no operation for that; unpack the subgraph, then pack the parts together with whatever else belongs there.

Args: pack: node and group ids to fold into one new subgraph. Anything that is not one convertible block is refused by litegraph, which says nothing else, so the refusal names that. unpack: subgraph node ids to dissolve, putting their contents back into the graph on screen. A node that is not a subgraph is refused. client_id: which browser tab to act in; defaults to the most recently focused one.

set_workspace_selectionA

Highlight nodes and groups on the canvas, so the user can see which ones you mean.

The write half of what get_workspace_graph reports as selected. Pointing is how people hand work over, and it is worth having in both directions: four node ids in a sentence are hard to check, while four highlighted boxes are not. Use it before an edit the user should agree to, and to answer "which ones?" without a list.

Selecting nothing clears the selection. A selection is not part of the workflow, so this does not go on the undo stack and Ctrl+Z will not take it back.

Args: nodes: node ids to select, as they appear in get_workspace_graph. They are local to the graph on screen - navigate_workspace first for a subgraph, and drop any 98:12 prefix once inside. groups: group ids or titles to select as well. add: add to what is already selected instead of replacing it. centre: move the view to fit the selection. Off by default, since the user may be looking somewhere deliberately. client_id: which tab; defaults to the most recently focused one.

diagnose_workspaceA

Report what is wrong with the workflow open in the browser, worst first.

Reads the live graph and checks it against ComfyUI's own node schemas: node types that are not installed, required inputs with nothing plugged in, links whose types do not match, widget values outside the declared range, and inputs a node no longer has. Muted and bypassed nodes are reported too - they are the commonest reason a workflow "does nothing" while looking fine.

Defaults to scope="all" because a workflow built from subgraphs keeps almost everything that can break inside them; checking only the top level would pass a graph that cannot run.

Each finding names the node and, where there is one, a fix - the other workspace tools are what applies it: set_workspace_links to rewire, set_workspace_values to bring a value into range, set_workspace_node_modes to un-mute, add_workspace_node to replace something missing.

A clean report is not a promise the run will succeed: ComfyUI validates more at queue time, and a value can be legal and still wrong.

Args: scope: "all", "root" or "active" - see get_workspace_graph. refresh_schemas: re-fetch /object_info first. Schemas are cached for the life of the process, so pass this after installing nodes or models. client_id: which tab to inspect; defaults to the most recently focused one.

add_workspace_nodeA

Add a node to the workflow open in the browser, wired up and configured.

Inserting a node is one edit, so it is one call and one Ctrl+Z: the widgets and links go in with it. Doing it in three calls would take three presses to undo one intention, and a failure partway would leave a stray node behind.

Nothing is added unless every value and link validates. Link types are checked against the slots before anything is written, because litegraph refuses a mismatched connection by doing nothing and reporting nothing.

Args: type: the registered node type, e.g. "ImageScale" or "VAEEncodeTiled". find_node_types looks one up, by slot type when the name is not known; an unknown one comes back with near matches rather than a bare refusal. title: the label on the node. Defaults to the type's own. pos: [x, y] on the canvas. Defaults to the middle of the current view, so the user can see what arrived. values: widget values for the new node, {"<widget>": value} - no node id, since it does not have one yet. connect: links to make at the same time, [{"from": ..., "to": ...}] with both ends written "<node_id>.<slot>". The new node is "this", as in {"from": "this.IMAGE", "to": "9.images"}. A slot can be named or given by index. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

remove_workspace_nodesA

Delete nodes from the workflow open in the browser.

The whole batch is one undo step and nothing is removed unless every id exists. The response reports links_lost per node: deleting a node unwires everything attached to it, which is not part of what the caller asked for and cannot be seen from an id alone.

To take a node out of the picture without losing its wiring, prefer set_workspace_node_modes - bypassing passes inputs straight through.

Args: nodes: node ids, from get_workspace_graph. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

set_workspace_linksA

Wire and unwire nodes in the workflow open in the browser.

Both lists are applied in one undo step, disconnects first, so moving a link from one input to another is a single Ctrl+Z rather than a half-wired graph between two calls.

An input holds one link, so connecting to a taken input replaces what was there - the response says what under replaced. Type compatibility is checked before anything is written: litegraph refuses a mismatched connection by doing nothing at all, which would otherwise leave a batch half-applied in silence.

Args: connect: [{"from": "<node_id>.<output>", "to": "<node_id>.<input>"}]. Slots may be named ("8.IMAGE", "9.images") or given by index ("8.0"). disconnect: inputs to clear, ["<node_id>.<input>"]. Only inputs - an output feeds many links, so "which one" would be ambiguous; clear the input end instead. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

set_workspace_layoutA

Move, resize, fold and unfold nodes on the canvas open in the browser.

None of this changes what a workflow does - it runs the same however it is laid out - so this is purely about making a graph readable. The whole batch is one undo step and nothing is written unless every id exists.

Groups follow the nodes they were holding. A group is only a rectangle, and what is "inside" it is whatever falls within it, so moving nodes out from under one would silently empty it; membership is read before anything moves and each affected group is refitted around those same nodes afterwards.

arrange_workspace computes positions rather than taking them, and its answer can be passed straight in here.

Args: positions: {"<node_id>": [x, y]}. Canvas coordinates, y downwards. A pinned node is skipped rather than moved, and reported in skipped - pinning is the author saying "not this one". sizes: {"<node_id>": [width, height]}. A size below what the node needs to draw its widgets is raised to that minimum and reported. collapsed: {"<node_id>": true} to fold a node down to its title bar, false to unfold it. The desired state, not a toggle, so asking for what a node already is does nothing. get_workspace_graph reports the current state per node. refit_groups: refit every group that held one of the changed nodes. Turning this off leaves the boxes where they were, which is what you want when moving a node deliberately out of a group. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

set_workspace_groupsA

Create, edit and delete the group boxes on the canvas open in the browser.

A group is a labelled rectangle drawn behind the nodes that fall inside it. It holds no membership of its own - which nodes are "in" it is decided by where the box is - so creating one around a list of nodes means fitting the box to them, and that is what this does.

Deleting a group takes only the box: the nodes it framed stay exactly where they are. Get the current groups, with their ids and members, from get_workspace_graph.

Args: create: [{"title": ..., "nodes": ["3", "8"]}] - a box fitted around those nodes. "color" takes a palette name ("green", "blue", "pale_blue", ...) or #rrggbb; "padding" is the gap to the nodes, 10 by default. A group with no nodes needs an explicit "bounding": [x, y, width, height] instead. update: [{"group": <id or title>, ...}] with any of "title", "color", "nodes" (refit around these) or "fit": true (refit around whatever it currently holds, after the nodes inside it have moved). remove: groups to delete, by id or title. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

arrange_workspaceA

Lay the workflow out left to right, in the order the data flows through it.

Each node goes as far right as its consumers allow, so a loader sits beside the sampler that reads it rather than in a column of loaders at the far edge, and the nodes in a column are ordered and placed to face what they are wired to. The result keeps the top-left corner it already had, so it lands where the author left it rather than at the origin.

Groups are laid out as groups. A group is a rectangle with no membership - what is in it is whatever falls inside - so arranging a grouped canvas flat scatters each group across the columns and its box stretches to follow, which on a real workflow turned eight tidy groups into overlapping sheets covering everything. So the layout runs inside each group first and then over the blocks they form, and ungrouped nodes travel together as one more block.

It still moves every node it is given, which is the blunt instrument. only is the narrow one: it arranges just those nodes and leaves the rest of the canvas alone - and since the caller has already said which nodes they mean, groups are not consulted in that case.

Args: only: node ids to arrange, leaving every other node where it is. Links to nodes outside the list are ignored, since they cannot place anything. spacing_x: gap between columns, past the widest node in the left one. spacing_y: gap between nodes stacked in one column. origin: [x, y] for the top-left of the result. Defaults to the top-left of what is being arranged, so nothing wanders off. apply: write the positions. False computes and reports them without touching the canvas - the same dict can then be passed to set_workspace_layout. scope: "root" for the top level, "active" for the subgraph on screen. Not "all": a subgraph is a canvas of its own with its own coordinates, so there is no one layout that covers several. Use navigate_workspace to go in and arrange with "active". client_id: which tab to arrange; defaults to the most recently focused one.

align_workspaceA

Line nodes up on a common edge, space them evenly, or both.

Unlike arrange_workspace this never reads a link: it moves the nodes it is given along one axis and changes nothing else about the layout. That is the point - straightening a row of loaders should not rearrange the workflow around them. Reach for this when the graph is already laid out the way the author wants and only looks untidy.

Edges account for how big each node draws, so aligning right lines up the far edges of nodes of different widths rather than their positions, and a collapsed node lines up by its title bar rather than by the size it reports.

Args: nodes: the node ids to align - at least two. From get_workspace_graph. edge: "left", "right", "top", "bottom", "centre_x" or "centre_y". The centre forms use the middle of the whole selection. distribute: "x" or "y" - even out the gaps between the nodes along that axis. The outermost two stay where they are and the rest are shared out between them; gaps rather than centres, since nodes differ in size enough that even centres look uneven. spacing: an exact gap for distribute, in canvas units, instead of filling the space the nodes already span. edge and distribute combine only across axes: aligning tops while spreading horizontally is one intention, aligning lefts while spreading horizontally is two contradictory ones. apply: write the positions. False reports them without touching the canvas; the same dict can be passed to set_workspace_layout. scope: "root" for the whole workflow, "active" for the subgraph on screen. client_id: which tab to edit; defaults to the most recently focused one.

list_workflowsA

List API-format workflow files available in the workflows directory.

describe_workflowA

Report the parameters a workflow accepts, plus its outputs and model files.

Each parameter lists where it is actually written in the graph. Values reached through primitives and switches are resolved automatically, so steps points at the primitive node feeding the sampler rather than the sampler itself.

When ComfyUI is running, each parameter also carries its real type, allowed options and numeric range, taken from the node's own schema.

Args: name: workflow file name, without the .json extension. refresh_schemas: re-read node schemas from ComfyUI. Use after installing models or custom nodes, since schemas are cached.

get_workflow_guideA

Read the instruction file that ships with a workflow.

A graph says which inputs exist, never what belongs in them. Some workflows only work with input in a particular shape - Ideogram 4 wants a JSON caption carrying bounding boxes, not a prose prompt - and that convention lives in a Markdown file named after the workflow. Read it and follow it before calling run_workflow.

Workflows that have one are flagged as guide by list_workflows and describe_workflow.

Args: name: workflow file name, without the .json extension.

describe_nodeA

Look up one node type: its inputs, their valid values, and what it outputs.

Use before wiring a node in with add_workspace_node, or to check allowed combo values (sampler names, schedulers, model files) before passing them to run_workflow. find_node_types is how you get the class_type in the first place.

Args: class_type: the node's class name, e.g. 'KSampler'. full: return the raw /object_info entry instead of the summary. Combo option lists are complete there and can be very large - one node on this install measures 199k characters - so only ask when a truncated option list is actually the problem.

find_node_typesA

Find a node type to add to a workflow, by name or by what it connects to.

Filters are ANDed and all are optional; with none of them this lists what is installed. Each result carries the node's slots, so it is usually enough on its own - reach for describe_node when you need a widget's allowed values.

The type filters answer the question a graph editor actually asks. "What turns a LATENT into an IMAGE" is input_type='LATENT', output_type='IMAGE'; searching for the word 'latent' would never find VAEDecode, whose name and category contain neither word.

Args: search: case-insensitive substring, matched against the node's name, title, category, description, and the search aliases ComfyUI ships - which is why 'latent to image' finds VAEDecode. input_type: only nodes accepting this slot type, e.g. 'IMAGE', 'MODEL'. output_type: only nodes producing it. Wildcard ('*') slots match anything, as they do when the link is drawn, but rank below nodes that name the type. category: substring of the node's category path, e.g. 'upscal', 'loaders'. pack: substring of the pack it came from, e.g. 'kjnodes', 'comfy_extras'. include_deprecated: include nodes ComfyUI marks as superseded. Off by default because something replaced them. Experimental nodes are always included - that flag means new, not unreliable. include_api: include paid cloud API nodes, which need an account. refresh: re-fetch /object_info first. Needed after installing nodes. limit: maximum results. What matched beyond it is still counted.

list_modelsA

List the models ComfyUI can actually load.

Asks ComfyUI itself, so the result honours extra_model_paths.yaml and matches the values a loader node will accept. A plain disk scan does not: model folders are routinely mapped in from elsewhere and ComfyUI/models/ can be almost empty.

Args: folder: which folder to list, e.g. 'loras', 'checkpoints', 'diffusion_models'. Empty lists the available folder names instead. search: case-insensitive substring filter on the file name. limit: maximum number of files to return.

download_modelA

Download a model file into the folder ComfyUI will actually load it from.

Meant for the models a workflow declares on its own loaders: at detail="full" get_workspace_graph reports each node's properties.models as [{name, url, directory}], and directory is this tool's folder. Graphs without that property usually ship a "Model Links" note saying the same thing in prose. Either way, check what is already there with list_models(folder) first and fetch only the rest.

Where the file lands is decided by ComfyUI, not by this server: the directory list honours extra_model_paths.yaml, so it is routinely on another drive entirely, and a model written anywhere else is invisible however right the bytes are. The reply always names the directory it chose.

Interrupted transfers resume: the bytes go to a .part file beside the target and re-issuing the same call continues from where it stopped, so a failure costs the remainder rather than the whole file.

Args: url: direct link to the file, as written in the note. folder: ComfyUI model folder, e.g. 'vae' or 'diffusion_models'. list_models() with no arguments lists the valid names. filename: name to save as. Defaults to the last segment of the URL. A 'subdir/name.safetensors' is allowed; ComfyUI loads those. directory: which of the folder's registered directories to use. Defaults to the first one that exists, which is what ComfyUI's own is_default ordering intends. wait: wait for the download to finish. False returns immediately and the transfer keeps running, so get_download_progress reports it - prefer that for anything large, since a multi-gigabyte file outlasts most tool-call deadlines. overwrite: fetch again even though the file is already there. dry_run: report size, checksum and destination without fetching anything. Worth doing first: a link in a note is often several gigabytes.

get_download_progressA

Report how far a download has got: bytes, percent, speed and an ETA.

silent_for_s is the telling number, as it is for a run: a slow link keeps it small while a dead one lets it grow. A stalled transfer does not need cancelling - the retry logic resumes by itself - so act only on a silence that outlasts COMFYUI_DOWNLOAD_TIMEOUT several times over.

Args: job_id: '/', or just the file name, or empty for the most recent download.

cancel_downloadA

Stop a download that is still running.

What has arrived stays in the .part file, so calling download_model again with the same arguments continues rather than starting over. For a transfer that is merely slow this is counter-productive.

Args: job_id: '/', or just the file name, or empty for the most recent download.

run_workflowA

Run a workflow and return the paths of the files it produced.

Call describe_workflow first to see which parameter names a workflow accepts. Pass seed=-1 to randomise the seed.

Args: name: workflow file name, without the .json extension. params: parameter overrides, e.g. {"prompt": "a red fox", "seed": -1, "steps": 8}. Raw '.' keys are accepted for anything not discovered. wait: wait for the run to finish. When False, returns the prompt_id immediately and the run keeps being watched in the background, so get_progress(prompt_id) reports its steps as they happen. Prefer this for anything slow: waiting blind is what makes a caller mistake a working generation for a hung one. timeout: seconds to wait before giving up on a running job. save_outputs: convert PreviewImage nodes to SaveImage so results are written to output/ instead of the temp folder that ComfyUI clears on restart. free_on_switch: unload models from VRAM before this run when it needs a different set than the previous one AND free VRAM is already below COMFYUI_FREE_VRAM_MIN_FRACTION. Defaults to COMFYUI_FREE_ON_SWITCH. Low free VRAM is normal on its own - ComfyUI keeps models cached - so this deliberately does nothing when there is headroom.

run_workspaceA

Press Queue Prompt in the browser and watch the run from here.

The tab queues its own canvas, unsaved edits and all, so everything reacts the way it does when the button is clicked by hand: nodes light up, progress bars fill, sampler previews appear, and the result lands in the node that produced it. Nothing is written to the workflows directory. Use this when the user is working on a graph in front of them; use run_workflow for a file.

It has to be the tab that queues. ComfyUI addresses execution events to whoever submitted the job, and the frontend only tracks jobs it queued itself, so a graph submitted from here leaves the canvas reporting someone else's run. The events are copied back to this server so progress is still reported; if that copy cannot be set up the run still happens and progress_mirrored says it did not, which is the one case where get_progress goes quiet.

Two consequences of it being the real button. Widget callbacks run, so a seed set to randomize advances on its own - no two runs are alike unless the canvas says so. And the graph is not rewritten on the way out: a PreviewImage stays a PreviewImage and its result lands in temp/, which ComfyUI clears on restart. Change the node itself with set_workspace_values to keep it.

There is no params argument on purpose: set_workspace_values makes the edit, reports what changed from and to, and leaves it as one Ctrl+Z. Folding that into a run would hide an edit to the user's canvas inside a call that reads as read-only, and spend GPU minutes before anyone had seen the change.

Muted and bypassed nodes are dropped when the graph is converted, so a muted SaveImage produces no output at all. get_workspace_graph lists both under issues, which is worth a look when a run finishes with nothing to show.

Args: wait: wait for the run to finish. When False, returns the prompt_id immediately and keeps watching in the background, so get_progress(prompt_id) reports steps as they happen. Prefer this for anything slow - waiting blind is what makes a caller mistake a working generation for a hung one. timeout: seconds to wait before giving up on a running job. client_id: which tab to run; defaults to the most recently focused one.

get_resultC

Fetch the outputs of a previously submitted prompt.

get_progressA

Report how far a run has got: step, percent, elapsed time and an ETA.

A generation takes minutes, and nothing about a long silence distinguishes real work from a hang - so check here instead of guessing. silent_for_s is the telling number: it stays small while the job advances, and only a large and growing one means something is actually wrong. Loading a model produces no steps for a minute or more, which is normal and shows up as working.

Interrupting and re-running costs more than waiting: the models are already resident and a re-run pays for them again.

Args: prompt_id: which run to report on. Defaults to the most recent one.

get_queueA

Show what ComfyUI is currently running and what is queued behind it.

Running items carry their progress when this server started them; get_progress gives the same detail for one run.

interruptA

Interrupt the job ComfyUI is currently executing.

For a run that is merely slow this is counter-productive - the steps already computed are lost and the models get reloaded. Check get_progress first.

free_memoryA

Ask ComfyUI to unload models and free VRAM.

show_imageA

Return a generated image so it can be viewed, downscaled to keep it small.

Args: path: a path from run_workflow's outputs. max_edge: longest edge in pixels after downscaling.

upload_input_imageA

Upload a local image into ComfyUI's input folder so workflows can load it.

Returns the name to pass to a LoadImage node.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pytraveler/local-comfyui-mcp'

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