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),
107 lines • 4.63 kB
JavaScript
import { workflowToDsl, dslToWorkflow } from "../services/workflow-dsl.js";
import { isTypeCompatible } from "../services/slot-compat.js";
import { getObjectInfo } from "../comfyui/client.js";
/** A connection input value: [sourceNodeId, outputIndex], where the source id exists in the graph. */
function isConnection(value, workflow) {
return (Array.isArray(value) &&
value.length === 2 &&
typeof value[0] === "string" &&
typeof value[1] === "number" &&
Number.isInteger(value[1]) &&
Object.prototype.hasOwnProperty.call(workflow, value[0]));
}
/** Declared type of a node input `key` from /object_info (string, COMBO array, or undefined if absent). */
function declaredInputType(def, key) {
const spec = def.input?.required?.[key] ?? def.input?.optional?.[key];
if (!Array.isArray(spec))
return undefined;
const type = spec[0];
return typeof type === "string" || Array.isArray(type) ? type : undefined;
}
function fmtType(t) {
return Array.isArray(t) ? "COMBO" : t;
}
/**
* Advisory wiring checks for a parsed DSL graph against a live /object_info map.
* Read-only: returns human-readable warnings; NEVER throws for graph content.
* Uses the shared slot-compat rules so it agrees with the panel's connect resolver.
*/
export function computeWiringWarnings(workflow, objectInfo) {
const warnings = [];
for (const [id, node] of Object.entries(workflow)) {
const def = objectInfo[node.class_type];
if (!def) {
warnings.push(`unknown class_type "${node.class_type}"`);
continue;
}
for (const [key, value] of Object.entries(node.inputs ?? {})) {
if (!isConnection(value, workflow))
continue;
const [srcId, idx] = value;
const srcNode = workflow[srcId];
const srcDef = objectInfo[srcNode.class_type];
if (!srcDef)
continue; // source class unknown — already flagged on that node
const outputs = srcDef.output ?? [];
if (idx < 0 || idx >= outputs.length) {
warnings.push(`output index ${idx} out of range for ${srcNode.class_type} (${outputs.length} outputs: ${outputs.join(", ")})`);
continue;
}
const inType = declaredInputType(def, key);
if (inType === undefined)
continue; // unknown input key — not our concern here
const outType = outputs[idx];
if (!isTypeCompatible(outType, inType)) {
warnings.push(`type mismatch: ${srcId}.${idx} (${fmtType(outType)}) → ${id}.${key} (${fmtType(inType)})`);
}
}
}
return warnings;
}
/**
* Parse a DSL string, then — only when ComfyUI is reachable — attach advisory
* wiring warnings. ComfyUI being offline/unreachable is swallowed silently so
* the conversion is identical to the offline path (no warnings). The conversion
* NEVER fails because of warnings.
*/
export async function dslToWorkflowWithWarnings(dsl) {
const workflow = dslToWorkflow(dsl);
let warnings = [];
try {
const objectInfo = await getObjectInfo();
warnings = computeWiringWarnings(workflow, objectInfo);
}
catch {
// ComfyUI offline/unreachable — advisory warnings are best-effort; skip silently.
}
return { workflow, warnings };
}
/**
* `visualize_workflow (action:"to_dsl")` — the body of the retired JSON-to-DSL
* tool (0.50.0 slice 14). Same `workflowToDsl` call, same single-text content
* block.
*/
export function workflowToDslAction(workflow) {
return { content: [{ type: "text", text: workflowToDsl(workflow) }] };
}
/**
* `visualize_workflow (action:"from_dsl")` — the body of the retired
* DSL-to-JSON tool (0.50.0 slice 14). Same `dslToWorkflowWithWarnings` call,
* same JSON-first content block with the advisory-warnings block appended only
* when there are warnings.
*
* Both throw rather than returning `errorToToolResult`; the caller wraps every
* action in the identical catch, so failures render exactly as before.
*/
export async function dslToWorkflowAction(dsl) {
const { workflow, warnings } = await dslToWorkflowWithWarnings(dsl);
const content = [{ type: "text", text: JSON.stringify(workflow, null, 2) }];
if (warnings.length > 0) {
content.push({
type: "text",
text: `Advisory wiring warnings (conversion still succeeded):\n${warnings.map((w) => `- ${w}`).join("\n")}`,
});
}
return { content };
}
//# sourceMappingURL=workflow-dsl.js.map