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.
- package/dist/orchestrator/panel-tools.js +86 -9
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/panel-sync.js +9 -0
- package/dist/services/panel-sync.js.map +1 -1
- package/dist/services/ui-bridge.js +16 -0
- package/dist/services/ui-bridge.js.map +1 -1
- package/package.json +1 -1
|
@@ -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) => {
|