Repository
comfy-mcp
Local MCP server for ComfyUI — run your local ComfyUI from AI agents
- Was
- Is now
- distribution / import package comfy-local-mcp / comfy_local_mcp
- comfy-mcp / comfy_mcp
- console script comfy-local-mcp (the "command" in your client config)
- comfy-mcp
- env var COMFY_LOCAL_MCP_DEBUG_LOG
- COMFY_MCP_DEBUG_LOG
Specs
- WasIs now
- distribution / import package comfy-local-mcp / comfy_local_mcpcomfy-mcp / comfy_mcp
- console script comfy-local-mcp (the "command" in your client config)comfy-mcp
- env var COMFY_LOCAL_MCP_DEBUG_LOGCOMFY_MCP_DEBUG_LOG
- failure-log directory leaf comfy-local-mcp/comfy-mcp/
- MachineGuidance
- Discrete GPU, ≥ 24 GB VRAM≥ 24 GB
- Discrete GPU, 8 GB to under 24 GB VRAM8 GB to under 24 GB
- < 8 GB VRAM, or the user confirming there is no GPU< 8 GB
- Apple Silicon , ≥ 32 GB unified memoryApple Silicon
- Apple Silicon , under 32 GB unified memoryApple Silicon
- local_checkMeans
- {"checked": true, "runnable": true, …}Every node class and input option the template uses exists in your install. Necessary, not sufficient — validate_workflow 's documented blind spots still apply.
- {"checked": false, "reason": …, …}The comparison could not be made — almost always because ComfyUI isn't running, so there is no live catalog to compare against. This is not a verdict about the template.
- COMFYUI_URL (+ COMFYUI_HOST / COMFYUI_PORT )COMFY_LOCAL_URL
- How it actsHow it acts
- Use it forUse it for
README
Comfy MCP
**Drive your own [ComfyUI](https://github.com/comfyanonymous/ComfyUI) from Claude Code, Claude Desktop, Cursor, or any MCP-speaking AI agent — an [MCP](https://modelcontextprotocol.io) server built on [`comfy-cli`](https://github.com/Comfy-Org/comfy-cli).**Quickstart · Configure your client · Comfy Cloud MCP · Tools · Contributing
Looking for the cloud-hosted version? Comfy Cloud MCP is set up below — install
it instead of this server, or alongside it.
What it does:
- 🖼️ Generate — run a workflow JSON (API-format or UI export), or go text-prompt → image in one call.
- ⏱️ Monitor jobs — submit async, then wait / watch / cancel, read the failure verdict, and collect the output PNGs.
- 🔍 Introspect your live install — search the nodes, models, and templates your ComfyUI actually has (custom nodes included), not a static catalog.
- 🧩 Build workflows — validate a graph, edit a template's slots, and fan one workflow into variants.
- ♻️ Manage ComfyUI — launch / stop / restart the server, tail its logs, and stage input assets.
Each tool shells out to the comfy command with --where local --json, parses comfy-cli's
envelope/1 output, and returns it — comfy-cli is the engine, and by default everything targets
the ComfyUI on your machine (127.0.0.1:8188).
Scope — local-first, not local-only. A few flows already reach beyond your machine:
partner_generate runs hosted partner models
(Flux / Ideogram / Kling / …) entirely on partner infrastructure — no local ComfyUI in the
execution path — and partner-API nodes let a locally-executed workflow call
those same hosted models, while COMFYUI_URL points the run/job tools
at a ComfyUI on another machine you control.
This server vs. Comfy Cloud MCP. Two different servers, and running both is
normal. This one is stdio: your client launches it as a subprocess on your own machine, and
it drives the ComfyUI installed there (or one on another machine you control). Comfy Cloud MCP is a
remote HTTP server at https://cloud.comfy.org/mcp that your client connects to over the
network, and it executes workflows on Comfy Cloud GPUs — no local GPU, no ComfyUI install. Both
authenticate: this one signs in to Comfy through comfy-cli (auth_login or
COMFY_API_KEY), the cloud one through OAuth in your browser or a Comfy Cloud API key. Both can
spend credits on partner models, so partner generation is not the dividing line — what this
server has no path to is Comfy Cloud itself: no cloud-hosted execution, no cloud queue, no
cross-session cloud batches. Every tool here shells out to comfy --where local. Pick by where you
want the work to run, or install the cloud server too.
Status: beta. 39 tools; core loop validated end-to-end against a live local ComfyUI
(server_info → run_workflow → fetch_outputs→ PNG on disk). CI runs pytest + ruff on
Python 3.10 and 3.14.
Quickstart
Four steps take you from a fresh install to your first generated image.
-
Install the pieces.
pip install comfy-mcp "comfy-cli>=1.14.0" # this server + the engine it wraps comfy install # create a ComfyUI workspace (skip if you have one)Both names, one command — on purpose. Installing
comfy-mcpdoes not install
comfy-cli: it is not a declared dependency of this package, because the server runs whichever
comfybinary yourPATH(orCOMFY_BIN) resolves to, which is often not
the environment you installed the server into. Declaring it would put a second copy in this
venv that may not be the one your tools actually drive; the version that matters is checked at
runtime instead, against the binary really being called. Skip it and the server still
starts and completes the MCP handshake — every tool call then fails with "comfynot found on
PATH", which is a missing engine, not a broken install.pip install comfy-mcpputs acomfy-mcpconsole script on yourPATH; that command is what
you point your AI client at in step 3. (A dedicated venv is fine — MCP clients may not see that
venv'sPATH, which is exactly whatCOMFY_BINis for; see Prerequisites.)
comfy-mcp --versionconfirms it landed — but don't runcomfy-mcpitself to test it: it
is a stdio server that talks MCP over stdin/stdout, so in a terminal it just waits and
exits without printing anything.comfy-mcp --helpsays the same thing in one screen.To install from a checkout of this repo instead, run
pip install .there (pip install -e .
for a working copy) — the comfy-cli half is the same either way.Installed this server back when it was called
comfy-local-mcp? Do
Upgrading fromcomfy-local-mcpfirst — installing
comfy-mcpalone will not clean up after the old name. -
Launch ComfyUI and leave it running:
comfy launch -
Add the server to your client using the snippet for your client in
Configure your AI client just below, then restart / reload it so
the tools appear. -
Ask your agent to run a workflow. For example:
"Confirm my local ComfyUI is running, then run the workflow at
~/workflows/txt2img.jsonand show me the image."Under the hood the agent calls
server_infoto confirm ComfyUI is up,run_workflowto
execute your workflow JSON (API-format or a UI export), andfetch_outputsto collect the
result. No hand-authored workflow? Ask it to start from a template instead — it can
search_templates,fetch_templateto write a runnable JSON, and run that — and
fetch_templatetells it up front if your install can't run that
template yet.
Where the images land. ComfyUI writes generated files into your ComfyUI workspace's
output/ directory (part of the workspace comfy install created). On top of that,
fetch_outputs(prompt_id, out_dir) copies a finished job's outputs into any directory you
name — so telling the agent "save them to ./outputs" puts a copy right where you asked while
the originals stay in the ComfyUI workspace.
Upgrading from comfy-local-mcp
This server used to be called comfy-local-mcp. It was never published to PyPI under that
name, so this only affects you if you installed it from a source checkout — but for those installs
the rename is not something pip install . finishes on its own, because comfy-mcp is a
different distribution, not a new version of the old one. Four things moved:
| Was | Is now |
|---|---|
distribution / import package comfy-local-mcp / comfy_local_mcp |
comfy-mcp / comfy_mcp |
console script comfy-local-mcp (the "command" in your client config) |
comfy-mcp |
env var COMFY_LOCAL_MCP_DEBUG_LOG |
COMFY_MCP_DEBUG_LOG |
failure-log directory leaf comfy-local-mcp/ |
comfy-mcp/ |
-
Uninstall the old distribution first. Installing the new one leaves the old one in place,
and itscomfy-local-mcpscript stays on yourPATHpointing at a package that no longer
exists — so an "upgraded" environment either keeps running the old code or fails with
ModuleNotFoundError:pip uninstall comfy-local-mcp # then: pip install comfy-mcp (or `pip install -e .`) -
Change
"command"tocomfy-mcpin every MCP client config that starts this server
(.mcp.json,claude_desktop_config.json,~/.cursor/mcp.json— see
Configure your AI client), then restart the client. The old
command name is gone; nothing aliases it. -
Rename the failure-log env var if you set it.
COMFY_LOCAL_MCP_DEBUG_LOGis no longer
read, and an env block that still sets it logs nothing — a disabled log and a stale
variable look identical from the outside. UseCOMFY_MCP_DEBUG_LOG; see
Failure log (opt-in). -
Move an existing failure log if you're mid-investigation. The default path's directory leaf
changed with the package, so a fresh run starts an emptyfailures.jsonlrather than appending
to the trail you were collecting. Nothing reads the old directory any more — copy it across, or
delete it:# macOS; ~/AppData/Local on Windows, ~/.config on Linux cd ~/Library/Application\ Support mkdir -p comfy-mcp mv comfy-local-mcp/failures.jsonl* comfy-mcp/ && rmdir comfy-local-mcpThe glob carries the two rotations (
failures.jsonl.1,failures.jsonl.2) along with the
live file, andmkdir -pfirst means this is also safe once the new directory exists.
Configure your AI client
All three clients speak the same MCP stdio contract: run the comfy-mcp command as a
server. Pick your client.
The server key (
comfy-mcpin every snippet below) is just the label your client files
these tools under — it is yours to choose, and the"command"(comfy-mcp) is the only part
that has to match the installed console script. Earlier versions of this README used
comfy-local, so if your config already has acomfy-localentry, edit it rather than
pasting a second one — two keys pointing at the same command register the server twice and
your client shows every tool twice. Keeping the old key is equally fine; nothing reads it.
The
COMFY_BINenv entry is shown in every example. Drop it ifcomfyis already on the
environment your client launches the server with; keep it (pointing at the absolute path) if
it isn't.COMFY_API_KEYis also shown, commented as optional — keep it only if you use
partner-API nodes (Seedream / Veo / Kling / Gemini / …); drop it
otherwise.
On macOS, keep ComfyUI out of
~/Documents,~/Desktopand~/Downloads— or grant your
client Full Disk Access. macOS blocks apps (and everything they launch) from reading those
folders, so an install there fails withOperation not permittedbefore anything runs. See
Troubleshooting.
Claude Code
One command registers the server:
# COMFY_API_KEY is optional — add it only if you use partner-API nodes
# (see the Partner-API nodes section).
claude mcp add comfy-mcp \
-e COMFY_BIN=/path/to/venv/bin/comfy \
-e COMFY_API_KEY=<your-comfy-api-key> \
-- comfy-mcp
Or, to check it into a project, add a .mcp.json at the repo root:
{
"mcpServers": {
"comfy-mcp": {
"command": "comfy-mcp",
"env": {
"COMFY_BIN": "/path/to/venv/bin/comfy",
"COMFY_API_KEY": "<your-comfy-api-key>"
}
}
}
}
Claude Desktop
Edit claude_desktop_config.json (Settings → Developer → Edit Config; on macOS it lives at
~/Library/Application Support/Claude/claude_desktop_config.json) and add the server, then
restart Claude Desktop:
{
"mcpServers": {
"comfy-mcp": {
"command": "comfy-mcp",
"env": {
"COMFY_BIN": "/path/to/venv/bin/comfy",
"COMFY_API_KEY": "<your-comfy-api-key>"
}
}
}
}
Cursor
Add the server to ~/.cursor/mcp.json (global) or .cursor/mcp.json in a project:
{
"mcpServers": {
"comfy-mcp": {
"command": "comfy-mcp",
"env": {
"COMFY_BIN": "/path/to/venv/bin/comfy",
"COMFY_API_KEY": "<your-comfy-api-key>"
}
}
}
}
Comfy Cloud MCP
Everything above sets up this server, which runs on your machine. Comfy also runs a hosted
MCP server — Comfy Cloud MCP — and it is a good fit when the machine can't carry local
diffusion, or when you'd rather not install ComfyUI at all. It lives at:
https://cloud.comfy.org/mcp
Your client connects to that URL over remote HTTP (no subprocess, nothing to pip install) and
workflows execute on Comfy Cloud GPUs. You need a Comfy Cloud
account — sign up first if you don't have one, since the sign-in below uses it.
Two ways to authenticate. OAuth is the default: your client opens a browser, you pick a
workspace, and tokens refresh themselves. For clients that don't speak MCP OAuth (Cursor today) and
for headless/CI use, create a Comfy Cloud API key at
platform.comfy.org/profile/api-keys — it starts with
comfyui- — and pass it as an X-API-Key header. Prefer your client's env interpolation
(${env:COMFY_API_KEY}) over pasting a key into a file you might commit.
Note that this is a separate credential path from the COMFY_API_KEY this server's own examples
show: that one is read by comfy-cli on this machine for partner-API
nodes. The same key works for both, but each server is configured on its own.
Claude Code (cloud)
Install the comfy-cloud plugin — it registers the MCP connection and adds /comfy-cloud:* slash
commands in one step:
/plugin marketplace add Comfy-Org/comfy-skills
/plugin install comfy-cloud@comfy-skills
Then run /mcp, select comfy-cloud → Authenticate, and finish the sign-in in your browser.
Prefer just the connection, without the plugin? Add the server directly (-s user makes it
available in every project):
claude mcp add --transport http comfy-cloud https://cloud.comfy.org/mcp
and authenticate the same way, via /mcp.
Claude Desktop (cloud)
Claude Desktop adds it as a custom connector through its UI:
- Sidebar → Customize → Connectors.
- Click + in the Connectors header → Add custom connector.
- Name it (e.g.
Comfy Cloud MCP), set Remote MCP server URL to
https://cloud.comfy.org/mcp, and click Add. - A browser window opens: choose your workspace and click Continue to authorize.
Cursor (cloud)
Cursor connects to remote MCP servers over HTTP but does not support MCP OAuth today, so use an
API key. Add this to ~/.cursor/mcp.json (global) or .cursor/mcp.json (project), with
COMFY_API_KEY set in your shell or system environment:
{
"mcpServers": {
"comfy-cloud": {
"url": "https://cloud.comfy.org/mcp",
"headers": {
"X-API-Key": "${env:COMFY_API_KEY}"
}
}
}
}
Other clients (cloud)
Any client with a remote HTTP MCP transport can connect to the same URL. Most use a JSON config
with a url field (Windsurf uses serverUrl instead):
{
"mcpServers": {
"comfy-cloud": {
"url": "https://cloud.comfy.org/mcp"
}
}
}
Sign in through the browser if the client supports MCP OAuth; otherwise add the X-API-Key header
shown above. Restart the client and you should see the cloud tools (search_templates,
submit_workflow, get_output, …) registered under the comfy-cloud server.
Codex and OpenClaw have first-class setup steps — including codex mcp add comfy-cloud --url https://cloud.comfy.org/mcp and openclaw mcp set / openclaw mcp login — in the Comfy Cloud MCP
docs, which is also where the screenshot walkthroughs, the
full cloud tool list, and the slash-command/prompt tables live.
Table of contents
- Quickstart
- Upgrading from
comfy-local-mcp - Configure your AI client
- Comfy Cloud MCP
- Prerequisites
- When to use this server
- Using with local LLMs (VRAM coordination)
- Partner-API nodes
- Spending credits on partner models
- Templates your install can't run
- Driving a remote ComfyUI
- Targeting a non-default ComfyUI address
- Which address variable do I want?
- Project anchoring
- Tools
- Troubleshooting
- Failure log (opt-in)
- Smoke test
- Contributing
- License
- Trademarks
Prerequisites
- Python ≥ 3.10.
- comfy-cli ≥ 1.14.0 on your
PATH:pip install "comfy-cli>=1.14.0". This is the engine
every tool wraps; the server refuses to run against an older comfy-cli with an upgrade message.
1.14.0 is the first release carrying everything this server needs. 1.13.0 brought the basics —
thecomfy logsverb, theenvelope/1contract, thecomfy outdatedverb behind
server_info'sfreshnessblock, and the machine-readablelogin_urleventauth_login
waits for — but a large slice of the tool surface calls verbs that only exist from 1.14.0 on:
comfy node deps(node_dependencies),system-stats/free,workflow notes
(list_workflow_notes),logs --port, the background download group (--backgroundplus
download-status/downloads/download-cancel),models search's cross-folder walk, the
templates gallery cache TTL, andcomfy run's--allow-spendinterlock. On 1.13.0 enough of
the surface is inert that the server reads as broken rather than as out-of-date, which is why
the floor moved rather than each tool degrading. Installing this server does not install
comfy-cli — it is deliberately not a declared dependency of thecomfy-mcppackage, because
the binary that matters is whichever onePATH/COMFY_BINresolves to rather than one pinned
into this server's environment, so the floor above is enforced at runtime against that binary
(the rationale lives inpyproject.toml). Install the two together:
pip install comfy-mcp "comfy-cli>=1.14.0". - Every capability degrade is still in place, because the floor and a degrade guard different
failures: the floor catches a wrong comfy-cli version, a degrade catches a correct version in
a broken environment. The version guard fails open on a--versionit can't parse (a
source build, a fork), so such a build can still reach the tools from below the floor — and a
dependency outside comfy-cli (a ComfyUI-Manager too old to know a flag comfy-cli forwarded to
it) can fail on an otherwise-compliant install. In those cases you get a named capability gap —
{"error": "…", "unsupported": true}, e.g.
freshness: {"error": "freshness unavailable: …", "unsupported": true}— rather than a raw
Click usage dump, and the rest of the tool keeps working. - A ComfyUI workspace. If you don't have one,
comfy-clican create it:comfy install
sets up a ComfyUI workspace it will point at. (An existing ComfyUI checkout works too — see
comfy set-default <path>.) - A running ComfyUI. ComfyUI must be started before you use the tools — launch it with
comfy launch(or, from an agent, thelaunch_comfyuitool), and confirm it is up with
server_info. Nothing here starts ComfyUI implicitly.
<details>
<summary><strong>Optional environment variables</strong> (<code>COMFY_BIN</code>, <code>COMFY_API_KEY</code>, <code>COMFYUI_URL</code>, <code>COMFY_MCP_REMOTE_SHARED_MODELS</code>, <code>COMFY_LOCAL_URL</code>, <code>COMFY_T2I_TEMPLATE</code>; plus <code>COMFY_USER_AGENT</code>, which the server sets itself)</summary>
<br>
COMFY_BINoverride (optional). By default the server callscomfyfromPATH. MCP
clients launch the server with their own environment, which often does not include your
shell'sPATH— so ifcomfylives in a virtualenv or a non-standard location, set
COMFY_BINto its absolute path (e.g./path/to/venv/bin/comfy). Every example in
Configure your AI client shows where it goes. Setting it is
sufficient on its own — you do not also have to put
that directory on the client'sPATH. The server prepends the resolved binary's directory to
thePATHit hands comfy-cli, because some comfy-cli commands (notably the background
launch) re-invokecomfyby name and have to be able to find themselves.COMFY_API_KEY(optional — needed only for partner-API nodes). Workflows that use
partner-API nodes (Seedream / Seedance / Nano Banana / Gemini / Veo / Kling / …) need a Comfy
credential, and — exactly likeCOMFY_BIN— an MCP client launches the server with its own
minimal environment, so a key from your shell won't reach it. SetCOMFY_API_KEYin the
client registrationenvblock. See Partner-API nodes below for the
full precedence chain; every example in
Configure your AI client shows where it goes.COMFYUI_URL/COMFYUI_HOST/COMFYUI_PORT(optional — drive a ComfyUI on another
machine). Read by this server. By default every tool targets127.0.0.1:8188. Set
COMFYUI_URL(e.g.http://gpu-box:8188) — or theCOMFYUI_HOST(+ optionalCOMFYUI_PORT,
default8188) pair — to point the submit / job tools at a ComfyUI running elsewhere, e.g. a
GPU box reachable over a private network (Tailscale). See Driving a remote
ComfyUI for what is and isn't remoted. Unset ⇒ nothing changes.COMFY_MCP_REMOTE_SHARED_MODELS(optional — only meaningful alongside the variables above).
download_modelwrites to this machine's models dir and has no remote mode, so with a remote
configured it refuses rather than downloading onto the wrong disk. Set this to1when this
machine's workspace models dir is the remote's — shared storage, e.g. an NFS / tailnet mount —
to skip that guard and download as usual. Nothing else reads it, and it does nothing at all when
no remote is configured.COMFY_LOCAL_URL(optional — a ComfyUI on this machine, on a non-default port). Read by
comfy-cli, never by this server — it rides the environment passthrough, so setting it in the
clientenvblock re-points every tool. For a ComfyUI on this machine that isn't on
127.0.0.1:8188(e.g.:8189because Docker Desktop's ComfyUI holds:8188). See
Targeting a non-default ComfyUI address, and
Which address variable do I want? for the difference
between the two.COMFY_USER_AGENT(set by the server — not yours to configure). Every comfy-cli call this
server makes is labelledcomfy-mcp, which is how comfy-cli tells work that came from this MCP
apart from a human typing the same command — most usefully on the partner-API calls that spend
credits. A value you set is overridden, on purpose: it would otherwise file this server's
calls under someone else's name. The label is a caller identity, not content — nothing about
your prompts, workflows, or outputs travels with it, and comfy-cli's own telemetry stays subject
to comfy-cli's consent settings (comfy tracking disable, or theDO_NOT_TRACK/
COMFY_NO_TELEMETRYenvironment variables, both of which this server passes straight through).COMFY_T2I_TEMPLATE/COMFY_T2I_PROMPT_SLOT/COMFY_T2I_CHECKPOINT_SLOT(optional — retarget
generate_image).generate_image(prompt)runs the gallery'sdefaulttemplate (ComfyUI's own
basic SD1.5 text-to-image graph), filling its positive-prompt slot6.textand, when you pass
checkpoint, itsckpt_nameslot. To point that on-ramp at a different local text-to-image graph,
set all three together — the slot keys describe one specific template, so changing the template
alone leaves the prompt address matching no slot. List a replacement's slots with
comfy templates fetch <name> -o wf.json && comfy workflow slots wf.json. For a one-off run of some
other template, prefer therun_templatetool over these.
</details>
When to use this server
Local diffusion is only a good default on a machine that can actually carry it, so the server's client instructions tell your agent to read server_info's hardware block (os, arch, ram_bytes, and a gpu object with vendor / model / vram_bytes / unified_memory) before the first generation and route on it. The agent does not even have to make that call: at startup the server probes comfy env once and appends a Machine snapshot section — the same hardware block verbatim, plus the configured remote target if any — to the instructions the MCP handshake carries, so the routing figures are in the agent's context from the first message. The probe is best-effort: if it fails (no comfy on PATH yet, a timeout), the section is simply absent and the instructions still say to call server_info first; a healthy probe on an older comfy-cli that reports no hardware states the figure is unknown, which routes to step 3 below (ask). The snapshot never carries live state — whether a server is running stays server_info's job. The thresholds:
| Machine | Guidance |
|---|---|
| Discrete GPU, ≥ 24 GB VRAM | Local generation is a good default. |
| Discrete GPU, 8 GB to under 24 GB VRAM | Images are fine (prefer current, smaller models); video will be slow or infeasible. |
| < 8 GB VRAM, or the user confirming there is no GPU | Don't run local diffusion. Use partner nodes (plain web calls, fine on any machine) or the Comfy Cloud MCP if your client has it connected. |
| Apple Silicon, ≥ 32 GB unified memory | Images are OK. Video on the Apple GPU is not recommended — time estimates are unreliable and thermals suffer. |
| Apple Silicon, under 32 GB unified memory | Same as the no-GPU row above — go partner/cloud rather than local. |
The discrete-GPU rows are written for NVIDIA but apply to an AMD or Intel card on a ROCm/XPU build too — the VRAM number is what matters. The no-local-video rule is an Apple GPU rule rather than a Mac rule: an Intel Mac with a discrete card follows the discrete-GPU rows.
The instructions walk these as an ordered procedure, because several of the checks only make sense in sequence:
- Is the work even local?
hardwaredescribes the machine this server runs on, and that is where most tools execute. Acomfy_targetblock (Driving a remote ComfyUI) diverts every tool that submits a job —run_workflow,generate_image,run_template— along with the queue/jobstools, while discovery, templates, downloads, outputs and the lifecycle tools stay here; so against a genuine remote the thresholds below describe the wrong machine. It counts as another machine only when itshostis neither loopback (anything in127.0.0.0/8,localhost, IPv6::1) nor this host's own address, and a malformed config produces an error-shaped{error, note}block that resolves no remote at all. Nothing the server returns carries the local hostname or interface addresses, so ahostthe agent can't place is a question for you rather than a guess — a hostname or LAN IP can be this same machine, and a loopback host can be a tunnel to a remote GPU.COMFY_LOCAL_URLis a second signal worth checking: it repoints comfy-cli without producing acomfy_targetblock. - Get a memory figure. The sizes are bytes (
ram_bytes,gpu.vram_bytes) and the divisor gives GiB, while drivers report under the advertised size — a consumer 24 GB card reads 23.99, an ECC/reserving datacenter card (A10, L4) about 22.3 — so a small shortfall, within ~10% of a nominal size, reads as that nominal capacity. A gap wider than that is not driver overhead and is taken at face value instead: on a MIG/vGPU partition the model string names the whole card whilevram_bytesis the slice you actually get, and rounding a 6 GB A100 slice up into the ≥ 24 GB band would OOM the run. On Apple Silicongpu.vram_bytesisnull(withgpu.unified_memorytrue) and the figure isram_bytes— an Apple-only substitution. - If the figure is missing, ask. A
nullor zerovram_byteson any non-Apple GPU (a discrete card comfy-cli can't size, but also a non-Apple unified part like a Jetson/Grace board or a Strix Halo APU), a missinggpuobject, or a missing/zeroram_byteson the Apple path all mean unknown, not "no GPU" — the agent asks rather than stranding a machine that has one. The "no GPU" verdict is reserved for a confirmed absence, and the only thing that confirms one is your own answer: nohardwarepayload encodes it, because a null or missinggpuis unknown by this same step. Nothing in this repo probes hardware, and the instructions tell the agent not to shell out either: a probe runs on a path this server can neither bound nor audit. - Route on the figure, then redirect rather than dead-end when the answer is "not on this machine". A figure that came from your answer rather than the payload routes on whichever row fits the machine — the unified-memory row on an Apple Silicon Mac, the VRAM rows otherwise, which is what covers the non-Apple unified-memory boards that have no row of their own.
"No local video on a Mac" is about the Apple GPU, not about video as such: API-tagged video templates (search_templates(tag="API", type="video") — both filters, since neither alone isolates partner-run video; each row's api boolean then confirms which side of the line it fell on) and emit_partner_workflow run the model on partner infrastructure, so they work on any machine. See Partner-API nodes.
The hardware block comes straight through from comfy env, and a comfy-cli that predates it simply omits the key. There is no HTTP client and no cloud code here — the cloud/partner steer is guidance text only.
Which model to use is deliberately not encoded here. The instructions tell the agent to pick via search_templates / search_models rather than assume a classic default (e.g. SDXL), because the gallery tracks current models and a hardcoded name would rot. Current-model guidance lives in Comfy-Org/comfy-skills, which is its canonical home.
Using with local LLMs (VRAM coordination)
Running a local LLM (Ollama, LM Studio, llama.cpp) and ComfyUI on the same GPU means the two compete for the same VRAM, and the LLM is usually the one holding it when the image job needs it. This server gives the agent both halves of the read/free loop, but the coordination is the client's — see why below.
The recipe, in order:
- Read the headroom.
system_stats()returns per-devicevram_free/vram_totalstraight from the live ComfyUI. Comparevram_freeagainst what the workflow's checkpoint needs. - If it is tight, the client unloads its own LLM using its runtime's own mechanism — this server has no way to do it (step 5 below):
- Ollama — send
keep_alive: 0on the next/api/generate(or/api/chat) call, which unloads the model as soon as that call returns, or runollama stop <model>. - LM Studio — let the model's TTL / JIT auto-evict expire, or unload explicitly with
lms unload <model>(lms unload --allfor everything). - llama.cpp (
llama-server) — in router mode (started with no-m, or with--models-dir)POST /models/unloadwith{"model": "<name>"}unloads one model;GET /modelslists what is currently loaded. Independently of router mode,--sleep-idle-seconds Nmakes the server unload the model and its KV cache after N idle seconds and reload it automatically on the next request — which handles both step 2 and step 5 with no orchestration at all. Only a classic single-model server started without either (llama-server -m model.gguf) has nothing to call: there, stopping and restarting the process is the reclaim.
- Ollama — send
- Free ComfyUI's own models too with
free_memory(). ComfyUI applies it when its queue worker next iterates — immediate if idle, after the current job if busy — and it never interrupts a running job. Re-readsystem_stats()to confirm the VRAM actually came back before committing to a big run. - Run the job —
run_workflow(...)/run_template(...)/generate_image(...)— then collect withfetch_outputs(...).
(This whole recipe is about this machine's VRAM:system_statsandfree_memoryare never
remoted, so with aCOMFYUI_URLconfigured steps 1–3 measure and free the wrong box while step 4
submits to the remote. See Driving a remote ComfyUI.) - The client reloads its LLM afterwards, again through its own runtime. Ollama, LM Studio and a sleep-idle
llama-serverall reload on demand, so for those "reload" is just the next request; a single-modelllama-serverstopped in step 2 has to be started again.
Why steps 2 and 5 cannot live in this MCP server. This server is a stdio subprocess of your MCP client — it holds no handle on whatever LLM runtime that client is using, is not told which one it is, and has no business reaching into a process it does not own. Reaching one anyway would also breach the thin-wrapper rule: every tool here is a comfy passthrough, and there is no comfy subcommand for "unload someone else's model". The deeper reason is step 5: the model that was unloaded cannot ask for itself back, so something still running has to sequence unload → run → reload. Where the LLM's own runtime can do that (Ollama's on-demand load, LM Studio's JIT, llama-server --sleep-idle-seconds) it should — that is the least-coordination option and it needs nothing from this server. Otherwise the client, or the orchestrator driving it, is the only participant present throughout. Either way the split is structural rather than a missing feature: this server owns reading and freeing ComfyUI's memory, and the client owns its own model's lifecycle.
A note on scope: free_memory() asks ComfyUI to release its models. It does nothing about VRAM held by an LLM runtime, a browser, or another process — if system_stats() still shows little free VRAM after a free_memory() call, the memory is probably someone else's and step 2 is what reclaims it.
Read that signal against the lag, not instantly. The free applies on the queue worker's next iteration, so on a busy server an immediate re-read legitimately shows no change while the VRAM is still ComfyUI's — the request simply has not been serviced yet. Before concluding the memory belongs to another process, either wait for the current job to finish (job(action="queue") shows whether one is running) or re-poll system_stats() a few times over a few seconds. Only a number that stays flat on an idle server means the holder is someone else.
A second caveat: system_stats() and free_memory() are not redirected by COMFYUI_URL / COMFYUI_HOST — they always describe and act on whichever ComfyUI comfy-cli itself targets, because comfy system-stats and comfy free take no --host / --port. With a remote ComfyUI configured, run_workflow / generate_image / run_template submit there while these two read and free the local install, so this recipe applies to a local-ComfyUI setup. Don't gate a remote run on it. Both payloads say so themselves when a remote is configured: a top-level comfy_target_note (host / port / source / note) names the target the run tools submit to and states that these numbers describe whichever ComfyUI comfy-cli itself targets, which need not be the same box — the note reports that divergence rather than adjudicating it, since a configured host can perfectly well resolve to this machine (and a loopback one can be a tunnel to a remote GPU). A malformed COMFYUI_URL / COMFYUI_HOST gives an error-shaped note (error / note) instead of breaking these local-only calls, so an absent key means exactly one thing: nothing is configured, and the payloads are unchanged.
Partner-API nodes
Some ComfyUI nodes call out to Comfy's partner APIs (Seedream / Seedance / Nano Banana / Gemini /
Veo / Kling / …). Running one locally still needs a Comfy credential, and comfy-cli resolves
it in this order (first match wins):
- a per-call flag (not exposed by this server);
- a live Comfy Cloud OAuth session (
comfy cloud login); - the
COMFY_API_KEYenvironment variable; - a stored key set with
comfy auth set comfy-cloud-api-key --key <KEY>.
Option 2 does not have to be typed into a terminal: the agent can call auth_login, which starts comfy cloud login in the background and hands back the OAuth URL for you to open. Complete the sign-in in your browser, then have the agent confirm it with auth_status. The sign-in itself is comfy-cli's — this server never sees your tokens, and the browser callback is handled by the CLI's own loopback listener on this machine (so auth_login is for a local MCP; on a remote/containerised one, sign in where comfy-cli actually runs).
Because an MCP client spawns the server with its own minimal environment (the same reason
COMFY_BIN exists), a COMFY_API_KEY from your interactive shell is not inherited — put it
in the client registration env block (shown in every
client example). If a run fails with
partner_node_requires_credential, the error now carries comfy-cli's hint verbatim, including
the comfy auth set comfy-cloud-api-key --key … fallback and the list of offending nodes; the
server also retries a transient credential failure briefly before surfacing it.
Confirmation prompts on clients that can't show them
Several tools ask you to confirm before they act: install_node,
update_comfyui(target="all"), switch_comfyui_version, restart_comfyui when it must stop a
server it did not start, and launch_comfyui with --listen (which exposes an unauthenticated
ComfyUI to your network). Each raises an MCP elicitation and fails closed if it is not
approved.
Some MCP clients answer that request without ever showing you a prompt. When that happens the
tool refuses and tells you so — nothing is changed, and the error names the equivalent terminal
command you can run instead.
If you would rather pre-authorize specific gates, set COMFY_MCP_ASSUME_CONSENT in the server's
environment — the env block of your client registration, alongside COMFY_BIN:
"env": { "COMFY_MCP_ASSUME_CONSENT": "install_node,update_all" }
Accepted names: install_node, update_all, version_switch, kill_untracked,
network_exposure — or all for every one of them. List only what you want; authorizing node
installs should not silently also authorize binding ComfyUI to every network interface.
This is a setting you write, in a file the model cannot edit. That is the point: an agent
cannot grant itself permission by passing an argument, which is why no tool parameter does this.
Spending credits is deliberately excluded. No value — including all — pre-authorizes
partner_generate, run_template or run_workflow. Money keeps a single owner: comfy-cli's own
durable consent (comfy generate consent always). See
Spending credits on partner models.
Spending credits on partner models
partner_generate is the one tool whose whole purpose is to spend: it wraps comfy generate <model>, which calls a hosted partner API and spends your Comfy credits. So every call is
confirmed with you first.
The other tools execute on your machine, and on their own they cost nothing — but that is a
property of the tool, not a guarantee about the workflow you hand it. A workflow run through
run_workflow can itself contain the partner-API nodes described just above (Seedream, Veo,
Kling, …), or any other node that bills a hosted service, and those still spend your credits —
they bill through the workflow, below this server. run_workflow therefore carries the same opt-in
confirm_spend gate run_template does (below).
That gate covers the partner-API nodes comfy-cli recognizes, which is not the same as every node
that can bill something: an arbitrary custom node can still call a paid service of its own, with
nothing to gate it. Check what a workflow contains before running one you did not build.
generate_image needs no gate because it runs a free OSS template — though note it is
retargetable via COMFY_T2I_TEMPLATE, and pointed at an API-tagged template it
would spend with no prompt.
emit_partner_workflow sits on the free side of that line for the same reason: it only writes a
graph containing a partner API node, never calls the partner, and so has no confirmation prompt.
The graph it writes is exactly one of the workflows the paragraph above is warning about — running
it with run_workflow bills the partner node, so that step needs confirm_spend=True.
On a client that supports MCP elicitation
(Claude Code and Claude Desktop do), the call raises a confirmation prompt naming the model and
saying that it spends credits:
- Approve → the server forwards comfy-cli's
--yesand the generation runs. - Decline (or dismiss it) → the tool returns an error, and comfy-cli is never started. No
credits are spent. - Leave it unanswered → after five minutes the prompt lapses into a refusal, so a forgotten
call never sits pending forever. Nothing is spent; call the tool again to get a fresh prompt.
Don't want to be asked every time? Persist it in comfy-cli, not here:
comfy generate consent always # spend without prompting
comfy generate consent show # what is it set to?
comfy generate consent ask # back to confirming each call
The server reads that setting per call and skips its own prompt when it is on — the durable
"always proceed" lives in comfy-cli's config, and this server keeps no spend state of its own.
On a client that cannot elicit, there is no prompt to raise, so consent has to be explicit in
the call: confirm_spend=True forwards --yes. Without it comfy-cli's gate fails closed (an MCP
server has no terminal to prompt at) and the call errors having spent nothing. On a client that
can elicit you are asked anyway — confirm_spend=True is not a way around the prompt.
Two things the server deliberately will not do:
- Treat tool permission as spend consent. Your agent host's "always allow this tool" toggle
authorizes callingpartner_generate; it never authorizes spending your money, and is never
read as consent. Only the prompt you answered, or the comfy-cli setting you persisted, is. - Run against a comfy-cli with no spend gate. The fail-closed guarantee is the engine's, so if
comfy generate consentis missing the tool refuses up front rather than spending on the
assumption something would have stopped it.pip install -U comfy-clito fix.
Templates that spend — run_template
run_template is the other tool that can spend, and it is confirmed the same way, with the
differences the verb forces. Most gallery templates are free OSS graphs that run on your machine;
some embed partner-API nodes and bill through them.
confirm_spend=False(the default) never prompts. Nothing is forwarded, so comfy-cli's gate
fails closed on a paid template — there is nothing to consent to. A free template just runs. This
is deliberate: prompting on every template run would train you to click through the one prompt
that matters.confirm_spend=Trueasks you first, naming the template, on any client that can elicit.
Approve →--allow-spendis forwarded. Decline → the tool errors and comfy-cli is never started.
As withpartner_generate, an agent setting the argument for itself is not your consent; on a
client that cannot elicit it stands alone as the fallback.comfy generate consent alwaysdoes not apply here. That setting is scoped to
comfy generate—comfy run-templatenever reads it — so it grants nothing for templates and
the prompt is raised regardless.
Unlike partner_generate, there is no up-front gate probe: run-template carries its spend gate
inside the verb itself, so a comfy-cli that has the verb has the gate.
Workflows that spend — run_workflow
run_workflow takes the same confirm_spend argument, with the same three rules as
run_template above — default never prompts and forwards nothing, confirm_spend=True asks you
per call on a client that can elicit, and comfy generate consent always grants nothing here
either. Most workflows are ordinary local graphs and are unaffected; the ones this matters for are
the graph emit_partner_workflow writes and an API-tagged gallery template you fetched with
fetch_template. When consent is withheld the engine refuses with spend_consent_required and
names the offending partner_nodes, so you learn which nodes cost money without a second call.
Consent is resolved once per call, so the server's brief credential retry never re-asks you.
One caveat specific to this verb, and the reason it is called out rather than folded into the
section above: comfy run long predates its spend gate, so unlike run-template the verb's
presence proves nothing. The gate shipped in comfy-cli 1.14.0, which is the floor this server
enforces, so on every published comfy-cli it accepts the interlock is there and
confirm_spend=False is a guarantee rather than a default. The probe stays because the floor
can't prove it: the version guard fails open, so a source build or fork whose --version
can't be read reaches the tool without the flag. The server probes comfy run --help on the
calls you approved and simply omits --allow-spend when that comfy-cli has no such flag, so an
approved run still runs instead of dying on a usage error — but on such a build what authorizes
the spend is your answer to the prompt, not an engine gate, and a paid workflow runs and spends
whether or not you pass confirm_spend. pip install -U comfy-cli closes that residual case.
Templates your install can't run
The template gallery is served fresh from Comfy-Org/workflow_templates, while your ComfyUI is
whatever version you installed. So the catalog can legitimately offer a template your install
cannot run yet — it references a node class you don't have, or a model option inside a node you
do have (a partner model key added in a later release is the common one). Discovery succeeds, the
run fails, and you get to work out why.
get_template and fetch_template cross-check the template against your install and report it
as a local_check block. Under the hood it is comfy validate — the same engine
validate_workflow uses, reading the live object_info of your running
ComfyUI, so it sees your custom nodes and your model options, not a bundled catalog.
local_check |
Means |
|---|---|
{"checked": true, "runnable": true, …} |
Every node class and input option the template uses exists in your install. Necessary, not sufficient — validate_workflow's documented blind spots still apply. |
{"checked": true, "runnable": false, "errors": [...], …} |
Running it will fail as-is: the errors name what is missing (and, where comfy-cli can, what your install offers instead). Update ComfyUI and its custom nodes, or pick another template. |
{"checked": false, "reason": …, …} |
The comparison could not be made — almost always because ComfyUI isn't running, so there is no live catalog to compare against. This is not a verdict about the template. |
The check is advisory and fails open: the workflow file is written either way, path always comes
back, and nothing is ever refused on its account. Pass check_local=False to skip it.
Driving a remote ComfyUI
By default the server drives ComfyUI on the local 127.0.0.1:8188. Point it at a ComfyUI running
elsewhere — e.g. a GPU box reachable over a private network (Tailscale) — by setting one of:
COMFYUI_URL— a full URL, e.g.http://gpu-box:8188(host-only is fine; port defaults to
8188). Takes precedence over the pair below. Only the host and port are forwarded to
comfy-cli, so the URL must be plainhttp://with no base path and no query or fragment:
anhttps://scheme, a reverse-proxy path (http://gpu-box:8188/comfyui), or a query / fragment /
;params(http://gpu-box:8188/?token=…) is rejected rather than silently downgraded to http /
dropped. That last one is the shape an auth-proxied ComfyUI is usually written as, and comfy-cli
has nowhere to put it — a dropped token would submit every run unauthenticated and fail later as a
401/403 naming nothing — so it is refused up front. Front a TLS/base-path/auth proxy locally if you
need one, and pointCOMFYUI_URLat that.COMFYUI_HOST(+ optionalCOMFYUI_PORT, default8188) — e.g.COMFYUI_HOST=gpu-box.
A port without a host (setting onlyCOMFYUI_PORT) is rejected — set the host too.
Set it in the client registration env block (same place as COMFY_BIN). With nothing set,
behavior is unchanged (127.0.0.1:8188 on this machine). If what you actually have is a ComfyUI on
this machine on a different port, you want COMFY_LOCAL_URL instead — see Which address variable
do I want?.
When configured, the server forwards --host / --port to comfy-cli for exactly the verbs that
accept them — comfy run, comfy run-template, comfy jobs … and comfy upload — so every tool
that submits a job, reads one back, or stages the files a job will read targets the remote:
run_workflow, generate_image, run_template, job (every action), upload_file. server_info
reports the configured target under a comfy_target block.
That set is deliberately closed under submit-then-poll: a prompt_id only means something to the
server that issued it, so a tool that submits and a tool that polls must never resolve to different
machines. upload_file is in it for the same reason one step earlier: an input file is only useful
on the machine that runs the workflow reading it, so staging it here while submitting there would
fail the run on a filename the remote cannot see. Its paths still name files on this machine —
they are read here and their bytes sent to the target. Remote upload needs comfy-cli ≥ 1.14.0
(this server's floor); an older one rejects the forwarded --host and upload_file raises with the
upgrade step rather than silently staging into the local input dir.
Not remoted (this repo is a thin wrapper and never opens its own socket):
- Lifecycle (
launch_comfyui,stop_comfyui,restart_comfyui,update_comfyui,
switch_comfyui_version,install_node,get_logs) — these manage a local ComfyUI
process/install and stay local-only; they cannot start/stop, update, version-switch, install node
packs into, or read logs from a remote box. Start and update ComfyUI, and install its node packs,
on the remote host yourself.install_nodein particular writes into this machine's ComfyUI
workspace and venv, so withCOMFYUI_URLset the pack lands where the run isn't. - Catalog / partner verbs —
search_templates/search_models/download_model/
partner_generate— this server forwards no--host/--portto these verbs (they accept
none at all), so they run against comfy-cli's local default. A model must be installed on the
machine that actually runs the job, anddownload_modelcannot do that for you — so rather than
writing the checkpoint to the wrong disk and letting the run fail later on a missing model, it
refuses whileCOMFYUI_URL/COMFYUI_HOSTis set, naming the remote it would have missed.
Install the model on the remote host itself (its own comfy-cli or MCP server). The exception is
shared storage — an NFS / tailnet mount where this machine's workspace models dir is the
remote's — which no environment check can distinguish, and which this server may not probe the
remote to confirm: assert it withCOMFY_MCP_REMOTE_SHARED_MODELS=1and the download runs as
it always did.download(every action) is never guarded —
they manage downloads already submitted here. - Output download (
fetch_outputs→comfy download) takes no--host/--porteither, but it
still retrieves a remote job's files, because it never asks a server which job that is: the
same comfy-cli run that submitted the job wrote a state file on this machine keyed by
prompt_id, and for a non-loopback target that file records each output as an absolute
http://<remote>:<port>/view?…URL, whichcomfy downloadthen streams from the remote. It falls
back to querying the local default server only when no such state file exists (an id this machine
never submitted).run_workflow(wait=True)/job(action="status")return those same URLs if you
would rather hand them off than copy bytes. - Discovery / validation (
nodes,validate_workflow, and the
local_checkblock onfetch_template/get_template) — their comfy-cli verbs do accept
--host/--port, but this version forwards only to the submit/poll tools, so they still
describe the local install. Remoting them is a planned follow-up; until then a workflow or
template can pass a local check and still fail on a remote whose node set differs, so
author/validate against a local ComfyUI matching the remote's. - The remote ComfyUI must be reachable and unauthenticated on that network (the private network
is the boundary); the server does not authenticate to it.server_infodoes not live-probe the
remote — reachability surfaces on the first run/job call.
Targeting a non-default ComfyUI address
The section above drives a ComfyUI on another machine. This one is for a ComfyUI on this
machine that simply isn't on the default 127.0.0.1:8188 — most often a port clash, e.g. Docker
Desktop's ComfyUI already holds :8188 so yours came up on :8189.
That address is resolved by comfy-cli, not by this server. Every tool shells out to comfy
with the server's full environment, so a COMFY_LOCAL_URL set in your MCP client's env block
reaches comfy-cli and re-points every local-targeting verb. Nothing to change here — set it
alongside COMFY_BIN in the client registration:
{
"mcpServers": {
"comfy-mcp": {
"command": "comfy-mcp",
"env": {
"COMFY_BIN": "/path/to/venv/bin/comfy",
"COMFY_LOCAL_URL": "http://127.0.0.1:8189"
}
}
}
}
Accepted values. http://host:port, host:port, or http://host (port defaults to 8188;
the scheme is optional and, if present, must be http). IPv6 literals are bracketed:
http://[::1]:8189. A malformed value is ignored with a one-line stderr warning rather than
breaking the call.
Verify it took effect — call server_info first. server_info wraps comfy env, which
resolves the local address by the same rules, so the server URL it reports is the resolved
address. Seeing :8189 there (and the server reported running) confirms the override is live.
Requires comfy-cli ≥ 1.14.0 (this server's floor). COMFY_LOCAL_URL itself landed after the
1.12.0 release and first shipped in 1.13.0 — below the enforced floor, so every published comfy-cli
this server accepts honors the variable. The floor is not a guarantee, though: the version guard
fails OPEN on a --version it can't parse, that errors, or that times out, so a source build or
fork older than 1.13.0 can still slip past it and silently ignore the variable. On any comfy-cli
without the support the variable is simply ignored (no error) and every tool keeps targeting
127.0.0.1:8188 — which is why the server_info check above is the way to confirm it took
effect, rather than the version alone.
Still reporting :8188? Three causes, all silent, in the order worth checking:
- The value never reached comfy-cli — it's in the wrong
envblock, or the client wasn't
restarted after the edit. The workspace/Python fieldsserver_inforeports confirm which
comfy-cli install you're actually talking to. - The value is malformed — comfy-cli ignores it and falls back to
127.0.0.1:8188, emitting
only a one-line stderr warning that this server's success path discards, so a typo
(https://…— onlyhttpis accepted; a non-numeric port; a port outside 1–65535) looks
exactly like the other two causes from the MCP side. Confirm by running
COMFY_LOCAL_URL=<your value> comfy envin a terminal and reading stderr; see
Accepted values above. - comfy-cli is too old — it predates the variable and ignored it.
server_info's
compatibility.comfy_cli_versionreports the detected version.
Precedence (comfy-cli resolves this, first match wins): an explicit --host/--port flag →
COMFY_LOCAL_URL → a comfy-cli-launched background server → 127.0.0.1:8188.
Which address variable do I want?
Two variables point ComfyUI work at an address, their names are similar, and they are not
alternative spellings of each other — they belong to different programs and are read at
different layers. Pick by which one you need; the table is the whole answer.
COMFYUI_URL (+ COMFYUI_HOST / COMFYUI_PORT) |
COMFY_LOCAL_URL |
|
|---|---|---|
| Read by | this MCP server (_comfy_target) |
comfy-cli (comfy_cli/local_address.py); this server never reads it |
| Means | "a ComfyUI on another machine I control" | "the ComfyUI on this machine is not on 127.0.0.1:8188" |
| How it acts | this server forwards --host / --port to the verbs that accept them |
comfy-cli resolves its own target from the environment it inherits |
| What it moves | the submit / job tools plus upload_file (run_workflow, generate_image, run_template, the jobs family, and input staging) — see what is and isn't remoted |
every verb, including the ones that take no --host / --port (comfy env, templates, models, download) |
| Reported as | a comfy_target block on server_info |
the resolved server URL on server_info — no comfy_target block |
| Use it for | a GPU box over Tailscale / a private network | a port clash, a second instance, a container publishing a different port |
Set one, not both. They resolve independently, so together they split your tools rather than
conflicting loudly: comfy-cli ranks an explicit --host / --port flag above COMFY_LOCAL_URL, so
the submit/job tools would follow COMFYUI_URL while every other verb followed COMFY_LOCAL_URL — two
different ComfyUIs, no error. For a non-default address on this machine prefer COMFY_LOCAL_URL
alone, since it also reaches the verbs COMFYUI_URL cannot.
Neither name is changing, and neither is deprecated. They look like a rename waiting to happen —
they are not, because only one of them is ours. COMFY_LOCAL_URL is comfy-cli's own published
variable: renaming it here would document a name nothing reads, and its "local" is a factual
address-scope word (comfy-cli's local target, as opposed to its cloud one), not this project's
branding. COMFYUI_URL is this server's, and already carries no "local" to strip. So there is no
old spelling to accept and no deprecation period to sit through — if you have either variable in an
MCP client config today, it keeps working unchanged.
Project anchoring
comfy-cli 1.15.0 ships a project/1 convention (comfy project init / comfy project status,
this server's project tool) — a comfy.yaml plus assets/ / fragments/ / blueprints/ /
outputs/ / .comfy/ under a root directory, with status reporting recent_runs and other
project-scoped state. comfy-cli resolves which project governs a call by walking up from
its own process's working directory only — there is no --project flag and no env var it reads
itself. That assumes a persistent shell session sitting inside the project tree; this server's own
working directory is whatever the MCP client happened to launch it from, arbitrary and unrelated to
any project the user has in mind — so out of the box, this server cannot participate in projects at
all.
Set COMFY_PROJECT to an absolute path to fix that: every comfy-cli spawn this server makes
then runs with that directory as its cwd, so comfy-cli's own cwd-walk resolves it exactly as if a
shell had cd'd there first. Read from the environment once per process (a value changed
mid-session is not picked up until restart) and validated on every spawn: the directory does not
need to contain comfy.yaml yet — call project(action="init") for that — but it does need to
exist, and it must be absolute. A relative value is rejected outright, never silently
resolved against this server's own (client-assigned, arbitrary) working directory — that resolution
would be exactly as non-deterministic as leaving COMFY_PROJECT unset while looking configured. A
set-but-relative or set-but-missing (or non-directory) value fails closed: the next comfy-cli
spawn raises rather than silently falling back to the unanch