comfyui-mcp 0.52.192 → 0.52.193

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";
@@ -23850,21 +23850,27 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
23850
23850
  resolved.push({ kind, dataUrl, filename, caption: item.caption });
23851
23851
  }
23852
23852
  else {
23853
- // ComfyUI /view ref. A browser panel fetches it same-origin — but a
23854
- // HEADLESS (mobile/remote) client can't reach ComfyUI, so resolve the
23855
- // bytes HERE and inline them as a data URL. Best-effort: any failure
23856
- // (fetch error, non-media, too big) falls back to forwarding the ref,
23857
- // which the client renders as a caption card.
23853
+ // ComfyUI /view ref. A local browser panel fetches it same-origin —
23854
+ // but a HEADLESS client cannot, and a REMOTE/CLOUD browser panel's
23855
+ // /view is a different route from get_image's authenticated client
23856
+ // (the panel 404s while get_image succeeds #2861).
23858
23857
  //
23859
23858
  // #2012 — judge the DESTINATION, not the address. `ctx.tabId` may be
23860
23859
  // a scope / prefix / alias; `isHeadless` on the address is a lookup
23861
23860
  // miss that answers false and a phone gets a /view URL it cannot
23862
- // fetch. Positive requirement: inline only a proven headless
23863
- // destination.
23861
+ // fetch. Positive requirement: inline a proven headless destination,
23862
+ // or a proven interactive destination whose ComfyUI is not local.
23863
+ //
23864
+ // #877/#899 — remote bytes come from fetchImage (the get_image
23865
+ // path), never a same-named file on the orchestrator disk. A remote
23866
+ // interactive tab that cannot be inlined is refused rather than
23867
+ // handed a viewRef the panel will drop (painted:0).
23864
23868
  let inlined = false;
23865
- if (resolveClientKind(ctx).kind === "headless") {
23869
+ const dest = resolveClientKind(ctx);
23870
+ const remoteTarget = isRemoteMode() || isCloudMode();
23871
+ if (dest.kind === "headless") {
23866
23872
  try {
23867
- const base = (process.env.COMFYUI_URL ?? "http://127.0.0.1:8188").replace(/\/+$/, "");
23873
+ const base = getComfyUIBaseUrl().replace(/\/+$/, "");
23868
23874
  const qs = new URLSearchParams({ filename: src.filename, type: src.type ?? "output" });
23869
23875
  if (src.subfolder)
23870
23876
  qs.set("subfolder", src.subfolder);
@@ -23872,9 +23878,10 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
23872
23878
  if (resp.ok) {
23873
23879
  const mime = resp.headers.get("content-type") ?? "";
23874
23880
  const buf = Buffer.from(await resp.arrayBuffer());
23875
- if ((mime.startsWith("image/") || mime.startsWith("video/")) && buf.length <= MAX_BYTES) {
23881
+ const kindFromMime = mediaKindFromMime(mime);
23882
+ if (kindFromMime && kindFromMime !== "audio" && buf.length <= MAX_BYTES) {
23876
23883
  resolved.push({
23877
- kind: mime.startsWith("video/") ? "video" : "image",
23884
+ kind: kindFromMime,
23878
23885
  dataUrl: `data:${mime};base64,${buf.toString("base64")}`,
23879
23886
  filename: src.filename,
23880
23887
  caption: item.caption,
@@ -23887,6 +23894,41 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
23887
23894
  // fall through to the viewRef path
23888
23895
  }
23889
23896
  }
23897
+ else if (remoteTarget && dest.kind === "interactive") {
23898
+ try {
23899
+ const fetched = await fetchImage(src.filename, viewRefTypeForFetch(src.type), src.subfolder ?? "", { maxBytes: MAX_BYTES });
23900
+ const mime = fetched.mimeType.split(";")[0].trim();
23901
+ const buf = Buffer.from(fetched.base64, "base64");
23902
+ const kindFromMime = mediaKindFromMime(mime);
23903
+ if (kindFromMime && buf.length <= MAX_BYTES) {
23904
+ resolved.push({
23905
+ kind: kindFromMime,
23906
+ dataUrl: `data:${mime};base64,${fetched.base64}`,
23907
+ filename: src.filename,
23908
+ caption: item.caption,
23909
+ });
23910
+ inlined = true;
23911
+ }
23912
+ else {
23913
+ const detail = !kindFromMime
23914
+ ? `The MCP client fetched it, but the body was not media (content-type "${mime || "unset"}").`
23915
+ : `The MCP client fetched it, but it is ${buf.length} bytes — over the ${MAX_BYTES} byte inline cap.`;
23916
+ return fail(remoteViewRefInlineFailedNote({
23917
+ filename: src.filename,
23918
+ detail,
23919
+ capBytes: MAX_BYTES,
23920
+ }));
23921
+ }
23922
+ }
23923
+ catch (err) {
23924
+ const detail = err instanceof Error ? err.message : String(err);
23925
+ return fail(remoteViewRefInlineFailedNote({
23926
+ filename: src.filename,
23927
+ detail,
23928
+ capBytes: MAX_BYTES,
23929
+ }));
23930
+ }
23931
+ }
23890
23932
  if (!inlined) {
23891
23933
  resolved.push({
23892
23934
  kind: "viewRef",