UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

205 lines • 8.13 kB
export class ComfyUIError extends Error { code; details; constructor(message, code, details) { super(message); this.code = code; this.details = details; this.name = "ComfyUIError"; } toToolResult() { return { isError: true, content: [ { type: "text", text: JSON.stringify({ error: this.code, message: this.message, details: this.details, }), }, ], }; } } export class ConnectionError extends ComfyUIError { constructor(message) { super(message, "CONNECTION_ERROR"); this.name = "ConnectionError"; } } export class WorkflowExecutionError extends ComfyUIError { constructor(message, details) { super(message, "WORKFLOW_EXECUTION_ERROR", details); this.name = "WorkflowExecutionError"; } } export class ValidationError extends ComfyUIError { constructor(message) { super(message, "VALIDATION_ERROR"); this.name = "ValidationError"; } } export class RegistryError extends ComfyUIError { constructor(message, details) { super(message, "REGISTRY_ERROR", details); this.name = "RegistryError"; } } export class ModelError extends ComfyUIError { constructor(message, details) { super(message, "MODEL_ERROR", details); this.name = "ModelError"; } } export class ProcessControlError extends ComfyUIError { /** * Set when the ComfyUI server IS reachable (answered /system_stats) but its * listening socket could not be mapped to a local PID — so callers surface a * precise diagnostic instead of a flat "no process" message (issue #449). */ reachableButNoPid; /** * Set when the server that answered could NOT be bound to the process we would * act on — the instance behind the port changed while it was being identified * (#776). Callers must surface the diagnostic and touch nothing: killing on an * identity we could not confirm is exactly the wrong-instance hazard. */ identityAmbiguous; constructor(message, details) { super(message, "PROCESS_CONTROL_ERROR", details); this.name = "ProcessControlError"; } } export class RemoteModeError extends ComfyUIError { constructor(message, details) { super(message, "REMOTE_MODE_ERROR", details); this.name = "RemoteModeError"; } } export class NodeSnapshotError extends ComfyUIError { constructor(message, details) { super(message, "NODE_SNAPSHOT_ERROR", details); this.name = "NodeSnapshotError"; } } export class NodeBisectError extends ComfyUIError { constructor(message, details) { super(message, "NODE_BISECT_ERROR", details); this.name = "NodeBisectError"; } } /** * Unwrap the `cause` chain of an error thrown by `fetch()` itself — a * network-layer failure, not an HTTP status. * * undici surfaces every one of these as `TypeError: fetch failed`, a message * that says nothing: not the host, not the port, not whether it was DNS, a * refused connection, a timeout, or a bad certificate. The actionable detail is * one level down on `.cause`, as an Error carrying a `code` (ENOTFOUND, * ECONNREFUSED, ECONNRESET, UND_ERR_CONNECT_TIMEOUT, ERR_TLS_CERT_ALTNAME_INVALID…). * * The returned message always BEGINS with the original text, so existing * transient-error matchers that look for /fetch failed/ keep matching. */ export function describeFetchFailure(err) { const parts = []; let code; let cur = err; const seen = new Set(); while (cur instanceof Error && !seen.has(cur)) { seen.add(cur); const c = cur.code; if (typeof c === "string" && !code) code = c; const label = typeof c === "string" ? `${cur.message} (${c})` : cur.message; if (label && !parts.includes(label)) parts.push(label); cur = cur.cause; } if (parts.length === 0) parts.push(String(err)); return { message: parts.join(": "), code }; } /** True for the opaque undici network failure whose detail lives on `.cause`. */ export function isBareFetchFailure(err) { return err instanceof Error && /^fetch failed$/i.test(err.message.trim()); } /** The host of `url`, or undefined if it will not parse. Never throws. */ function hostOf(url) { try { return new URL(url).host || undefined; } catch { return undefined; } } /** * Say UNREACHABLE, and say which host (#1136). * * A `fetch()` that never completed is not a result. When the answer it would * have carried is a LIST, the difference matters enormously: an empty list reads * as "nothing matched", so the reader concludes the thing they are looking for * does not exist and goes on trying variations of a query that can never * succeed. That is what happened to a user behind a national filter, who was * told to search ComfyUI Manager and reported 我这边搜不到任何内容 — "I can't * find anything when I search". Three rounds of advice were spent before anyone * suspected the door itself could not open. * * So the message states three things the bare error does not: * - the HOST, so someone behind a filter recognises their own situation; * - that NOTHING WAS SEARCHED, so absence was never established; * - that a blocked network is a common cause, since the user who most needs * this cannot be expected to infer it from `ENOTFOUND`. * * `what` names the operation as a noun phrase — "the custom-node search". * `opts.remedy` adds a host-specific escape hatch where one exists (HuggingFace * has HF_ENDPOINT; the ComfyUI Registry has none). * * A timeout is called a timeout: the host may well be reachable and merely slow, * and telling a user their network is blocked when it is not is its own wrong * answer. The underlying detail is kept at the end so nothing is lost. */ export function unreachableHostMessage(err, url, what, opts) { const { message: detail, code } = describeFetchFailure(err); const host = hostOf(url); const named = host ?? "the remote host"; const timedOut = code === "UND_ERR_CONNECT_TIMEOUT" || code === "UND_ERR_HEADERS_TIMEOUT" || code === "ETIMEDOUT" || (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")); return { code, message: (timedOut ? `${named} did not respond in time, so ${what} did not complete. ` : `Could not reach ${named}, so ${what} did not run. `) + `This is NOT an empty result — nothing was queried, so nothing follows about whether ` + `what you asked for exists. ` + (timedOut ? `Retry; if it keeps timing out, the host may be throttled or filtered on this network.` : `${named} is commonly blocked by corporate, campus and national network filters — ` + `if other sites work but this one never does, that is the likely cause, and a VPN, ` + `proxy or different network is what changes it.`) + (opts?.remedy ? ` ${opts.remedy}` : "") + ` (${detail})`, }; } export function errorToToolResult(err) { if (err instanceof ComfyUIError) return err.toToolResult(); // A bare "fetch failed" reaching a tool result tells the reader nothing and // has repeatedly been read as "ComfyUI is broken" when it was a wrong port or // a stale target (#952, #954, #411). Expand the cause chain; the original // message stays at the front so nothing matching on it changes. const message = isBareFetchFailure(err) ? describeFetchFailure(err).message : err instanceof Error ? err.message : String(err); return { isError: true, content: [{ type: "text", text: message }], }; } //# sourceMappingURL=errors.js.map