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),
111 lines • 5.38 kB
JavaScript
import { validateWorkflow } from "../services/workflow-validator.js";
import { ValidationError } from "../utils/errors.js";
function parseWorkflow(input) {
if (typeof input === "string") {
try {
const parsed = JSON.parse(input);
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new ValidationError("Workflow JSON must be an object with node IDs as keys");
}
return parsed;
}
catch (err) {
if (err instanceof ValidationError)
throw err;
throw new ValidationError(`Invalid JSON string: ${err.message}`);
}
}
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
return input;
}
throw new ValidationError("Workflow must be a JSON string or object");
}
/**
* `create_workflow (action:"validate")` — the body of the retired standalone
* workflow-validation tool, verbatim (0.50.0 slice 14).
*
* It no longer registers a tool of its own: the surface consolidation folded it
* into `create_workflow`, which owns the schema and the single try/catch. The
* WORK stayed here, unchanged — same `validateWorkflow` call, same arguments,
* same rendered content block — so this file remains the one place the
* "verdict is derived from the surfaced errors" rendering of #342/#505 lives.
*
* Throws instead of returning `errorToToolResult`: the caller wraps every action
* in the identical `errorToToolResult` catch, so a failure produces the same
* result object it always did.
*/
export async function validateWorkflowAction(args) {
const workflow = parseWorkflow(args.workflow);
const result = await validateWorkflow(workflow, { health: args.health });
const lines = [];
lines.push(`## ${result.summary}`);
lines.push("");
// Health findings are rendered in their own "### Graph health" section —
// exclude them from the Errors/Warnings buckets to avoid double-listing.
// NOTE: partition on `.health`, NOT `.kind`. Authoritative validator issues
// (missing_node_type / missing_model / value_not_in_list) also carry `kind`,
// so filtering by `!i.kind` (the old logic) masked real errors behind
// "No issues found" while the header still counted them (#342, #505).
const nonHealth = result.issues.filter((i) => !i.health);
const errors = nonHealth.filter((i) => i.severity === "error");
const warnings = nonHealth.filter((i) => i.severity === "warning");
if (errors.length === 0 && warnings.length === 0) {
// Health findings are deliberately excluded from the buckets above, so an
// empty bucket does NOT mean an empty report. Claiming "No issues found"
// over a graph-health WARNING contradicts the header (which counts health
// warnings) and buries the one line the caller needed — #2678 was a graph
// that "validated and executed successfully" and rendered a flat field.
// Info-level findings stay on the plain ready-to-execute wording; only a
// warning changes the verdict line.
// Counted from `issues` -- the very array being partitioned -- so this can never
// disagree with the header. Reading `result.health.findings` instead would leave a
// caller whose issues carry health warnings but whose `health` section is absent
// being told "No issues found" over its own warning.
const healthWarnings = result.issues.filter((i) => i.health && i.severity === "warning").length;
// Verdict is DERIVED from the surfaced errors, never asserted independently.
lines.push(result.valid
? healthWarnings > 0
? `Nothing here blocks execution, but ${healthWarnings} graph-health warning(s) below flag a graph that may not produce what you intend — read them before running.`
: "No issues found. The workflow is ready to execute."
: "No errors were surfaced, but the workflow is NOT valid — see the header and graph-health section below.");
}
else {
if (errors.length > 0) {
lines.push("### Errors");
for (const issue of errors) {
const loc = issue.node_id
? `Node ${issue.node_id} (${issue.node_type})`
: "Workflow";
lines.push(`- **${loc}**: ${issue.message}`);
}
lines.push("");
}
if (warnings.length > 0) {
lines.push("### Warnings");
for (const issue of warnings) {
const loc = issue.node_id
? `Node ${issue.node_id} (${issue.node_type})`
: "Workflow";
lines.push(`- **${loc}**: ${issue.message}`);
}
}
}
if (result.health) {
lines.push("");
lines.push("### Graph health");
lines.push(`- ${result.health.summary}`);
for (const f of result.health.findings) {
const tag = f.heuristic ? `${f.severity}, heuristic` : f.severity;
lines.push(`- [${tag}] ${f.detail}`);
}
}
return {
content: [
{
type: "text",
text: lines.join("\n"),
},
],
};
}
//# sourceMappingURL=workflow-validate.js.map