node_pack
Author and publish ComfyUI custom-node packs: scaffold new nodes, edit files, verify they load in ComfyUI, and release to the Comfy Registry.
Instructions
Author, edit, test and publish YOUR OWN ComfyUI custom-node pack under /custom_nodes/. LOCAL-ONLY: it acts on the local filesystem and is meaningless for a remote --comfyui-url target. Every file-touching action (list_files, read, search, write, patch, git) is jailed to custom_nodes/ and needs COMFYUI_PATH; the one exception is action:"publish", which also accepts an explicit path to a pack directory ANYWHERE on this machine and therefore works without COMFYUI_PATH. To INSTALL or update someone else's pack use install_custom_node instead. Driven by the action parameter:
action:"scaffold" — Generate a new pack from a template into /custom_nodes//. Writes pyproject.toml (with the [tool.comfy] PublisherId/DisplayName/Icon table the Comfy Registry requires), init.py exporting NODE_CLASS_MAPPINGS / NODE_DISPLAY_NAME_MAPPINGS, and src/nodes.py containing a runnable sample node (INPUT_TYPES/RETURN_TYPES/FUNCTION/CATEGORY), plus .comfyignore and .gitignore. Optionally emits a web/js frontend stub (wiring WEB_DIRECTORY) and a GitHub Actions publish workflow (with_ci). This is the FIRST step of the author loop: scaffold here, then restart_comfyui to load it, test it, and finally action:"publish". Names must be a safe lowercase slug and cannot escape custom_nodes/; an existing non-empty directory is left untouched unless overwrite is true. Requires
nameanddisplay_name.action:"verify" — Test that a pack actually LOADS in ComfyUI — the middle step of the author loop. Restarts the local ComfyUI and waits for it to become ready, then checks that the pack's node class_types appear in /object_info. A node that fails to import (a missing dependency or a syntax error) simply never registers, so any missing class_types pinpoint a broken pack. Provide
class_typesexplicitly, or a packnamewhose init.py declares NODE_CLASS_MAPPINGS (the keys are inferred). Needs a managed local ComfyUI. Set restart:false to check the already-running server without restarting it.action:"publish" — Publish a local pack to the public Comfy Registry (registry.comfy.org) by running
comfy node publishinside the pack directory. First validates the pack's pyproject.toml has the required [project].name, [project].version and [tool.comfy].PublisherId (refusing the scaffold placeholder), then publishes using the API key from the REGISTRY_ACCESS_TOKEN environment variable (passed to comfy-cli via the environment, never via logged arguments). This is the LAST step of the author loop and an IRREVERSIBLE, EXTERNAL action: it creates/updates a PUBLIC registry version that this tool cannot undo. Requires comfy-cli installed and REGISTRY_ACCESS_TOKEN set. Givename(a folder under custom_nodes/) orpath(an explicit pack directory).action:"list_files" — List the files in one installed pack under custom_nodes// (read-only). Skips .git/, pycache/ and node_modules/. Use this to orient before action:"read" / action:"search" when diagnosing or editing a pack you found via bisect or install_custom_node (action:"fix"). Requires
pack.action:"read" — Read a slice of ONE file inside a pack (read-only), with bounded output so a huge file can't flood the context. Returns the requested line range with a truncation notice when clipped; long lines are chunked. Pair with action:"search" to locate the line, then action:"patch" or action:"write" to change it. Requires
path.action:"search" — Regex-search custom-node source under custom_nodes/ (read-only). Uses ripgrep when it's on PATH, otherwise a bounded built-in scanner (skips dot-dirs, pycache/node_modules, binary and >1 MiB files). Returns file/line/text matches with per-line and result caps. Use this to find where a node class, import, or error string lives before reading or patching. Requires
query.action:"write" — Create or overwrite ONE file inside a pack. Refuses to clobber an existing file unless overwrite is true, and creates parent directories by default. Use for whole-file edits or new files; for surgical edits prefer action:"patch". After writing, run action:"verify" and restart_comfyui to load the change. Requires
pathandcontent.action:"patch" — Apply a unified diff to custom-node source under custom_nodes/. Every touched path is jail-checked BEFORE any git call, then the patch is validated with
git apply --checkand only applied if the check passes (two-phase; never uses --unsafe-paths). Paths are relative to custom_nodes/ and may carry a/ b/ prefixes; works on non-repo packs too. Ideal for surgical edits located via action:"search". Requirespatch.action:"git" — Run a git operation inside one pack, selected by
git_action(status/diff/log/commit/push). Reads (status/diff/log) are always allowed. Writes (commit/push) require the environment flag COMFYUI_MCP_ALLOW_GIT_WRITES=1 (default OFF) and otherwise return a structured DISABLED_BY_CONFIG refusal so you can self-correct. commit requires amessageand stages either the givenpathsor all pack changes. This is the final step of the author loop after scaffold → write/patch → verify → restart_comfyui, before action:"publish". Requirespackandgit_action.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| glob | No | action:"list_files" — optional glob to filter entries (supports *, **, ?), matched against pack-relative paths. action:"search" — optional glob to restrict which files are searched (e.g. '**/*.py'). | |
| name | No | Pack folder name under <COMFYUI_PATH>/custom_nodes/. REQUIRED for action:"scaffold" — a safe lowercase slug (letters, digits, hyphens, underscores), e.g. 'my-cool-nodes', which becomes the directory under custom_nodes/ and the pyproject [project].name. For action:"verify", the pack whose __init__.py NODE_CLASS_MAPPINGS keys are inferred and checked when `class_types` is omitted. For action:"publish", the pack folder to publish (give this or `path`). | |
| pack | No | Pack folder name under custom_nodes/ (e.g. 'ComfyUI-Manager'). REQUIRED for action:"list_files" and action:"git". | |
| path | No | REQUIRED for action:"read" and action:"write": a pack-relative path under custom_nodes/, e.g. 'MyPack/nodes.py'. For action:"search", the pack-relative directory to search, or '.' for all packs (default '.'). For action:"publish" ONLY, this is instead an explicit absolute path to the pack directory to publish, and it overrides `name` when both are given. | |
| patch | No | action:"patch" — REQUIRED. A unified diff. File headers (---/+++) are read to determine touched paths, which must resolve inside custom_nodes/ (e.g. 'a/MyPack/nodes.py'). | |
| paths | No | action:"git" — pack-relative paths to stage/scope (jail-checked). Defaults to all pack changes. | |
| query | No | action:"search" — REQUIRED. Regular expression to search for. | |
| action | Yes | Which node-pack operation to perform. "scaffold" requires `name` + `display_name`; "list_files" requires `pack`; "read" requires `path`; "search" requires `query`; "write" requires `path` + `content`; "patch" requires `patch`; "git" requires `pack` + `git_action`. "verify" and "publish" have no required parameters — "verify" resolves the pack from `name` (or checks `class_types` directly), "publish" from `name` or `path`. | |
| content | No | action:"write" — REQUIRED. Full file contents to write. | |
| message | No | action:"git" — commit message (required for git_action 'commit'). | |
| restart | No | action:"verify" — restart ComfyUI before checking so newly-added packs load (default true). Set false to check the live server as-is. | |
| with_ci | No | action:"scaffold" — if true, also generate .github/workflows/publish_action.yml (Comfy-Org/publish-node-action; needs the REGISTRY_ACCESS_TOKEN repo secret) so pushing a pyproject.toml version bump auto-publishes (default false). | |
| category | No | action:"scaffold" — node menu category for the sample node (default 'custom'). | |
| max_chars | No | action:"read" — maximum characters to return (default 12000, min 500, max 24000 — hard clamps; values outside are silently pulled into range). action:"git" — maximum characters of git output to return (default 12000, min 500, max 24000 — hard clamps the runtime applies; values outside are silently pulled into range). | |
| overwrite | No | action:"scaffold" — overwrite template files in an existing pack directory instead of refusing (default false). action:"write" — overwrite an existing file instead of refusing (default false). | |
| git_action | No | action:"git" — REQUIRED. Which git operation to run: status/diff/log are read-only; commit/push require COMFYUI_MCP_ALLOW_GIT_WRITES=1. Named `git_action` rather than `action` only because `action` is this tool's dispatch field; the git operation itself is unchanged. | |
| line_count | No | action:"read" — number of lines to return (default 240, max 800). | |
| start_line | No | action:"read" — 1-based line to start at (default 1). | |
| class_types | No | action:"verify" — explicit NODE_CLASS_MAPPINGS keys to confirm are registered in /object_info. Takes precedence over inferring from `name`. | |
| create_dirs | No | action:"write" — create missing parent directories (default true). | |
| description | No | action:"scaffold" — short description written to pyproject [project].description. | |
| max_entries | No | action:"list_files" — maximum entries to return (default 500, max 2000 — a hard clamp). The walk STOPS at this many, so a capped result is not the pack's full file list. | |
| max_results | No | action:"search" — maximum matches to return (default 50, max 100). The scan STOPS at this many, so a capped result is not a complete match set. | |
| display_name | No | action:"scaffold" — REQUIRED. Human-readable name shown in the ComfyUI node menu and the registry listing. | |
| publisher_id | No | action:"scaffold" — your Comfy Registry publisher id, stamped into [tool.comfy].PublisherId. If omitted a placeholder is written that you must replace before publishing. | |
| with_frontend | No | action:"scaffold" — if true, also generate a web/js/<name>.js extension stub and set WEB_DIRECTORY (default false). | |
| case_sensitive | No | action:"search" — match case-sensitively (default false). |