comfyui-mcp 0.52.192 → 0.52.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -28,7 +28,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
28
28
|
import { resolveToolSurfacePolicy, toolActionPolicyError, toolAllowed, } from "../tools/tool-surface-filter.js";
|
|
29
29
|
import { logger as toolPolicyLogger } from "../utils/logger.js";
|
|
30
30
|
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
31
|
-
import { forwardedByReferenceNote, oversizedInlineRefusal, resolveServableViewRef, stageFileIntoServedDir, stagedForDisplayNote, readShowMediaAck, unaccountedShowMediaNote, unverifiedViewRefNote, } from "../services/comfy-view-ref.js";
|
|
31
|
+
import { forwardedByReferenceNote, mediaKindFromMime, oversizedInlineRefusal, remoteViewRefInlineFailedNote, resolveServableViewRef, stageFileIntoServedDir, stagedForDisplayNote, readShowMediaAck, unaccountedShowMediaNote, unverifiedViewRefNote, viewRefTypeForFetch, } from "../services/comfy-view-ref.js";
|
|
32
32
|
import { extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
34
|
import { comfyuiFetch } from "../comfyui/fetch.js";
|
|
@@ -205,7 +205,7 @@ import { QueueMonitor, RUNNING_UNCONFIRMED_MS } from "../services/queue-monitor.
|
|
|
205
205
|
import { stallThresholdMs } from "../services/stall-threshold.js";
|
|
206
206
|
import { RunCompletions } from "./run-completion-journal.js";
|
|
207
207
|
import { AskAnswers, askFingerprint, PANEL_ASK_ID_PREFIX, } from "./ask-answer-journal.js";
|
|
208
|
-
import { getLogs, getQueueVerified, resetClient, resetObjectInfoCache, } from "../comfyui/client.js";
|
|
208
|
+
import { fetchImage, getLogs, getQueueVerified, resetClient, resetObjectInfoCache, } from "../comfyui/client.js";
|
|
209
209
|
import { convertUiToApi, collectNodeTypes } from "../services/workflow-converter.js";
|
|
210
210
|
import { outputNodeObjectInfoForPanel, recoverOutputNodeScopedRun, stampOutputNodeFlagsOnToolResult, } from "../services/output-node.js";
|
|
211
211
|
import { captureComfyUITargetFence, restartComfyUI, preflightLocalRestart, readServingArgv, resolveVerifiedProxyRestartTarget, describeArgvDrift, recordRestartDispatch, clearRestartDispatch, getRestartDispatchRecord, RESTART_DISPATCH_CAUSATION_WINDOW_MS, PROCESS_WIDE_RESTART_DISPATCH_TOKEN, __processControlTestHooks, targetFenceMatchesCurrent, targetFencesEqual, } from "../services/process-control.js";
|
|
@@ -21555,10 +21555,14 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
21555
21555
|
.optional()
|
|
21556
21556
|
.describe("Reconnect pasted nodes' inputs to existing nodes where they line up (default false)."),
|
|
21557
21557
|
}, async (args, ctx) => ctx.call({ cmd: "graph_paste_nodes", pos: args.pos, connect_inputs: args.connect_inputs }, 15000)),
|
|
21558
|
-
def("panel_save_subgraph", "Save a SUBGRAPH node to the user's reusable blueprint LIBRARY (publish), so it can be dropped into any workflow later. Pass node_id to pick the subgraph node (else a single selected subgraph node is used) and name to title the blueprint (defaults to the node's title). Runs programmatically — NO save dialog pops. The blueprint becomes the addable type 'SubgraphBlueprint.<name>' (use panel_add_subgraph or panel_list_subgraphs). Returns {saved: {name, type}}.", {
|
|
21558
|
+
def("panel_save_subgraph", "Save a SUBGRAPH node to the user's reusable blueprint LIBRARY (publish), so it can be dropped into any workflow later. Pass node_id to pick the subgraph node (else a single selected subgraph node is used) and name to title the blueprint (defaults to the node's title). On a name collision with an existing USER blueprint, pass overwrite:true to replace it in place (the panel already implements this; without the flag the call is refused and names overwrite:true as the remedy). Bundled/global blueprints cannot be overwritten. Runs programmatically — NO save dialog pops. The blueprint becomes the addable type 'SubgraphBlueprint.<name>' (use panel_add_subgraph or panel_list_subgraphs). Returns {saved: {name, type}}.", {
|
|
21559
21559
|
node_id: nodeId().optional().describe("Subgraph node id to publish (is_subgraph=true). Omit to use the selected subgraph node."),
|
|
21560
21560
|
name: z.string().optional().describe("Blueprint name. Defaults to the subgraph node's title."),
|
|
21561
|
-
|
|
21561
|
+
overwrite: z
|
|
21562
|
+
.boolean()
|
|
21563
|
+
.optional()
|
|
21564
|
+
.describe("Replace an existing USER blueprint of the same name in place. Required on a name collision; omitted/false refuses. Bundled/global blueprints stay refused."),
|
|
21565
|
+
}, async (args, ctx) => ctx.call({ cmd: "graph_save_subgraph", node_id: args.node_id, name: args.name, overwrite: args.overwrite }, 20000)),
|
|
21562
21566
|
def("panel_list_subgraphs", "List the saved subgraph BLUEPRINTS in the user's library (from panel_save_subgraph, plus any global/bundled ones). Each entry has {name, type, display_name, description, is_global} — use name/type with panel_add_subgraph to drop it onto the canvas. Read-only.\n\nTOKEN-BOUNDED like the other reads (panel#690). The reply's count field is always the LIBRARY TOTAL, so compare it against the returned array. When entries are withheld the reply carries truncated:true, a returned field, and a note — an absent entry in a TRUNCATED list is NOT evidence the blueprint does not exist, so narrow with `filter` (or raise `limit`, up to 500) before concluding anything. When a filter is applied the reply's matched field reports how many the filter selected, distinct from the count field, so matched:0 against a non-zero count means the filter missed, not that the library is empty.", {
|
|
21563
21567
|
filter: z
|
|
21564
21568
|
.string()
|
|
@@ -23850,21 +23854,27 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
23850
23854
|
resolved.push({ kind, dataUrl, filename, caption: item.caption });
|
|
23851
23855
|
}
|
|
23852
23856
|
else {
|
|
23853
|
-
// ComfyUI /view ref. A browser panel fetches it same-origin —
|
|
23854
|
-
// HEADLESS
|
|
23855
|
-
//
|
|
23856
|
-
// (
|
|
23857
|
-
// which the client renders as a caption card.
|
|
23857
|
+
// ComfyUI /view ref. A local browser panel fetches it same-origin —
|
|
23858
|
+
// but a HEADLESS client cannot, and a REMOTE/CLOUD browser panel's
|
|
23859
|
+
// /view is a different route from get_image's authenticated client
|
|
23860
|
+
// (the panel 404s while get_image succeeds — #2861).
|
|
23858
23861
|
//
|
|
23859
23862
|
// #2012 — judge the DESTINATION, not the address. `ctx.tabId` may be
|
|
23860
23863
|
// a scope / prefix / alias; `isHeadless` on the address is a lookup
|
|
23861
23864
|
// miss that answers false and a phone gets a /view URL it cannot
|
|
23862
|
-
// fetch. Positive requirement: inline
|
|
23863
|
-
// destination.
|
|
23865
|
+
// fetch. Positive requirement: inline a proven headless destination,
|
|
23866
|
+
// or a proven interactive destination whose ComfyUI is not local.
|
|
23867
|
+
//
|
|
23868
|
+
// #877/#899 — remote bytes come from fetchImage (the get_image
|
|
23869
|
+
// path), never a same-named file on the orchestrator disk. A remote
|
|
23870
|
+
// interactive tab that cannot be inlined is refused rather than
|
|
23871
|
+
// handed a viewRef the panel will drop (painted:0).
|
|
23864
23872
|
let inlined = false;
|
|
23865
|
-
|
|
23873
|
+
const dest = resolveClientKind(ctx);
|
|
23874
|
+
const remoteTarget = isRemoteMode() || isCloudMode();
|
|
23875
|
+
if (dest.kind === "headless") {
|
|
23866
23876
|
try {
|
|
23867
|
-
const base = (
|
|
23877
|
+
const base = getComfyUIBaseUrl().replace(/\/+$/, "");
|
|
23868
23878
|
const qs = new URLSearchParams({ filename: src.filename, type: src.type ?? "output" });
|
|
23869
23879
|
if (src.subfolder)
|
|
23870
23880
|
qs.set("subfolder", src.subfolder);
|
|
@@ -23872,9 +23882,10 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
23872
23882
|
if (resp.ok) {
|
|
23873
23883
|
const mime = resp.headers.get("content-type") ?? "";
|
|
23874
23884
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
23875
|
-
|
|
23885
|
+
const kindFromMime = mediaKindFromMime(mime);
|
|
23886
|
+
if (kindFromMime && kindFromMime !== "audio" && buf.length <= MAX_BYTES) {
|
|
23876
23887
|
resolved.push({
|
|
23877
|
-
kind:
|
|
23888
|
+
kind: kindFromMime,
|
|
23878
23889
|
dataUrl: `data:${mime};base64,${buf.toString("base64")}`,
|
|
23879
23890
|
filename: src.filename,
|
|
23880
23891
|
caption: item.caption,
|
|
@@ -23887,6 +23898,41 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
23887
23898
|
// fall through to the viewRef path
|
|
23888
23899
|
}
|
|
23889
23900
|
}
|
|
23901
|
+
else if (remoteTarget && dest.kind === "interactive") {
|
|
23902
|
+
try {
|
|
23903
|
+
const fetched = await fetchImage(src.filename, viewRefTypeForFetch(src.type), src.subfolder ?? "", { maxBytes: MAX_BYTES });
|
|
23904
|
+
const mime = fetched.mimeType.split(";")[0].trim();
|
|
23905
|
+
const buf = Buffer.from(fetched.base64, "base64");
|
|
23906
|
+
const kindFromMime = mediaKindFromMime(mime);
|
|
23907
|
+
if (kindFromMime && buf.length <= MAX_BYTES) {
|
|
23908
|
+
resolved.push({
|
|
23909
|
+
kind: kindFromMime,
|
|
23910
|
+
dataUrl: `data:${mime};base64,${fetched.base64}`,
|
|
23911
|
+
filename: src.filename,
|
|
23912
|
+
caption: item.caption,
|
|
23913
|
+
});
|
|
23914
|
+
inlined = true;
|
|
23915
|
+
}
|
|
23916
|
+
else {
|
|
23917
|
+
const detail = !kindFromMime
|
|
23918
|
+
? `The MCP client fetched it, but the body was not media (content-type "${mime || "unset"}").`
|
|
23919
|
+
: `The MCP client fetched it, but it is ${buf.length} bytes — over the ${MAX_BYTES} byte inline cap.`;
|
|
23920
|
+
return fail(remoteViewRefInlineFailedNote({
|
|
23921
|
+
filename: src.filename,
|
|
23922
|
+
detail,
|
|
23923
|
+
capBytes: MAX_BYTES,
|
|
23924
|
+
}));
|
|
23925
|
+
}
|
|
23926
|
+
}
|
|
23927
|
+
catch (err) {
|
|
23928
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
23929
|
+
return fail(remoteViewRefInlineFailedNote({
|
|
23930
|
+
filename: src.filename,
|
|
23931
|
+
detail,
|
|
23932
|
+
capBytes: MAX_BYTES,
|
|
23933
|
+
}));
|
|
23934
|
+
}
|
|
23935
|
+
}
|
|
23890
23936
|
if (!inlined) {
|
|
23891
23937
|
resolved.push({
|
|
23892
23938
|
kind: "viewRef",
|