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

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).**

ComfyUI self-hosted MCP Python

CI Release License: AGPL-3.0-or-later OR Commercial

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.

  1. 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-mcp does not install
    comfy-cli: it is not a declared dependency of this package, because the server runs whichever
    comfy binary your PATH (or COMFY_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 "comfy not found on
    PATH", which is a missing engine, not a broken install.

    pip install comfy-mcp puts a comfy-mcp console script on your PATH; 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's PATH, which is exactly what COMFY_BIN is for; see Prerequisites.)
    comfy-mcp --version confirms it landed — but don't run comfy-mcp itself 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 --help says 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 from comfy-local-mcp first — installing
    comfy-mcp alone will not clean up after the old name.

  2. Launch ComfyUI and leave it running:

    comfy launch
  3. 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.

  4. Ask your agent to run a workflow. For example:

    "Confirm my local ComfyUI is running, then run the workflow at
    ~/workflows/txt2img.json and show me the image."

    Under the hood the agent calls server_info to confirm ComfyUI is up, run_workflow to
    execute your workflow JSON (API-format or a UI export), and fetch_outputs to collect the
    result. No hand-authored workflow? Ask it to start from a template instead — it can
    search_templates, fetch_template to write a runnable JSON, and run that — and
    fetch_template tells 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/
  1. Uninstall the old distribution first. Installing the new one leaves the old one in place,
    and its comfy-local-mcp script stays on your PATH pointing 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 .`)
  2. Change "command" to comfy-mcp in 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.

  3. Rename the failure-log env var if you set it. COMFY_LOCAL_MCP_DEBUG_LOG is 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. Use COMFY_MCP_DEBUG_LOG; see
    Failure log (opt-in).

  4. 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 empty failures.jsonl rather 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-mcp

    The glob carries the two rotations (failures.jsonl.1, failures.jsonl.2) along with the
    live file, and mkdir -p first 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-mcp in 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 a comfy-local entry, 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_BIN env entry is shown in every example. Drop it if comfy is already on the
environment your client launches the server with; keep it (pointing at the absolute path) if
it isn't. COMFY_API_KEY is 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, ~/Desktop and ~/Downloads — or grant your
client Full Disk Access. macOS blocks apps (and everything they launch) from reading those
folders, so an install there fails with Operation not permitted before 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:

  1. Sidebar → Customize → Connectors.
  2. Click + in the Connectors header → Add custom connector.
  3. Name it (e.g. Comfy Cloud MCP), set Remote MCP server URL to
    https://cloud.comfy.org/mcp, and click Add.
  4. 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

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 —
    the comfy logs verb, the envelope/1 contract, the comfy outdated verb behind
    server_info's freshness block, and the machine-readable login_url event auth_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 (--background plus
    download-status / downloads / download-cancel), models search's cross-folder walk, the
    templates gallery cache TTL, and comfy run's --allow-spend interlock. 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 the comfy-mcp package, because
    the binary that matters is whichever one PATH / COMFY_BIN resolves to rather than one pinned
    into this server's environment, so the floor above is enforced at runtime against that binary
    (the rationale lives in pyproject.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 --version it 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-cli can 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, the launch_comfyui tool), 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_BIN override (optional). By default the server calls comfy from PATH. MCP
    clients launch the server with their own environment, which often does not include your
    shell's PATH — so if comfy lives in a virtualenv or a non-standard location, set
    COMFY_BIN to 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's PATH. The server prepends the resolved binary's directory to
    the PATH it hands comfy-cli, because some comfy-cli commands (notably the background
    launch) re-invoke comfy by 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 like COMFY_BIN — an MCP client launches the server with its own
    minimal environment, so a key from your shell won't reach it. Set COMFY_API_KEY in the
    client registration env block. 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 targets 127.0.0.1:8188. Set
    COMFYUI_URL (e.g. http://gpu-box:8188) — or the COMFYUI_HOST (+ optional COMFYUI_PORT,
    default 8188) 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_model writes 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 to 1 when 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
    client env block re-points every tool. For a ComfyUI on this machine that isn't on
    127.0.0.1:8188 (e.g. :8189 because 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 labelled comfy-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 the DO_NOT_TRACK /
    COMFY_NO_TELEMETRY environment 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's default template (ComfyUI's own
    basic SD1.5 text-to-image graph), filling its positive-prompt slot 6.text and, when you pass
    checkpoint, its ckpt_name slot. 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 the run_template tool 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:

  1. Is the work even local? hardware describes the machine this server runs on, and that is where most tools execute. A comfy_target block (Driving a remote ComfyUI) diverts every tool that submits a job — run_workflow, generate_image, run_template — along with the queue/jobs tools, 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 its host is neither loopback (anything in 127.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 a host the 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_URL is a second signal worth checking: it repoints comfy-cli without producing a comfy_target block.
  2. 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 while vram_bytes is the slice you actually get, and rounding a 6 GB A100 slice up into the ≥ 24 GB band would OOM the run. On Apple Silicon gpu.vram_bytes is null (with gpu.unified_memory true) and the figure is ram_bytes — an Apple-only substitution.
  3. If the figure is missing, ask. A null or zero vram_bytes on 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 missing gpu object, or a missing/zero ram_bytes on 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: no hardware payload encodes it, because a null or missing gpu is 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.
  4. 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:

  1. Read the headroom. system_stats() returns per-device vram_free / vram_total straight from the live ComfyUI. Compare vram_free against what the workflow's checkpoint needs.
  2. 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: 0 on the next /api/generate (or /api/chat) call, which unloads the model as soon as that call returns, or run ollama stop <model>.
    • LM Studio — let the model's TTL / JIT auto-evict expire, or unload explicitly with lms unload <model> (lms unload --all for everything).
    • llama.cpp (llama-server) — in router mode (started with no -m, or with --models-dir) POST /models/unload with {"model": "<name>"} unloads one model; GET /models lists what is currently loaded. Independently of router mode, --sleep-idle-seconds N makes 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.
  3. 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-read system_stats() to confirm the VRAM actually came back before committing to a big run.
  4. Run the job — run_workflow(...) / run_template(...) / generate_image(...) — then collect with fetch_outputs(...).
    (This whole recipe is about this machine's VRAM: system_stats and free_memory are never
    remoted, so with a COMFYUI_URL configured steps 1–3 measure and free the wrong box while step 4
    submits to the remote. See Driving a remote ComfyUI.)
  5. The client reloads its LLM afterwards, again through its own runtime. Ollama, LM Studio and a sleep-idle llama-server all reload on demand, so for those "reload" is just the next request; a single-model llama-server stopped 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):

  1. a per-call flag (not exposed by this server);
  2. a live Comfy Cloud OAuth session (comfy cloud login);
  3. the COMFY_API_KEY environment variable;
  4. 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 --yes and 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 calling partner_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 consent is missing the tool refuses up front rather than spending on the
    assumption something would have stopped it. pip install -U comfy-cli to 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=True asks you first, naming the template, on any client that can elicit.
    Approve → --allow-spend is forwarded. Decline → the tool errors and comfy-cli is never started.
    As with partner_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 always does not apply here. That setting is scoped to
    comfy generate — comfy run-template never 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 plain http:// with no base path and no query or fragment:
    an https:// 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 point COMFYUI_URL at that.
  • COMFYUI_HOST (+ optional COMFYUI_PORT, default 8188) — e.g. COMFYUI_HOST=gpu-box.
    A port without a host (setting only COMFYUI_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_node in particular writes into this machine's ComfyUI
    workspace and venv, so with COMFYUI_URL set the pack lands where the run isn't.
  • Catalog / partner verbs — search_templates / search_models / download_model /
    partner_generate — this server forwards no --host/--port to 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, and download_model cannot 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 while COMFYUI_URL/COMFYUI_HOST is 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 with COMFY_MCP_REMOTE_SHARED_MODELS=1 and 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/--port either, 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, which comfy download then 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_check block on fetch_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_info does 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:

  1. The value never reached comfy-cli — it's in the wrong env block, or the client wasn't
    restarted after the edit. The workspace/Python fields server_info reports confirm which
    comfy-cli install you're actually talking to.
  2. 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://… — only http is 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 env in a terminal and reading stderr; see
    Accepted values above.
  3. comfy-cli is too old — it predates the variable and ignored it. server_info's
    compatibility.comfy_cli_version reports 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