comfyui-mcp 0.52.18 → 0.52.20

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.
@@ -40,8 +40,9 @@ import { isPanelAnsweredError } from "../services/panel-answered.js";
40
40
  import { isPreExecutorRefusal } from "../services/panel-refusal.js";
41
41
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
42
42
  import { parse as parseYaml } from "yaml";
43
- import { SEMVER_RE } from "../services/ui-bridge.js";
43
+ import { requiredPanelVersion, SEMVER_RE } from "../services/ui-bridge.js";
44
44
  import { compareSemver } from "../services/self-update.js";
45
+ import { describeInstallPanelAction } from "../services/panel-recovery.js";
45
46
  import { primePanelBase, verifiedPanelDiskVersion, } from "../services/panel-workspace.js";
46
47
  import { conversationOfScopeAddress, isScopeAddress, shortTabId } from "../services/session-scope.js";
47
48
  import { NODE_ID_MESSAGE, NODE_ID_PATTERN, normalizeNodeId } from "./node-id.js";
@@ -2551,6 +2552,80 @@ function withLoraManagerAutocompleteNote(res) {
2551
2552
  return res;
2552
2553
  return appendToolResultText(res, LORA_MANAGER_AUTOCOMPLETE_NOTE);
2553
2554
  }
2555
+ /**
2556
+ * Types the CURRENT panel's add-node guard allowlists (comfyui-mcp-panel
2557
+ * `web/js/lib/node-resolve.js` `FRONTEND_ONLY_NODE_TYPES`). An older panel
2558
+ * without that allowlist refuses them as "the ComfyUI backend does not provide
2559
+ * it" and points at unrelated failed-import packs (#1828).
2560
+ *
2561
+ * Kept as a copy of the panel's list rather than the orchestrator's
2562
+ * `FRONTEND_ONLY_NODE_TYPES` (workflow-converter): that set is the runtime
2563
+ * classifier's skip list and deliberately omits third-party names. Adding
2564
+ * rgthree types there would change check_runtime, which is not this bug.
2565
+ */
2566
+ const PANEL_ADD_NODE_FRONTEND_ONLY_TYPES = new Set([
2567
+ "Note",
2568
+ "MarkdownNote",
2569
+ "Reroute",
2570
+ "PrimitiveNode",
2571
+ "Fast Bypasser (rgthree)",
2572
+ "Fast Muter (rgthree)",
2573
+ "Fast Groups Bypasser (rgthree)",
2574
+ "Fast Groups Muter (rgthree)",
2575
+ "Label (rgthree)",
2576
+ "Reroute (rgthree)",
2577
+ "Node Collector (rgthree)",
2578
+ "SetNode",
2579
+ "GetNode",
2580
+ ]);
2581
+ function isUnknownBackendNodeRefusal(res) {
2582
+ if (!res.isError)
2583
+ return false;
2584
+ return /Unknown node type .+ — the ComfyUI backend does not provide it/i.test(textOfToolResult(res));
2585
+ }
2586
+ function tabAdvertisedPanelVersion(ctx) {
2587
+ const fn = ctx.bridge.advertisedPanelVersion;
2588
+ if (typeof fn !== "function")
2589
+ return undefined;
2590
+ let reading;
2591
+ try {
2592
+ reading = fn.call(ctx.bridge, ctx.tabId);
2593
+ }
2594
+ catch {
2595
+ return undefined;
2596
+ }
2597
+ const v = reading?.version;
2598
+ return typeof v === "string" && SEMVER_RE.test(v.trim()) ? v.trim() : undefined;
2599
+ }
2600
+ /**
2601
+ * #1828 — an allowlisted frontend-only type refused as "backend does not
2602
+ * provide it" is version skew when the tab's advertised panel is below this
2603
+ * orchestrator's floor, not a missing pack. Keep the panel's refusal and name
2604
+ * the pack update + hard-refresh; a restart alone leaves cached JS running the
2605
+ * old guard.
2606
+ */
2607
+ function withFrontendOnlyPanelSkewNote(res, classType, ctx) {
2608
+ if (typeof classType !== "string")
2609
+ return res;
2610
+ if (!isUnknownBackendNodeRefusal(res))
2611
+ return res;
2612
+ if (!PANEL_ADD_NODE_FRONTEND_ONLY_TYPES.has(classType))
2613
+ return res;
2614
+ const advertised = tabAdvertisedPanelVersion(ctx);
2615
+ if (!advertised)
2616
+ return res;
2617
+ const required = requiredPanelVersion();
2618
+ if (compareSemver(advertised, required) >= 0)
2619
+ return res;
2620
+ const update = describeInstallPanelAction("update", "update the panel pack on the ComfyUI host");
2621
+ return appendToolResultText(res, `\n\nThis is NOT a missing backend pack. "${classType}" is a frontend-only type ` +
2622
+ `that current panels (${required}+) allowlist, so its absence from /object_info is ` +
2623
+ `expected. This tab announced panel ${advertised}, which is below that floor — ` +
2624
+ `the add-node guard in this tab's JavaScript does not have the allowlist. ` +
2625
+ `${update}, restart ComfyUI, then HARD-REFRESH the ComfyUI browser tab ` +
2626
+ `(Ctrl+Shift+R, or Cmd+Shift+R on macOS). A restart alone leaves the tab running ` +
2627
+ `cached old JS, which is why this error can persist after an update.`);
2628
+ }
2554
2629
  function appendToolResultText(res, extra) {
2555
2630
  const idx = res?.content?.findIndex((c) => c.type === "text") ?? -1;
2556
2631
  const block = idx >= 0 ? res.content[idx] : undefined;
@@ -9346,8 +9421,9 @@ export function buildPanelToolDefs() {
9346
9421
  // install. Give it the bounded refresh ack budget.
9347
9422
  const add = () => ctx.call({ cmd: "graph_add_node", class_type: args.class_type, pos: args.pos, title: args.title }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS);
9348
9423
  const first = await add();
9349
- if (!isStaleNodeSchemaRefusal(first))
9350
- return withLoraManagerAutocompleteNote(first);
9424
+ if (!isStaleNodeSchemaRefusal(first)) {
9425
+ return withFrontendOnlyPanelSkewNote(withLoraManagerAutocompleteNote(first), args.class_type, ctx);
9426
+ }
9351
9427
  // #1329 — DO THE REFRESH THE REFUSAL ASKS FOR, instead of billing the caller.
9352
9428
  //
9353
9429
  // The reporter uploaded two images, and both LoadImage adds were refused with
@@ -9365,19 +9441,20 @@ export function buildPanelToolDefs() {
9365
9441
  // The refusal is the better error: it names what is wrong with the SCHEMA and
9366
9442
  // what clears it. Carry the refresh failure alongside so the caller knows the
9367
9443
  // automatic attempt happened and why it did not help.
9368
- return withLoraManagerAutocompleteNote(appendToolResultText(first, `\n\n(Tried to clear this automatically: panel_refresh_nodes was dispatched and FAILED, ` +
9444
+ return withFrontendOnlyPanelSkewNote(withLoraManagerAutocompleteNote(appendToolResultText(first, `\n\n(Tried to clear this automatically: panel_refresh_nodes was dispatched and FAILED, ` +
9369
9445
  `so the schema is unchanged and retrying the add will refuse again. ` +
9370
- `${textOfToolResult(refreshed)})`));
9446
+ `${textOfToolResult(refreshed)})`)), args.class_type, ctx);
9371
9447
  }
9372
9448
  const second = await add();
9373
- if (!isStaleNodeSchemaRefusal(second))
9374
- return withLoraManagerAutocompleteNote(second);
9449
+ if (!isStaleNodeSchemaRefusal(second)) {
9450
+ return withFrontendOnlyPanelSkewNote(withLoraManagerAutocompleteNote(second), args.class_type, ctx);
9451
+ }
9375
9452
  // Still stale after a successful refresh: report THAT, because it means the
9376
9453
  // remedy the refusal prescribes does not fix this instance and a caller
9377
9454
  // following it by hand would loop.
9378
- return withLoraManagerAutocompleteNote(appendToolResultText(second, `\n\n(This was already retried ONCE automatically: panel_refresh_nodes reported success ` +
9455
+ return withFrontendOnlyPanelSkewNote(withLoraManagerAutocompleteNote(appendToolResultText(second, `\n\n(This was already retried ONCE automatically: panel_refresh_nodes reported success ` +
9379
9456
  `and the add still refuses, so repeating panel_refresh_nodes will not clear it. ` +
9380
- `Reload the ComfyUI browser tab, which rebuilds the page's node registry from scratch.)`));
9457
+ `Reload the ComfyUI browser tab, which rebuilds the page's node registry from scratch.)`)), args.class_type, ctx);
9381
9458
  }),
9382
9459
  def("panel_remove_node", "Remove a node (and its connections) from the user's open graph by id. Undoable with Ctrl+Z.", { node_id: nodeId().describe("Node id from panel_graph_outline / panel_query_graph.") }, async (args, ctx) => ctx.call({ cmd: "graph_remove_node", node_id: args.node_id })),
9383
9460
  def("panel_clear", "Remove EVERY node from the user's open graph — only for an explicit 'clear/reset the canvas'. Just CALL THIS DIRECTLY when they ask to clear: the tool itself pops a confirm card and only wipes on a yes (don't ask separately first). The wipe is a single Ctrl+Z undo. NEVER use this for a 'new workflow' — that's panel_new_workflow (a new tab, leaves this graph intact).", {}, async (_args, ctx) => {