@fre4x/comfyui 1.0.60 → 1.0.62

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.
Files changed (3) hide show
  1. package/README.md +48 -30
  2. package/dist/index.js +951 -1337
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -6801,40 +6801,13 @@ var require_dist = __commonJS({
6801
6801
 
6802
6802
  // src/index.ts
6803
6803
  import * as fs from "node:fs/promises";
6804
+ import * as crypto from "node:crypto";
6804
6805
  import { realpathSync } from "node:fs";
6805
6806
  import { homedir } from "node:os";
6806
6807
  import path from "node:path";
6807
6808
  import { fileURLToPath } from "node:url";
6808
6809
 
6809
6810
  // ../packages/shared/dist/errors.js
6810
- function createApiError(message, statusCode) {
6811
- let hint = "Check your network connection and retry.";
6812
- let type = "Service Error";
6813
- if (statusCode === 429) {
6814
- type = "Rate Limit";
6815
- hint = "Request volume is too high. Suggestion: Wait briefly before retrying or reduce concurrent calls.";
6816
- } else if (statusCode === 401 || statusCode === 403) {
6817
- type = "Authentication Error";
6818
- hint = "The request could not be authorized. Suggestion: Verify your API key or token in the environment configuration.";
6819
- } else if (statusCode && statusCode >= 500) {
6820
- type = "Upstream Error";
6821
- hint = "The remote service is experiencing temporary issues. Suggestion: Try again in a few minutes.";
6822
- } else if (statusCode === 404) {
6823
- type = "Not Found";
6824
- hint = "The requested information could not be found. Suggestion: Check if the ID, Ticker, or query parameters are correct.";
6825
- }
6826
- return {
6827
- isError: true,
6828
- content: [
6829
- {
6830
- type: "text",
6831
- text: `${type}: ${message}
6832
-
6833
- **Next Action**: ${hint}`
6834
- }
6835
- ]
6836
- };
6837
- }
6838
6811
  function createValidationError(field, message) {
6839
6812
  return {
6840
6813
  isError: true,
@@ -6876,6 +6849,20 @@ Suggestion: If the problem persists, check server logs.`
6876
6849
  };
6877
6850
  }
6878
6851
 
6852
+ // ../packages/shared/dist/format.js
6853
+ function formatListItems(items, renderer, emptyMessage = "_No results found._") {
6854
+ if (items.length === 0)
6855
+ return emptyMessage;
6856
+ return items.map((item, i) => renderer(item, i)).join("\n\n");
6857
+ }
6858
+ function formatPaginationFooter(offset, limit, total) {
6859
+ const start = offset + 1;
6860
+ const end = Math.min(offset + limit, total);
6861
+ return `
6862
+ ---
6863
+ _Showing ${start}\u2013${end} of ${total}. Use \`offset: ${offset + limit}\` for the next page._`;
6864
+ }
6865
+
6879
6866
  // ../node_modules/zod/index.js
6880
6867
  var zod_exports = {};
6881
6868
  __export(zod_exports, {
@@ -30582,801 +30569,237 @@ var MOCK_FIXTURES = {
30582
30569
  }
30583
30570
  };
30584
30571
 
30585
- // src/index.ts
30586
- var zNamespace = zod_exports;
30587
- var z3 = zNamespace.z ?? zNamespace.default ?? zNamespace;
30588
- var COMFYUI_SERVER_URL = process.env.COMFYUI_SERVER_URL || "http://localhost:8188";
30589
- var COMFYUI_WORKFLOWS_DIR = process.env.COMFYUI_WORKFLOWS_DIR || path.join(homedir(), ".fre4x-comfyui", "workflows");
30590
- var PACKAGE_VERSION = getPackageVersion(import.meta.url);
30591
- var BYTES_PER_GIB = 1024 ** 3;
30592
- var PaginationMetadataSchema = z3.object({
30593
- total_count: z3.number(),
30594
- limit: z3.number(),
30595
- offset: z3.number(),
30596
- has_more: z3.boolean()
30597
- });
30598
- var SystemInfoSchema = z3.object({
30599
- system: z3.object({
30600
- os: z3.string(),
30601
- python_version: z3.string(),
30602
- embedded_python: z3.boolean()
30603
- }),
30604
- devices: z3.array(
30605
- z3.object({
30606
- name: z3.string(),
30607
- type: z3.string(),
30608
- index: z3.number(),
30609
- vram_total: z3.number(),
30610
- vram_free: z3.number()
30611
- })
30612
- )
30613
- });
30614
- var ObjectInfoSchema = z3.object({
30615
- nodes: z3.record(z3.string(), z3.unknown()),
30616
- pagination: PaginationMetadataSchema.optional()
30617
- });
30618
- var HistoryImageSchema = z3.object({
30619
- filename: z3.string(),
30620
- subfolder: z3.string(),
30621
- type: z3.string(),
30622
- view_url: z3.string().url()
30623
- });
30624
- var WorkflowResponseSchema = z3.object({
30625
- prompt_id: z3.string(),
30626
- number: z3.number(),
30627
- node_errors: z3.record(z3.string(), z3.unknown()),
30628
- status: z3.string().optional(),
30629
- completed: z3.boolean().optional(),
30630
- timed_out: z3.boolean().optional(),
30631
- elapsed_seconds: z3.number().optional(),
30632
- image_count: z3.number().optional(),
30633
- images: z3.array(HistoryImageSchema).optional(),
30634
- history_entry: z3.record(z3.string(), z3.unknown()).nullable().optional()
30635
- });
30636
- var HistorySchema = z3.object({
30637
- history: z3.record(z3.string(), z3.unknown()),
30638
- pagination: PaginationMetadataSchema.optional()
30639
- });
30640
- var WaitForWorkflowSchema = z3.object({
30641
- prompt_id: z3.string(),
30642
- status: z3.string(),
30643
- completed: z3.boolean(),
30644
- timed_out: z3.boolean(),
30645
- elapsed_seconds: z3.number(),
30646
- image_count: z3.number(),
30647
- images: z3.array(HistoryImageSchema),
30648
- history_entry: z3.record(z3.string(), z3.unknown()).nullable()
30649
- });
30650
- var WorkflowTagSchema = z3.string().min(1).max(50);
30651
- var EditableWorkflowValueSchema = z3.union([
30652
- z3.string(),
30653
- z3.number(),
30654
- z3.boolean()
30572
+ // src/converter.ts
30573
+ var WIDGET_TYPES = /* @__PURE__ */ new Set(["INT", "FLOAT", "STRING", "BOOLEAN"]);
30574
+ var UI_CONTROL_VALUES = /* @__PURE__ */ new Set([
30575
+ "fixed",
30576
+ "increment",
30577
+ "decrement",
30578
+ "randomize"
30655
30579
  ]);
30656
- var WorkflowEditableInputSchema = z3.object({
30657
- node_id: z3.string(),
30658
- node_class: z3.string(),
30659
- input_name: z3.string(),
30660
- value_type: z3.enum(["string", "number", "boolean", "enum"]),
30661
- current_value: EditableWorkflowValueSchema,
30662
- options: z3.array(z3.string()).default([])
30663
- });
30664
- var StoredWorkflowMetadataSchema = z3.object({
30665
- id: z3.string(),
30666
- name: z3.string(),
30667
- description: z3.string().optional(),
30668
- tags: z3.array(WorkflowTagSchema),
30669
- created_at: z3.string(),
30670
- updated_at: z3.string(),
30671
- node_count: z3.number(),
30672
- editable_input_count: z3.number(),
30673
- source: z3.enum(["local", "mock"])
30674
- });
30675
- var StoredWorkflowSchema = z3.object({
30676
- workflow: StoredWorkflowMetadataSchema,
30677
- prompt: z3.record(z3.string(), z3.unknown()),
30678
- editable_inputs: z3.array(WorkflowEditableInputSchema)
30679
- });
30680
- var WorkflowListSchema = z3.object({
30681
- workflows: z3.array(StoredWorkflowMetadataSchema),
30682
- pagination: PaginationMetadataSchema.optional()
30683
- });
30684
- var WorkflowOverrideSchema = z3.object({
30685
- node_id: z3.string().min(1).describe("Node ID to override."),
30686
- input_name: z3.string().min(1).describe("Input field name to override."),
30687
- value: EditableWorkflowValueSchema.describe("Replacement scalar value.")
30688
- });
30689
- var RunStoredWorkflowSchema = WorkflowResponseSchema.extend({
30690
- workflow_id: z3.string(),
30691
- workflow_name: z3.string(),
30692
- applied_overrides: z3.array(WorkflowOverrideSchema)
30693
- });
30694
- var QueueSchema = z3.object({
30695
- queue_running: z3.array(z3.unknown()),
30696
- queue_pending: z3.array(z3.unknown())
30697
- });
30698
- var ViewUrlSchema = z3.object({
30699
- url: z3.string().url()
30700
- });
30701
- var NoArgsSchema = z3.object({}).strict();
30702
- var ListObjectInfoInputSchema = z3.object({
30703
- node_class: z3.string().optional().describe(
30704
- "Specific node class to inspect (for example 'KSampler')."
30705
- ),
30706
- limit: z3.number().int().min(1).max(100).default(50).describe("Pagination limit."),
30707
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30708
- }).strict();
30709
- var ExecuteWorkflowInputSchema = z3.object({
30710
- prompt: z3.record(z3.string(), z3.unknown()).describe("Workflow in ComfyUI API JSON format."),
30711
- client_id: z3.string().optional().describe("Optional unique client identifier."),
30712
- await: z3.boolean().default(false).describe("Wait for completion before returning."),
30713
- timeout: z3.number().int().min(1).max(600).default(120).describe("Wait timeout in seconds.")
30714
- }).strict();
30715
- var GetHistoryInputSchema = z3.object({
30716
- prompt_id: z3.string().optional().describe("Specific prompt ID to retrieve. Omit for full history."),
30717
- limit: z3.number().int().min(1).max(100).default(20).describe("Pagination limit."),
30718
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30719
- }).strict();
30720
- var GetViewUrlInputSchema = z3.object({
30721
- filename: z3.string().min(1).describe("Name of the file to view."),
30722
- subfolder: z3.string().optional().describe("Subfolder within the output directory."),
30723
- type: z3.enum(["output", "input", "temp"]).default("output").describe("Storage type.")
30724
- }).strict();
30725
- var ListWorkflowsInputSchema = z3.object({
30726
- limit: z3.number().int().min(1).max(100).default(50).describe("Pagination limit."),
30727
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30728
- }).strict();
30729
- var SearchWorkflowsInputSchema = z3.object({
30730
- query: z3.string().min(1).describe("Search text for name, tags, or description."),
30731
- limit: z3.number().int().min(1).max(100).default(50).describe("Pagination limit."),
30732
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30733
- }).strict();
30734
- var GetWorkflowInputSchema = z3.object({
30735
- workflow_id: z3.string().min(1).describe("Stored workflow ID.")
30736
- }).strict();
30737
- var SaveWorkflowInputSchema = z3.object({
30738
- workflow_id: z3.string().min(1).optional().describe("Optional stable workflow ID."),
30739
- name: z3.string().min(1).describe("Human-readable workflow name."),
30740
- description: z3.string().optional().describe("Optional workflow summary."),
30741
- tags: z3.array(WorkflowTagSchema).default([]).describe("Optional workflow tags."),
30742
- prompt: z3.record(z3.string(), z3.unknown()).describe("Workflow in ComfyUI API JSON format."),
30743
- overwrite: z3.boolean().default(false).describe("Overwrite an existing workflow with the same ID.")
30744
- }).strict();
30745
- var RunWorkflowInputSchema = z3.object({
30746
- workflow_id: z3.string().min(1).describe("Stored workflow ID to execute."),
30747
- overrides: z3.array(WorkflowOverrideSchema).default([]).describe("Editable input overrides to apply before execution."),
30748
- client_id: z3.string().optional().describe("Optional unique client identifier."),
30749
- await: z3.boolean().default(false).describe("Wait for completion before returning."),
30750
- timeout: z3.number().int().min(1).max(600).default(120).describe("Wait timeout in seconds."),
30751
- require_images: z3.boolean().default(false).describe("When awaiting, require generated images before return.")
30752
- }).strict();
30753
- var WaitForWorkflowInputSchema = z3.object({
30754
- prompt_id: z3.string().min(1).describe("Prompt ID to wait for."),
30755
- timeout_seconds: z3.number().int().min(1).max(600).default(120).describe("Maximum wait time in seconds."),
30756
- poll_interval_ms: z3.number().int().min(100).max(1e4).default(1e3).describe("Polling interval in milliseconds."),
30757
- require_images: z3.boolean().default(false).describe("Wait until images are available too.")
30758
- }).strict();
30759
- var ComfyApiError = class extends Error {
30760
- constructor(message, statusCode, details) {
30761
- super(message);
30762
- this.statusCode = statusCode;
30763
- this.details = details;
30764
- this.name = "ComfyApiError";
30765
- }
30766
- };
30767
- function isRecord(value) {
30768
- return typeof value === "object" && value !== null && !Array.isArray(value);
30769
- }
30770
- function ensureRecord(value, label) {
30771
- if (!isRecord(value)) {
30772
- throw new Error(`Invalid ${label} response from ComfyUI.`);
30773
- }
30774
- return value;
30775
- }
30776
- function isEditableWorkflowValue(value) {
30777
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
30778
- }
30779
- function slugifyWorkflowId(value) {
30780
- const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
30781
- return slug.length > 0 ? slug : "workflow";
30782
- }
30783
- function getWorkflowFilePath(workflowId) {
30784
- return path.join(COMFYUI_WORKFLOWS_DIR, `${workflowId}.json`);
30580
+ function isWebUIFormat(json2) {
30581
+ return typeof json2 === "object" && json2 !== null && Array.isArray(json2.nodes) && Array.isArray(json2.links);
30785
30582
  }
30786
- async function ensureWorkflowStoreDir() {
30787
- await fs.mkdir(COMFYUI_WORKFLOWS_DIR, { recursive: true });
30583
+ function isLinkData(link) {
30584
+ return Array.isArray(link) && link.length >= 6 && typeof link[0] === "number" && typeof link[1] === "number" && typeof link[2] === "number" && typeof link[3] === "number" && typeof link[4] === "number" && typeof link[5] === "string";
30788
30585
  }
30789
- async function readStoredWorkflowFile(workflowId) {
30790
- try {
30791
- const content = await fs.readFile(
30792
- getWorkflowFilePath(workflowId),
30793
- "utf8"
30794
- );
30795
- return StoredWorkflowSchema.parse(JSON.parse(content));
30796
- } catch (error48) {
30797
- if (isRecord(error48) && error48.code === "ENOENT") {
30798
- return void 0;
30799
- }
30800
- throw error48;
30586
+ function getOrderedInputEntries(group, orderedNames) {
30587
+ if (!group) {
30588
+ return [];
30801
30589
  }
30802
- }
30803
- async function writeStoredWorkflowFile(workflow) {
30804
- await ensureWorkflowStoreDir();
30805
- await fs.writeFile(
30806
- getWorkflowFilePath(workflow.workflow.id),
30807
- `${JSON.stringify(workflow, null, 2)}
30808
- `,
30809
- "utf8"
30810
- );
30811
- }
30812
- async function listStoredWorkflowFiles() {
30813
- await ensureWorkflowStoreDir();
30814
- const entries = await fs.readdir(COMFYUI_WORKFLOWS_DIR, {
30815
- withFileTypes: true
30816
- });
30817
- const workflows = [];
30818
- for (const entry of entries) {
30819
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
30590
+ const seen = /* @__PURE__ */ new Set();
30591
+ const orderedEntries = [];
30592
+ for (const inputName of orderedNames ?? []) {
30593
+ const inputDefinition = group[inputName];
30594
+ if (!inputDefinition) {
30820
30595
  continue;
30821
30596
  }
30822
- const content = await fs.readFile(
30823
- path.join(COMFYUI_WORKFLOWS_DIR, entry.name),
30824
- "utf8"
30825
- );
30826
- workflows.push(StoredWorkflowSchema.parse(JSON.parse(content)));
30827
- }
30828
- workflows.sort(
30829
- (left, right) => right.workflow.updated_at.localeCompare(left.workflow.updated_at)
30830
- );
30831
- return workflows;
30832
- }
30833
- function unwrapNamedNode(nodeClass, value) {
30834
- if (!isRecord(value)) {
30835
- return value;
30836
- }
30837
- if (nodeClass in value) {
30838
- return value[nodeClass];
30839
- }
30840
- return value;
30841
- }
30842
- function getErrorMessageFromDetails(details) {
30843
- if (!isRecord(details)) {
30844
- return void 0;
30597
+ orderedEntries.push([inputName, inputDefinition]);
30598
+ seen.add(inputName);
30845
30599
  }
30846
- const errorValue = details.error;
30847
- if (typeof errorValue === "string") {
30848
- return errorValue;
30849
- }
30850
- if (isRecord(errorValue) && typeof errorValue.message === "string") {
30851
- return errorValue.message;
30852
- }
30853
- return void 0;
30854
- }
30855
- function getNodeErrors(details) {
30856
- if (!isRecord(details) || !isRecord(details.node_errors)) {
30857
- return void 0;
30600
+ for (const [inputName, inputDefinition] of Object.entries(group)) {
30601
+ if (seen.has(inputName)) {
30602
+ continue;
30603
+ }
30604
+ orderedEntries.push([inputName, inputDefinition]);
30858
30605
  }
30859
- return details.node_errors;
30860
- }
30861
- function normalizeSystemInfo(source) {
30862
- const record2 = ensureRecord(source, "system info");
30863
- const systemRecord = ensureRecord(record2.system, "system info.system");
30864
- const devicesValue = Array.isArray(record2.devices) ? record2.devices : [];
30865
- return {
30866
- system: {
30867
- os: typeof systemRecord.os === "string" ? systemRecord.os : "unknown",
30868
- python_version: typeof systemRecord.python_version === "string" ? systemRecord.python_version : "unknown",
30869
- embedded_python: systemRecord.embedded_python === true
30870
- },
30871
- devices: devicesValue.map((device) => {
30872
- const deviceRecord = ensureRecord(device, "system info.devices[]");
30873
- return {
30874
- name: typeof deviceRecord.name === "string" ? deviceRecord.name : "unknown",
30875
- type: typeof deviceRecord.type === "string" ? deviceRecord.type : "unknown",
30876
- index: typeof deviceRecord.index === "number" ? deviceRecord.index : -1,
30877
- vram_total: typeof deviceRecord.vram_total === "number" ? deviceRecord.vram_total : 0,
30878
- vram_free: typeof deviceRecord.vram_free === "number" ? deviceRecord.vram_free : 0
30879
- };
30880
- })
30881
- };
30882
- }
30883
- function formatGiB(bytes) {
30884
- return `${(bytes / BYTES_PER_GIB).toFixed(1)} GiB`;
30606
+ return orderedEntries;
30885
30607
  }
30886
- function formatJsonCodeBlock(value) {
30887
- return `\`\`\`json
30888
- ${JSON.stringify(value, null, 2)}
30889
- \`\`\``;
30608
+ function getWidgetTypeName(inputTypeData) {
30609
+ return Array.isArray(inputTypeData) ? "COMBO" : String(inputTypeData).toUpperCase();
30890
30610
  }
30891
- function formatMarkdownBulletList(items) {
30892
- return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : "- none";
30893
- }
30894
- async function sleep(ms) {
30895
- await new Promise((resolve) => setTimeout(resolve, ms));
30896
- }
30897
- function getEnumLikeOptions(value) {
30898
- if (!Array.isArray(value) || value.length === 0) {
30899
- return [];
30900
- }
30901
- const firstEntry = value[0];
30902
- if (!Array.isArray(firstEntry)) {
30903
- return [];
30904
- }
30905
- return firstEntry.filter(
30906
- (entry) => typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean"
30907
- );
30611
+ function isWidgetInput(inputTypeData) {
30612
+ const typeName = getWidgetTypeName(inputTypeData);
30613
+ return typeName === "COMBO" || WIDGET_TYPES.has(typeName);
30908
30614
  }
30909
- function buildViewUrl(filename, type, subfolder) {
30910
- const params = new URLSearchParams({
30911
- filename,
30912
- type
30913
- });
30914
- if (subfolder) {
30915
- params.set("subfolder", subfolder);
30615
+ function isSkippableUiArtifact(value, expectedTypeName) {
30616
+ if (expectedTypeName !== "STRING" && typeof value === "string" && UI_CONTROL_VALUES.has(value)) {
30617
+ return true;
30916
30618
  }
30917
- return new URL(`/view?${params.toString()}`, COMFYUI_SERVER_URL).toString();
30619
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30918
30620
  }
30919
- async function fetchNodeDefinition(nodeClass) {
30920
- if (IS_MOCK) {
30921
- return MOCK_FIXTURES.object_info[nodeClass];
30922
- }
30923
- try {
30924
- const rawInfo = await fetchComfyJson(
30925
- `/object_info/${encodeURIComponent(nodeClass)}`
30926
- );
30927
- return unwrapNamedNode(nodeClass, rawInfo);
30928
- } catch (error48) {
30929
- if (error48 instanceof ComfyApiError && error48.statusCode === 404) {
30930
- return void 0;
30931
- }
30932
- throw error48;
30621
+ function normalizeWidgetValue(value, typeName, extraInfo) {
30622
+ const defaultValue = extraInfo?.default;
30623
+ const shouldUseDefaultForNumeric = (typeName === "INT" || typeName === "FLOAT") && (value === "" || value === void 0 || value === null);
30624
+ if (shouldUseDefaultForNumeric && defaultValue !== void 0) {
30625
+ return defaultValue;
30933
30626
  }
30627
+ return value;
30934
30628
  }
30935
- async function deriveEditableWorkflowInputs(prompt) {
30936
- const nodeDefinitions = /* @__PURE__ */ new Map();
30937
- const editableInputs = [];
30938
- for (const [nodeId, nodeValue] of Object.entries(prompt)) {
30939
- if (!isRecord(nodeValue)) {
30940
- continue;
30941
- }
30942
- const nodeClass = typeof nodeValue.class_type === "string" ? nodeValue.class_type : void 0;
30943
- const inputs = isRecord(nodeValue.inputs) ? nodeValue.inputs : void 0;
30944
- if (!nodeClass || !inputs) {
30945
- continue;
30946
- }
30947
- if (!nodeDefinitions.has(nodeClass)) {
30948
- nodeDefinitions.set(
30949
- nodeClass,
30950
- await fetchNodeDefinition(nodeClass)
30951
- );
30952
- }
30953
- const nodeDefinition = nodeDefinitions.get(nodeClass);
30954
- const nodeInputRecord = isRecord(nodeDefinition) && isRecord(nodeDefinition.input) ? nodeDefinition.input : void 0;
30955
- const requiredInputs = isRecord(nodeInputRecord?.required) ? nodeInputRecord.required : void 0;
30956
- const optionalInputs = isRecord(nodeInputRecord?.optional) ? nodeInputRecord.optional : void 0;
30957
- for (const [inputName, inputValue] of Object.entries(inputs)) {
30958
- if (!isEditableWorkflowValue(inputValue)) {
30959
- continue;
30629
+ function convertWebUIToAPI(webUIJson, objectInfo) {
30630
+ const prompt = {};
30631
+ const links = webUIJson.links || [];
30632
+ const nodes = webUIJson.nodes || [];
30633
+ const linkMap = /* @__PURE__ */ new Map();
30634
+ for (const link of links) {
30635
+ if (isLinkData(link)) {
30636
+ linkMap.set(link[0], link);
30637
+ }
30638
+ }
30639
+ for (const node of nodes) {
30640
+ if (!node.id || !node.type) continue;
30641
+ const objInfo = objectInfo[node.type];
30642
+ if (!objInfo) continue;
30643
+ const nodeIdStr = String(node.id);
30644
+ const promptNode = {
30645
+ class_type: node.type,
30646
+ inputs: {}
30647
+ };
30648
+ const connectedInputNames = /* @__PURE__ */ new Set();
30649
+ if (Array.isArray(node.inputs)) {
30650
+ for (const input of node.inputs) {
30651
+ if (input.name && input.link !== null && input.link !== void 0) {
30652
+ const linkData = linkMap.get(input.link);
30653
+ if (Array.isArray(linkData) && linkData.length >= 6) {
30654
+ const originNodeId = String(linkData[1]);
30655
+ const originSlot = linkData[2];
30656
+ promptNode.inputs[input.name] = [
30657
+ originNodeId,
30658
+ originSlot
30659
+ ];
30660
+ connectedInputNames.add(input.name);
30661
+ }
30662
+ }
30960
30663
  }
30961
- const definition = requiredInputs?.[inputName] ?? optionalInputs?.[inputName];
30962
- const options = getEnumLikeOptions(definition).map(String);
30963
- const valueType = typeof inputValue === "boolean" ? "boolean" : typeof inputValue === "number" ? "number" : options.length > 0 ? "enum" : "string";
30964
- editableInputs.push({
30965
- node_id: nodeId,
30966
- node_class: nodeClass,
30967
- input_name: inputName,
30968
- value_type: valueType,
30969
- current_value: inputValue,
30970
- options
30971
- });
30972
30664
  }
30973
- }
30974
- return editableInputs;
30975
- }
30976
- function cloneWorkflowPrompt(prompt) {
30977
- return JSON.parse(JSON.stringify(prompt));
30978
- }
30979
- function applyWorkflowOverrides(prompt, editableInputs, overrides) {
30980
- const nextPrompt = cloneWorkflowPrompt(prompt);
30981
- const editableKeys = new Set(
30982
- editableInputs.map((input) => `${input.node_id}.${input.input_name}`)
30983
- );
30984
- for (const override of overrides) {
30985
- const key = `${override.node_id}.${override.input_name}`;
30986
- if (!editableKeys.has(key)) {
30987
- throw new Error(
30988
- `Override ${key} is not editable for this stored workflow.`
30665
+ if (objInfo) {
30666
+ const widgets = Array.isArray(node.widgets_values) ? node.widgets_values : [];
30667
+ let widgetIndex = 0;
30668
+ const processInputGroup = (group, orderedNames) => {
30669
+ for (const [inputName, inputDataRaw] of getOrderedInputEntries(
30670
+ group,
30671
+ orderedNames
30672
+ )) {
30673
+ const [inputTypeData, inputExtraInfo] = inputDataRaw;
30674
+ if (!isWidgetInput(inputTypeData)) {
30675
+ continue;
30676
+ }
30677
+ const typeName = getWidgetTypeName(inputTypeData);
30678
+ while (widgetIndex < widgets.length && isSkippableUiArtifact(widgets[widgetIndex], typeName)) {
30679
+ widgetIndex++;
30680
+ }
30681
+ const isConnectedWidget = connectedInputNames.has(inputName);
30682
+ if (widgetIndex >= widgets.length) {
30683
+ if (!isConnectedWidget && inputExtraInfo?.default !== void 0) {
30684
+ promptNode.inputs[inputName] = inputExtraInfo.default;
30685
+ }
30686
+ continue;
30687
+ }
30688
+ const value = normalizeWidgetValue(
30689
+ widgets[widgetIndex],
30690
+ typeName,
30691
+ inputExtraInfo
30692
+ );
30693
+ widgetIndex++;
30694
+ if (!isConnectedWidget) {
30695
+ promptNode.inputs[inputName] = value;
30696
+ }
30697
+ }
30698
+ };
30699
+ processInputGroup(
30700
+ objInfo.input?.required,
30701
+ objInfo.input_order?.required
30989
30702
  );
30990
- }
30991
- const node = nextPrompt[override.node_id];
30992
- if (!isRecord(node) || !isRecord(node.inputs)) {
30993
- throw new Error(
30994
- `Stored workflow node ${override.node_id} is invalid.`
30703
+ processInputGroup(
30704
+ objInfo.input?.optional,
30705
+ objInfo.input_order?.optional
30995
30706
  );
30996
30707
  }
30997
- node.inputs[override.input_name] = override.value;
30708
+ prompt[nodeIdStr] = promptNode;
30998
30709
  }
30999
- return nextPrompt;
30710
+ return prompt;
31000
30711
  }
31001
- function summarizeOptionLines(inputSection) {
31002
- if (!inputSection) {
31003
- return [];
30712
+
30713
+ // src/index.ts
30714
+ var zNamespace = zod_exports;
30715
+ var z3 = zNamespace.z ?? zNamespace.default ?? zNamespace;
30716
+ var COMFYUI_SERVER_URL = process.env.COMFYUI_SERVER_URL || "http://localhost:8188";
30717
+ var PACKAGE_VERSION = getPackageVersion(import.meta.url);
30718
+ var ComfyApiError = class extends Error {
30719
+ constructor(message, statusCode, details) {
30720
+ super(message);
30721
+ this.statusCode = statusCode;
30722
+ this.details = details;
30723
+ this.name = "ComfyApiError";
31004
30724
  }
31005
- return Object.entries(inputSection).flatMap(([inputName, definition]) => {
31006
- const options = getEnumLikeOptions(definition);
31007
- if (options.length === 0) {
31008
- return [];
31009
- }
31010
- return [`${inputName}: ${options.map(String).join(", ")}`];
31011
- });
31012
- }
31013
- function summarizeSystemInfoText(structuredContent) {
31014
- const deviceSummary = formatMarkdownBulletList(
31015
- structuredContent.devices.map(
31016
- (device) => `[${device.index}] ${device.name} (${device.type}, ${formatGiB(device.vram_free)} free / ${formatGiB(device.vram_total)} total VRAM)`
31017
- )
31018
- );
31019
- return [
31020
- "# ComfyUI System Info",
31021
- "",
31022
- `ComfyUI server is running on ${structuredContent.system.os} with ${structuredContent.devices.length} device(s).`,
31023
- "",
31024
- "## Summary",
31025
- formatMarkdownBulletList([
31026
- `OS: ${structuredContent.system.os}`,
31027
- `Python: ${structuredContent.system.python_version}`,
31028
- `Embedded Python: ${structuredContent.system.embedded_python ? "yes" : "no"}`,
31029
- `Devices: ${structuredContent.devices.length}`
31030
- ]),
31031
- "",
31032
- "## Devices",
31033
- deviceSummary,
31034
- "",
31035
- "## JSON",
31036
- formatJsonCodeBlock(structuredContent)
31037
- ].join("\n");
31038
- }
31039
- function summarizeNodeDefinitionText(nodeClass, nodeDefinition) {
31040
- if (!isRecord(nodeDefinition)) {
31041
- return `Schema retrieved for node: ${nodeClass}`;
31042
- }
31043
- const inputRecord = isRecord(nodeDefinition.input) ? nodeDefinition.input : void 0;
31044
- const requiredInputs = isRecord(inputRecord?.required) ? inputRecord.required : void 0;
31045
- const optionalInputs = isRecord(inputRecord?.optional) ? inputRecord.optional : void 0;
31046
- const outputNames = Array.isArray(nodeDefinition.output) ? nodeDefinition.output.filter(
31047
- (output) => typeof output === "string"
31048
- ) : [];
31049
- const optionLines = [
31050
- ...summarizeOptionLines(requiredInputs),
31051
- ...summarizeOptionLines(optionalInputs)
31052
- ];
31053
- return [
31054
- "# ComfyUI Node Definition",
31055
- "",
31056
- `Schema retrieved for node: ${nodeClass}`,
31057
- "",
31058
- "## Summary",
31059
- formatMarkdownBulletList([
31060
- `Node Class: ${nodeClass}`,
31061
- `Display Name: ${typeof nodeDefinition.display_name === "string" ? nodeDefinition.display_name : typeof nodeDefinition.name === "string" ? nodeDefinition.name : nodeClass}`,
31062
- `Category: ${typeof nodeDefinition.category === "string" ? nodeDefinition.category : "unknown"}`,
31063
- `Required Inputs: ${requiredInputs ? Object.keys(requiredInputs).join(", ") || "none" : "none"}`,
31064
- `Optional Inputs: ${optionalInputs ? Object.keys(optionalInputs).join(", ") || "none" : "none"}`,
31065
- `Outputs: ${outputNames.join(", ") || "none"}`
31066
- ]),
31067
- "",
31068
- optionLines.length > 0 ? `## Enumerated Options
31069
- ${optionLines.map((line) => `- ${line}`).join("\n")}
31070
- ` : "",
31071
- "## JSON",
31072
- formatJsonCodeBlock({
31073
- nodes: { [nodeClass]: nodeDefinition }
31074
- }),
31075
- ""
31076
- ].filter(Boolean).join("\n");
31077
- }
31078
- function summarizeHistoryEntryText(promptId, entry) {
31079
- if (!isRecord(entry)) {
31080
- return `History retrieved for job: ${promptId}`;
31081
- }
31082
- const statusRecord = isRecord(entry.status) ? entry.status : void 0;
31083
- const outputsRecord = isRecord(entry.outputs) ? entry.outputs : void 0;
31084
- const imageRefs = [];
31085
- if (outputsRecord) {
31086
- for (const output of Object.values(outputsRecord)) {
31087
- if (!isRecord(output) || !Array.isArray(output.images)) {
31088
- continue;
31089
- }
31090
- for (const image of output.images) {
31091
- if (!isRecord(image) || typeof image.filename !== "string") {
31092
- continue;
31093
- }
31094
- const subfolder = typeof image.subfolder === "string" && image.subfolder ? `${image.subfolder}/` : "";
31095
- imageRefs.push(`${subfolder}${image.filename}`);
31096
- }
31097
- }
31098
- }
31099
- return [
31100
- "# ComfyUI History Entry",
31101
- "",
31102
- `History retrieved for job: ${promptId}`,
31103
- "",
31104
- "## Summary",
31105
- formatMarkdownBulletList([
31106
- `Prompt ID: ${promptId}`,
31107
- `Status: ${typeof statusRecord?.status_str === "string" ? statusRecord.status_str : "unknown"}`,
31108
- `Completed: ${statusRecord?.completed === true ? "yes" : statusRecord?.completed === false ? "no" : "unknown"}`,
31109
- `Images: ${imageRefs.length}`
31110
- ]),
31111
- "",
31112
- "## Images",
31113
- formatMarkdownBulletList(imageRefs),
31114
- "",
31115
- "## JSON",
31116
- formatJsonCodeBlock({
31117
- history: { [promptId]: entry }
31118
- })
31119
- ].join("\n");
30725
+ };
30726
+ var WorkflowInputError = class extends Error {
30727
+ constructor(message, field, kind = "validation") {
30728
+ super(message);
30729
+ this.field = field;
30730
+ this.kind = kind;
30731
+ this.name = "WorkflowInputError";
30732
+ }
30733
+ };
30734
+ function isRecord(value) {
30735
+ return typeof value === "object" && value !== null;
31120
30736
  }
31121
- function extractHistoryImages(entry) {
31122
- if (!isRecord(entry) || !isRecord(entry.outputs)) {
30737
+ function summarizeNodeErrors(nodeErrors) {
30738
+ if (!isRecord(nodeErrors)) {
31123
30739
  return [];
31124
30740
  }
31125
- const images = [];
31126
- for (const output of Object.values(entry.outputs)) {
31127
- if (!isRecord(output) || !Array.isArray(output.images)) {
30741
+ const lines = [];
30742
+ for (const [nodeId, rawNodeError] of Object.entries(nodeErrors)) {
30743
+ if (!isRecord(rawNodeError)) {
31128
30744
  continue;
31129
30745
  }
31130
- for (const image of output.images) {
31131
- if (!isRecord(image) || typeof image.filename !== "string" || typeof image.type !== "string") {
30746
+ const classType = typeof rawNodeError.class_type === "string" ? ` (${rawNodeError.class_type})` : "";
30747
+ const rawErrors = Array.isArray(rawNodeError.errors) ? rawNodeError.errors : [];
30748
+ const missingInputs = [];
30749
+ const otherMessages = [];
30750
+ for (const rawError of rawErrors) {
30751
+ if (!isRecord(rawError)) {
31132
30752
  continue;
31133
30753
  }
31134
- const subfolder = typeof image.subfolder === "string" ? image.subfolder : "";
31135
- images.push({
31136
- filename: image.filename,
31137
- subfolder,
31138
- type: image.type,
31139
- view_url: buildViewUrl(
31140
- image.filename,
31141
- image.type,
31142
- subfolder || void 0
31143
- )
31144
- });
30754
+ const errorType = rawError.type;
30755
+ const errorMessage = rawError.message;
30756
+ const errorDetails = rawError.details;
30757
+ if (errorType === "required_input_missing" && typeof errorDetails === "string") {
30758
+ missingInputs.push(errorDetails);
30759
+ continue;
30760
+ }
30761
+ if (typeof errorMessage === "string") {
30762
+ otherMessages.push(
30763
+ typeof errorDetails === "string" && errorDetails.length > 0 ? `${errorMessage}: ${errorDetails}` : errorMessage
30764
+ );
30765
+ }
30766
+ }
30767
+ const parts = [];
30768
+ if (missingInputs.length > 0) {
30769
+ parts.push(`missing inputs: ${missingInputs.join(", ")}`);
30770
+ }
30771
+ if (otherMessages.length > 0) {
30772
+ parts.push(otherMessages.join("; "));
30773
+ }
30774
+ if (parts.length > 0) {
30775
+ lines.push(`Node ${nodeId}${classType}: ${parts.join("; ")}`);
31145
30776
  }
31146
30777
  }
31147
- return images;
31148
- }
31149
- function getHistoryStatus(entry) {
31150
- if (!isRecord(entry) || !isRecord(entry.status)) {
31151
- return "unknown";
31152
- }
31153
- return typeof entry.status.status_str === "string" ? entry.status.status_str : "unknown";
30778
+ return lines;
31154
30779
  }
31155
- function isHistoryCompleted(entry) {
31156
- if (!isRecord(entry) || !isRecord(entry.status)) {
31157
- return false;
30780
+ function summarizeComfyErrorDetails(details) {
30781
+ if (!isRecord(details)) {
30782
+ return "";
31158
30783
  }
31159
- if (entry.status.completed === true) {
31160
- return true;
30784
+ const lines = [];
30785
+ const errorDetails = isRecord(details.error) ? details.error : void 0;
30786
+ if (typeof errorDetails?.message === "string") {
30787
+ lines.push(errorDetails.message);
30788
+ } else if (typeof details.message === "string") {
30789
+ lines.push(details.message);
31161
30790
  }
31162
- return entry.status.status_str === "error";
31163
- }
31164
- function summarizeWaitForWorkflowText(structuredContent) {
31165
- return [
31166
- "# ComfyUI Workflow Wait Result",
31167
- "",
31168
- "## Summary",
31169
- formatMarkdownBulletList([
31170
- `Prompt ID: ${structuredContent.prompt_id}`,
31171
- `Status: ${structuredContent.status}`,
31172
- `Completed: ${structuredContent.completed ? "yes" : "no"}`,
31173
- `Timed Out: ${structuredContent.timed_out ? "yes" : "no"}`,
31174
- `Elapsed Seconds: ${structuredContent.elapsed_seconds}`,
31175
- `Images: ${structuredContent.image_count}`
31176
- ]),
31177
- "",
31178
- "## Images",
31179
- formatMarkdownBulletList(
31180
- structuredContent.images.map(
31181
- (image) => `${image.subfolder ? `${image.subfolder}/` : ""}${image.filename} (${image.type}) -> ${image.view_url}`
31182
- )
31183
- ),
31184
- "",
31185
- "## JSON",
31186
- formatJsonCodeBlock(structuredContent)
31187
- ].join("\n");
31188
- }
31189
- function summarizeWorkflowListText(heading, workflows, pagination) {
31190
- return [
31191
- heading,
31192
- "",
31193
- "## Pagination",
31194
- formatMarkdownBulletList([
31195
- `Returned: ${workflows.length}`,
31196
- `Total: ${pagination.total_count}`,
31197
- `Limit: ${pagination.limit}`,
31198
- `Offset: ${pagination.offset}`,
31199
- `Has More: ${pagination.has_more ? "yes" : "no"}`
31200
- ]),
31201
- "",
31202
- "## Workflows",
31203
- formatMarkdownBulletList(
31204
- workflows.map(
31205
- (workflow) => `${workflow.id} \u2014 ${workflow.name} | tags: ${workflow.tags.join(", ") || "none"} | editable inputs: ${workflow.editable_input_count}`
31206
- )
31207
- ),
31208
- "",
31209
- "## JSON",
31210
- formatJsonCodeBlock({
31211
- workflows,
31212
- pagination
31213
- })
31214
- ].join("\n");
31215
- }
31216
- function summarizeStoredWorkflowText(workflow) {
31217
- return [
31218
- "# ComfyUI Stored Workflow",
31219
- "",
31220
- "## Summary",
31221
- formatMarkdownBulletList([
31222
- `Workflow ID: ${workflow.workflow.id}`,
31223
- `Name: ${workflow.workflow.name}`,
31224
- `Description: ${workflow.workflow.description ?? "none"}`,
31225
- `Tags: ${workflow.workflow.tags.join(", ") || "none"}`,
31226
- `Created At: ${workflow.workflow.created_at}`,
31227
- `Updated At: ${workflow.workflow.updated_at}`,
31228
- `Node Count: ${workflow.workflow.node_count}`,
31229
- `Editable Inputs: ${workflow.workflow.editable_input_count}`,
31230
- `Source: ${workflow.workflow.source}`
31231
- ]),
31232
- "",
31233
- "## Editable Inputs",
31234
- formatMarkdownBulletList(
31235
- workflow.editable_inputs.map(
31236
- (input) => `${input.node_id}.${input.input_name} (${input.node_class}, ${input.value_type}) = ${String(
31237
- input.current_value
31238
- )}${input.options.length > 0 ? ` | options: ${input.options.join(", ")}` : ""}`
31239
- )
31240
- ),
31241
- "",
31242
- "## JSON",
31243
- formatJsonCodeBlock(workflow)
31244
- ].join("\n");
31245
- }
31246
- function summarizeSavedWorkflowText(workflow) {
31247
- return [
31248
- "# ComfyUI Workflow Saved",
31249
- "",
31250
- "## Summary",
31251
- formatMarkdownBulletList([
31252
- `Workflow ID: ${workflow.workflow.id}`,
31253
- `Name: ${workflow.workflow.name}`,
31254
- `Tags: ${workflow.workflow.tags.join(", ") || "none"}`,
31255
- `Editable Inputs: ${workflow.workflow.editable_input_count}`
31256
- ]),
31257
- "",
31258
- "## Next Step",
31259
- `Call comfyui_run_workflow with workflow_id="${workflow.workflow.id}" and optional overrides to reuse it.`,
31260
- "",
31261
- "## JSON",
31262
- formatJsonCodeBlock(workflow)
31263
- ].join("\n");
31264
- }
31265
- async function waitForWorkflowResult(params) {
31266
- const startTime = Date.now();
31267
- const timeoutMs = params.timeout_seconds * 1e3;
31268
- let latestEntry;
31269
- while (Date.now() - startTime <= timeoutMs) {
31270
- latestEntry = await fetchHistoryEntry(params.prompt_id);
31271
- if (latestEntry !== void 0) {
31272
- const images = extractHistoryImages(latestEntry);
31273
- const completed = isHistoryCompleted(latestEntry);
31274
- if (completed && (!params.require_images || images.length > 0)) {
31275
- return {
31276
- prompt_id: params.prompt_id,
31277
- status: getHistoryStatus(latestEntry),
31278
- completed: true,
31279
- timed_out: false,
31280
- elapsed_seconds: Number(
31281
- ((Date.now() - startTime) / 1e3).toFixed(3)
31282
- ),
31283
- image_count: images.length,
31284
- images,
31285
- history_entry: ensureRecord(latestEntry, "history entry")
31286
- };
31287
- }
31288
- }
31289
- await sleep(params.poll_interval_ms);
30791
+ const nodeLines = summarizeNodeErrors(details.node_errors);
30792
+ if (nodeLines.length > 0) {
30793
+ lines.push(...nodeLines);
31290
30794
  }
31291
- const timeoutImages = latestEntry !== void 0 ? extractHistoryImages(latestEntry) : [];
31292
- return {
31293
- prompt_id: params.prompt_id,
31294
- status: latestEntry !== void 0 ? getHistoryStatus(latestEntry) : "not_found",
31295
- completed: latestEntry !== void 0 ? isHistoryCompleted(latestEntry) : false,
31296
- timed_out: true,
31297
- elapsed_seconds: Number(((Date.now() - startTime) / 1e3).toFixed(3)),
31298
- image_count: timeoutImages.length,
31299
- images: timeoutImages,
31300
- history_entry: latestEntry !== void 0 ? ensureRecord(latestEntry, "history entry") : null
31301
- };
31302
- }
31303
- async function createStoredWorkflowRecord(params) {
31304
- const now = (/* @__PURE__ */ new Date()).toISOString();
31305
- const editableInputs = await deriveEditableWorkflowInputs(params.prompt);
31306
- const workflowId = params.workflow_id ? slugifyWorkflowId(params.workflow_id) : slugifyWorkflowId(params.name);
31307
- return {
31308
- workflow: {
31309
- id: workflowId,
31310
- name: params.name,
31311
- description: params.description,
31312
- tags: params.tags,
31313
- created_at: params.existing?.workflow.created_at ?? now,
31314
- updated_at: now,
31315
- node_count: Object.keys(params.prompt).length,
31316
- editable_input_count: editableInputs.length,
31317
- source: params.source
31318
- },
31319
- prompt: params.prompt,
31320
- editable_inputs: editableInputs
31321
- };
31322
- }
31323
- async function submitWorkflow(args) {
31324
- const result = IS_MOCK ? MOCK_FIXTURES.prompt_response : await fetchComfyJson(
31325
- "/prompt",
31326
- {
31327
- method: "POST",
31328
- body: JSON.stringify({
31329
- prompt: args.prompt,
31330
- client_id: args.client_id
31331
- })
31332
- }
31333
- );
31334
- return {
31335
- prompt_id: result.prompt_id,
31336
- number: result.number,
31337
- node_errors: result.node_errors
31338
- };
31339
- }
31340
- function summarizeWorkflowResponseText(result) {
31341
- const hasErrors = Object.keys(result.node_errors).length > 0;
31342
- return [
31343
- "# ComfyUI Workflow Submission",
31344
- "",
31345
- "## Summary",
31346
- formatMarkdownBulletList([
31347
- `Status: ${hasErrors ? "Submitted with node errors" : "Successfully queued"}`,
31348
- `Prompt ID: ${result.prompt_id}`,
31349
- `Queue Number: ${result.number}`,
31350
- `Node Errors: ${Object.keys(result.node_errors).length}`
31351
- ]),
31352
- "",
31353
- "## Next Step",
31354
- hasErrors ? "Fix the reported node_errors before retrying this workflow." : `Next step: call comfyui_get_history with prompt_id="${result.prompt_id}" to monitor outputs.`,
31355
- "",
31356
- "## JSON",
31357
- formatJsonCodeBlock({
31358
- prompt_id: result.prompt_id,
31359
- number: result.number,
31360
- node_errors: result.node_errors
31361
- })
31362
- ].join("\n");
30795
+ return lines.join("\n");
31363
30796
  }
31364
- function createAgentFacingResult(text, structuredContent) {
31365
- const normalizedText = text.trim();
31366
- if (normalizedText.length === 0) {
31367
- throw new Error(
31368
- "Tool response text must be non-empty. Do not rely on structuredContent alone because many MCP clients expose content.text to the model first."
31369
- );
30797
+ function formatWorkflowNodeErrors(nodeErrors) {
30798
+ const nodeLines = summarizeNodeErrors(nodeErrors);
30799
+ if (nodeLines.length === 0) {
30800
+ return "Workflow submission failed validation.";
31370
30801
  }
31371
- return {
31372
- content: [
31373
- {
31374
- type: "text",
31375
- text: normalizedText
31376
- }
31377
- ],
31378
- structuredContent
31379
- };
30802
+ return ["Workflow submission failed validation.", ...nodeLines].join("\n");
31380
30803
  }
31381
30804
  async function fetchComfyJson(apiPath, options) {
31382
30805
  const url2 = new URL(apiPath, COMFYUI_SERVER_URL).toString();
@@ -31385,666 +30808,857 @@ async function fetchComfyJson(apiPath, options) {
31385
30808
  response = await fetch(url2, {
31386
30809
  ...options,
31387
30810
  headers: {
31388
- "Content-Type": "application/json",
31389
- ...options?.headers
30811
+ Accept: "application/json",
30812
+ ...options?.headers || {}
31390
30813
  }
31391
30814
  });
31392
- } catch {
31393
- throw new ComfyApiError(
31394
- "Failed to connect to ComfyUI. Verify the server is running and COMFYUI_SERVER_URL is correct.",
31395
- 503
30815
+ } catch (error48) {
30816
+ throw new Error(
30817
+ `Failed to connect to ComfyUI server at ${COMFYUI_SERVER_URL}: ${error48 instanceof Error ? error48.message : String(error48)}`
31396
30818
  );
31397
30819
  }
31398
- const contentType = response.headers.get("content-type") || "";
30820
+ if (response.status === 404) {
30821
+ throw new ComfyApiError(`Not found: ${apiPath}`, 404, null);
30822
+ }
30823
+ const text = await response.text();
31399
30824
  let body;
31400
- if (contentType.includes("application/json")) {
31401
- body = await response.json();
31402
- } else {
31403
- body = await response.text();
30825
+ try {
30826
+ body = text ? JSON.parse(text) : null;
30827
+ } catch {
30828
+ throw new ComfyApiError(
30829
+ `Invalid JSON response from ComfyUI: ${text.slice(0, 100)}`,
30830
+ response.status,
30831
+ text
30832
+ );
31404
30833
  }
31405
30834
  if (!response.ok) {
31406
30835
  throw new ComfyApiError(
31407
- getErrorMessageFromDetails(body) || "The ComfyUI service returned an error.",
30836
+ "The ComfyUI service returned an error.",
31408
30837
  response.status,
31409
30838
  body
31410
30839
  );
31411
30840
  }
31412
30841
  return body;
31413
30842
  }
31414
- async function fetchHistoryEntry(promptId) {
31415
- if (IS_MOCK) {
31416
- return MOCK_FIXTURES.history[promptId];
31417
- }
31418
- try {
31419
- return await fetchComfyJson(
31420
- `/history/${encodeURIComponent(promptId)}`
31421
- );
31422
- } catch (error48) {
31423
- if (error48 instanceof ComfyApiError && error48.statusCode === 404) {
31424
- return void 0;
31425
- }
31426
- throw error48;
31427
- }
30843
+ async function fetchImageBase64(filename, type, subfolder) {
30844
+ const params = new URLSearchParams({ filename, type });
30845
+ if (subfolder) params.set("subfolder", subfolder);
30846
+ const url2 = new URL(
30847
+ `/view?${params.toString()}`,
30848
+ COMFYUI_SERVER_URL
30849
+ ).toString();
30850
+ const response = await fetch(url2);
30851
+ if (!response.ok)
30852
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
30853
+ const arrayBuffer = await response.arrayBuffer();
30854
+ const mimeType = response.headers.get("content-type") || "image/png";
30855
+ return {
30856
+ data: Buffer.from(arrayBuffer).toString("base64"),
30857
+ mimeType
30858
+ };
31428
30859
  }
31429
30860
  function handleComfyError(error48) {
31430
30861
  if (error48 instanceof ZodError) {
31431
30862
  return createValidationError(
31432
- error48.issues[0]?.path.join(".") || "arguments",
30863
+ "arguments",
31433
30864
  error48.issues[0]?.message || "Invalid input."
31434
30865
  );
31435
30866
  }
30867
+ if (error48 instanceof WorkflowInputError) {
30868
+ return error48.kind === "not_found" ? createNotFoundError(error48.message) : createValidationError(error48.field, error48.message);
30869
+ }
31436
30870
  if (error48 instanceof ComfyApiError) {
31437
- const nodeErrors = getNodeErrors(error48.details);
31438
- if (nodeErrors) {
31439
- const details = JSON.stringify(nodeErrors, null, 2);
31440
- return createValidationError(
31441
- "prompt",
31442
- `${error48.message}
31443
- node_errors:
31444
- ${details}`
31445
- );
31446
- }
31447
- return createApiError(error48.message, error48.statusCode);
30871
+ const detailSummary = summarizeComfyErrorDetails(error48.details);
30872
+ return {
30873
+ content: [
30874
+ {
30875
+ type: "text",
30876
+ text: detailSummary.length > 0 ? `${error48.message}
30877
+ ${detailSummary}` : error48.message
30878
+ }
30879
+ ],
30880
+ structuredContent: error48.details || {},
30881
+ isError: true
30882
+ };
31448
30883
  }
31449
30884
  return createInternalError(error48);
31450
30885
  }
31451
- async function handleGetSystemInfo() {
31452
- try {
31453
- const rawStats = IS_MOCK ? MOCK_FIXTURES.system_stats : await fetchComfyJson("/system_stats");
31454
- const structuredContent = normalizeSystemInfo(rawStats);
31455
- return createAgentFacingResult(
31456
- summarizeSystemInfoText(structuredContent),
31457
- structuredContent
30886
+ async function validateWorkflowPath(targetPath) {
30887
+ const resolvedPath = path.resolve(targetPath);
30888
+ const allowedRoots = getWorkflowDirectories();
30889
+ const isAllowed = allowedRoots.some(
30890
+ (root) => resolvedPath.startsWith(path.resolve(root))
30891
+ );
30892
+ if (!isAllowed) {
30893
+ throw new WorkflowInputError(
30894
+ `Access denied: ${targetPath} is outside allowed workflow directories.`,
30895
+ "workflow_file_path"
31458
30896
  );
31459
- } catch (error48) {
31460
- return handleComfyError(error48);
31461
30897
  }
31462
- }
31463
- async function handleListObjectInfo(args) {
31464
30898
  try {
31465
- const rawInfo = IS_MOCK ? args.node_class ? MOCK_FIXTURES.object_info[args.node_class] : MOCK_FIXTURES.object_info : await fetchComfyJson(
31466
- args.node_class ? `/object_info/${encodeURIComponent(args.node_class)}` : "/object_info"
30899
+ await fs.access(resolvedPath);
30900
+ return resolvedPath;
30901
+ } catch {
30902
+ throw new WorkflowInputError(
30903
+ `Workflow file not found: ${targetPath}`,
30904
+ "workflow_file_path",
30905
+ "not_found"
31467
30906
  );
31468
- if (args.node_class) {
31469
- if (rawInfo === void 0) {
31470
- return createNotFoundError(`Node class ${args.node_class}`);
31471
- }
31472
- const nodeDefinition = unwrapNamedNode(args.node_class, rawInfo);
31473
- return createAgentFacingResult(
31474
- summarizeNodeDefinitionText(args.node_class, nodeDefinition),
31475
- {
31476
- nodes: { [args.node_class]: nodeDefinition }
30907
+ }
30908
+ }
30909
+ function getWorkflowDirectories(extraPath) {
30910
+ const directories = [
30911
+ path.join(
30912
+ homedir(),
30913
+ "comfy",
30914
+ "ComfyUI",
30915
+ "user",
30916
+ "default",
30917
+ "workflows"
30918
+ ),
30919
+ path.join(homedir(), ".fre4x-comfyui", "workflows"),
30920
+ path.join(process.cwd(), "workflows")
30921
+ ];
30922
+ if (extraPath) {
30923
+ directories.push(extraPath);
30924
+ }
30925
+ return directories;
30926
+ }
30927
+ function getWorkflowNameCandidates(name) {
30928
+ const trimmed = name.trim();
30929
+ if (trimmed.length === 0) {
30930
+ return [];
30931
+ }
30932
+ if (trimmed.endsWith(".json")) {
30933
+ return [trimmed];
30934
+ }
30935
+ return [trimmed, `${trimmed}.json`];
30936
+ }
30937
+ function normalizeWorkflowFileName(fileName) {
30938
+ const trimmed = fileName.trim();
30939
+ if (trimmed.length === 0) {
30940
+ throw new WorkflowInputError(
30941
+ "Filename cannot be empty.",
30942
+ "output_file_name"
30943
+ );
30944
+ }
30945
+ if (path.basename(trimmed) !== trimmed) {
30946
+ throw new WorkflowInputError(
30947
+ "Filename must not include directory separators.",
30948
+ "output_file_name"
30949
+ );
30950
+ }
30951
+ return trimmed.endsWith(".json") ? trimmed : `${trimmed}.json`;
30952
+ }
30953
+ function isConnectionValue(value) {
30954
+ return Array.isArray(value) && value.length >= 2 && (typeof value[0] === "string" || typeof value[0] === "number") && typeof value[1] === "number";
30955
+ }
30956
+ async function getObjectInfo() {
30957
+ return IS_MOCK ? MOCK_FIXTURES.object_info : await fetchComfyJson(
30958
+ "/object_info"
30959
+ );
30960
+ }
30961
+ async function resolveWorkflowReference(args) {
30962
+ if (args.workflow_file_path) {
30963
+ const targetPath = args.workflow_file_path;
30964
+ if (path.basename(targetPath) === targetPath) {
30965
+ const candidates = getWorkflowNameCandidates(targetPath);
30966
+ for (const dir of getWorkflowDirectories()) {
30967
+ for (const candidate of candidates) {
30968
+ const candidatePath = path.join(dir, candidate);
30969
+ try {
30970
+ const content = await fs.readFile(
30971
+ candidatePath,
30972
+ "utf8"
30973
+ );
30974
+ return {
30975
+ targetPath: candidatePath,
30976
+ content
30977
+ };
30978
+ } catch {
30979
+ }
31477
30980
  }
30981
+ }
30982
+ throw new WorkflowInputError(
30983
+ `Workflow file not found: ${targetPath}`,
30984
+ "workflow_file_path",
30985
+ "not_found"
31478
30986
  );
31479
30987
  }
31480
- const nodes = ensureRecord(rawInfo, "object_info");
31481
- const entries = Object.entries(nodes);
31482
- const paginated = applyPagination(entries, args);
31483
- const structuredContent = {
31484
- nodes: Object.fromEntries(paginated.items),
31485
- pagination: {
31486
- total_count: paginated.total,
31487
- limit: paginated.limit,
31488
- offset: paginated.offset,
31489
- has_more: paginated.hasMore
31490
- }
30988
+ const validatedPath = await validateWorkflowPath(targetPath);
30989
+ return {
30990
+ targetPath: validatedPath,
30991
+ content: await fs.readFile(validatedPath, "utf8")
31491
30992
  };
31492
- return createAgentFacingResult(
31493
- [
31494
- "# ComfyUI Node Definitions",
31495
- "",
31496
- "## Pagination",
31497
- formatMarkdownBulletList([
31498
- `Returned: ${paginated.items.length}`,
31499
- `Total: ${paginated.total}`,
31500
- `Limit: ${paginated.limit}`,
31501
- `Offset: ${paginated.offset}`,
31502
- `Has More: ${paginated.hasMore ? "yes" : "no"}`
31503
- ]),
31504
- "",
31505
- "## Node Classes In This Page",
31506
- formatMarkdownBulletList(
31507
- paginated.items.map(([nodeClass]) => nodeClass)
31508
- ),
31509
- "",
31510
- "## Next Step",
31511
- paginated.hasMore ? `Use offset=${paginated.offset + paginated.limit} to retrieve the next page.` : "This page contains the final set of node classes for the current query.",
31512
- "If you need a usable workflow field list, call comfyui_list_object_info again with node_class set to the exact node name.",
31513
- "",
31514
- "## JSON",
31515
- formatJsonCodeBlock(structuredContent)
31516
- ].join("\n"),
31517
- structuredContent
30993
+ }
30994
+ if (args.workflow_id) {
30995
+ const requestedId = args.workflow_id.trim();
30996
+ if (requestedId.length === 0) {
30997
+ throw new WorkflowInputError(
30998
+ "Workflow ID cannot be empty.",
30999
+ "workflow_id"
31000
+ );
31001
+ }
31002
+ for (const dir of getWorkflowDirectories()) {
31003
+ try {
31004
+ const entries = await fs.readdir(dir, { withFileTypes: true });
31005
+ for (const entry of entries) {
31006
+ if (!entry.isFile() || !entry.name.endsWith(".json")) {
31007
+ continue;
31008
+ }
31009
+ const fullPath = path.join(dir, entry.name);
31010
+ const defaultId = getWorkflowId(fullPath);
31011
+ if (requestedId === defaultId || requestedId === entry.name.replace(/\.json$/, "")) {
31012
+ return {
31013
+ targetPath: fullPath,
31014
+ content: await fs.readFile(fullPath, "utf8")
31015
+ };
31016
+ }
31017
+ }
31018
+ } catch {
31019
+ }
31020
+ }
31021
+ throw new WorkflowInputError(
31022
+ `Workflow ID not found: ${args.workflow_id}`,
31023
+ "workflow_id",
31024
+ "not_found"
31025
+ );
31026
+ }
31027
+ throw new WorkflowInputError(
31028
+ "Must provide either workflow_file_path or workflow_id.",
31029
+ "workflow_id"
31030
+ );
31031
+ }
31032
+ async function normalizeWorkflowPrompt(workflow) {
31033
+ if (!isRecord(workflow)) {
31034
+ throw new WorkflowInputError(
31035
+ "Workflow must be a JSON object.",
31036
+ "workflow"
31037
+ );
31038
+ }
31039
+ let sourceFormat = "api";
31040
+ let normalizedWorkflow = workflow;
31041
+ if (isWebUIFormat(normalizedWorkflow)) {
31042
+ sourceFormat = "webui";
31043
+ normalizedWorkflow = convertWebUIToAPI(
31044
+ normalizedWorkflow,
31045
+ await getObjectInfo()
31046
+ );
31047
+ }
31048
+ const prompt = isRecord(normalizedWorkflow.prompt) && normalizedWorkflow.prompt ? normalizedWorkflow.prompt : normalizedWorkflow;
31049
+ if (!isRecord(prompt)) {
31050
+ throw new WorkflowInputError(
31051
+ "Workflow prompt must be a JSON object.",
31052
+ "workflow"
31518
31053
  );
31054
+ }
31055
+ return {
31056
+ prompt,
31057
+ sourceFormat
31058
+ };
31059
+ }
31060
+ async function loadWorkflow(args) {
31061
+ const { targetPath, content } = await resolveWorkflowReference(args);
31062
+ let parsed;
31063
+ try {
31064
+ parsed = JSON.parse(content);
31519
31065
  } catch (error48) {
31520
- return handleComfyError(error48);
31066
+ throw new WorkflowInputError(
31067
+ `Invalid JSON in ${targetPath}: ${error48 instanceof Error ? error48.message : String(error48)}`,
31068
+ args.workflow_file_path ? "workflow_file_path" : "workflow_id"
31069
+ );
31070
+ }
31071
+ const { prompt, sourceFormat } = await normalizeWorkflowPrompt(parsed);
31072
+ return {
31073
+ targetPath,
31074
+ prompt,
31075
+ sourceFormat
31076
+ };
31077
+ }
31078
+ function inferEditableRole(nodeId, classType, inputName, consumerMap) {
31079
+ if (classType === "CLIPTextEncode" && inputName === "text") {
31080
+ const consumers = consumerMap.get(nodeId) ?? [];
31081
+ if (consumers.some((consumer) => consumer.inputName === "positive")) {
31082
+ return "positive_prompt";
31083
+ }
31084
+ if (consumers.some((consumer) => consumer.inputName === "negative")) {
31085
+ return "negative_prompt";
31086
+ }
31087
+ return "prompt_text";
31088
+ }
31089
+ switch (inputName) {
31090
+ case "seed":
31091
+ return "seed";
31092
+ case "steps":
31093
+ return "steps";
31094
+ case "cfg":
31095
+ return "cfg";
31096
+ case "sampler_name":
31097
+ return "sampler";
31098
+ case "scheduler":
31099
+ return "scheduler";
31100
+ case "denoise":
31101
+ return "denoise";
31102
+ case "width":
31103
+ return "width";
31104
+ case "height":
31105
+ return "height";
31106
+ case "batch_size":
31107
+ return "batch_size";
31108
+ case "ckpt_name":
31109
+ return "checkpoint";
31110
+ case "filename_prefix":
31111
+ return "filename_prefix";
31112
+ case "text":
31113
+ return "text";
31114
+ default:
31115
+ return void 0;
31521
31116
  }
31522
31117
  }
31523
- async function handleExecuteWorkflow(args) {
31118
+ function formatValuePreview(value) {
31119
+ if (typeof value === "string") {
31120
+ return value.length > 100 ? `${value.slice(0, 97)}...` : value;
31121
+ }
31524
31122
  try {
31525
- const result = await submitWorkflow(args);
31526
- const structuredContent = {
31527
- prompt_id: result.prompt_id,
31528
- number: result.number,
31529
- node_errors: result.node_errors
31530
- };
31531
- if (args.await && Object.keys(result.node_errors).length === 0) {
31532
- const waitResult = await waitForWorkflowResult({
31533
- prompt_id: result.prompt_id,
31534
- timeout_seconds: args.timeout,
31535
- poll_interval_ms: 1e3,
31536
- require_images: false
31123
+ const serialized = JSON.stringify(value);
31124
+ if (!serialized) {
31125
+ return String(value);
31126
+ }
31127
+ return serialized.length > 100 ? `${serialized.slice(0, 97)}...` : serialized;
31128
+ } catch {
31129
+ return String(value);
31130
+ }
31131
+ }
31132
+ function analyzeEditableInputs(prompt) {
31133
+ const consumerMap = /* @__PURE__ */ new Map();
31134
+ for (const [nodeId, rawNode] of Object.entries(prompt)) {
31135
+ if (!isRecord(rawNode) || !isRecord(rawNode.inputs)) {
31136
+ continue;
31137
+ }
31138
+ const classType = typeof rawNode.class_type === "string" ? rawNode.class_type : "";
31139
+ for (const [inputName, value] of Object.entries(rawNode.inputs)) {
31140
+ if (!isConnectionValue(value)) {
31141
+ continue;
31142
+ }
31143
+ const sourceNodeId = String(value[0]);
31144
+ const consumers = consumerMap.get(sourceNodeId) ?? [];
31145
+ consumers.push({
31146
+ inputName,
31147
+ classType,
31148
+ nodeId
31149
+ });
31150
+ consumerMap.set(sourceNodeId, consumers);
31151
+ }
31152
+ }
31153
+ const editableInputs = [];
31154
+ for (const [nodeId, rawNode] of Object.entries(prompt)) {
31155
+ if (!isRecord(rawNode) || !isRecord(rawNode.inputs)) {
31156
+ continue;
31157
+ }
31158
+ const classType = typeof rawNode.class_type === "string" ? rawNode.class_type : "";
31159
+ for (const [inputName, value] of Object.entries(rawNode.inputs)) {
31160
+ if (isConnectionValue(value)) {
31161
+ continue;
31162
+ }
31163
+ editableInputs.push({
31164
+ path: `${nodeId}.inputs.${inputName}`,
31165
+ node_id: nodeId,
31166
+ class_type: classType,
31167
+ input_name: inputName,
31168
+ value,
31169
+ value_preview: formatValuePreview(value),
31170
+ role: inferEditableRole(
31171
+ nodeId,
31172
+ classType,
31173
+ inputName,
31174
+ consumerMap
31175
+ )
31537
31176
  });
31538
- const awaitedContent = {
31539
- ...structuredContent,
31540
- status: waitResult.status,
31541
- completed: waitResult.completed,
31542
- timed_out: waitResult.timed_out,
31543
- elapsed_seconds: waitResult.elapsed_seconds,
31544
- image_count: waitResult.image_count,
31545
- images: waitResult.images,
31546
- history_entry: waitResult.history_entry
31547
- };
31548
- return createAgentFacingResult(
31549
- [
31550
- summarizeWorkflowResponseText(awaitedContent),
31551
- "",
31552
- "## Await Result",
31553
- summarizeWaitForWorkflowText(waitResult)
31554
- ].join("\n"),
31555
- awaitedContent
31556
- );
31557
31177
  }
31558
- return createAgentFacingResult(
31559
- summarizeWorkflowResponseText(result),
31560
- structuredContent
31561
- );
31562
- } catch (error48) {
31563
- return handleComfyError(error48);
31564
31178
  }
31179
+ return editableInputs.sort(
31180
+ (left, right) => left.path.localeCompare(right.path, void 0, { numeric: true })
31181
+ );
31565
31182
  }
31566
- async function handleListWorkflows(args) {
31183
+ function formatEditableInputsText(editableInputs) {
31184
+ if (editableInputs.length === 0) {
31185
+ return "No literal editable inputs were detected.";
31186
+ }
31187
+ return formatListItems(editableInputs, (entry) => {
31188
+ const role = entry.role ? ` [${entry.role}]` : "";
31189
+ const classType = entry.class_type ? ` (${entry.class_type})` : "";
31190
+ return `${entry.path}${role}${classType}: ${entry.value_preview}`;
31191
+ });
31192
+ }
31193
+ function deriveSavedWorkflowFileName(sourcePath, requestedName) {
31194
+ if (requestedName) {
31195
+ return normalizeWorkflowFileName(requestedName);
31196
+ }
31197
+ if (sourcePath) {
31198
+ return normalizeWorkflowFileName(path.basename(sourcePath));
31199
+ }
31200
+ return `workflow-${Date.now()}.json`;
31201
+ }
31202
+ function applyOverrides(prompt, overrides) {
31203
+ for (const [key, value] of Object.entries(overrides)) {
31204
+ const parts = key.split(".");
31205
+ let current = prompt;
31206
+ for (let i = 0; i < parts.length - 1; i++) {
31207
+ const part = parts[i];
31208
+ if (typeof current[part] !== "object" || current[part] === null) {
31209
+ current[part] = {};
31210
+ }
31211
+ current = current[part];
31212
+ }
31213
+ current[parts[parts.length - 1]] = value;
31214
+ }
31215
+ }
31216
+ var InspectNodeSchema = paginationSchema.extend({
31217
+ node_class: z3.string().optional().describe(
31218
+ "Specific node class to inspect. If omitted, lists all available nodes."
31219
+ ),
31220
+ query: z3.string().optional().describe(
31221
+ "Search term to filter the list of available nodes by name or category."
31222
+ )
31223
+ });
31224
+ var DiscoverWorkflowsSchema = paginationSchema.extend({
31225
+ directory_path: z3.string().optional().describe("Custom directory path to scan for workflows.")
31226
+ });
31227
+ var WorkflowRunSchema = z3.object({
31228
+ workflow_file_path: z3.string().optional().describe(
31229
+ "Path or filename of workflow JSON. Scans default dirs if simple name provided."
31230
+ ),
31231
+ workflow_id: z3.string().optional().describe("Workflow identifier returned by discover_workflows."),
31232
+ overrides: z3.record(z3.string(), z3.unknown()).optional().describe(
31233
+ 'Key-value pairs to override using dot-notation. e.g. {"6.inputs.text": "dragon"}'
31234
+ ),
31235
+ await: z3.boolean().default(true).describe("Wait for completion and return native images."),
31236
+ timeout: z3.number().int().min(1).default(120).describe("Wait timeout in seconds.")
31237
+ });
31238
+ var GetWorkflowSchema = z3.object({
31239
+ workflow_file_path: z3.string().optional().describe("Workflow path or filename to inspect."),
31240
+ workflow_id: z3.string().optional().describe("Workflow identifier returned by discovery."),
31241
+ include_prompt: z3.boolean().default(false).describe("Include normalized API JSON in structuredContent.")
31242
+ });
31243
+ var SaveWorkflowSchema = z3.object({
31244
+ workflow: z3.unknown().optional().describe("Workflow JSON object to save locally."),
31245
+ workflow_file_path: z3.string().optional().describe("Existing workflow path or filename to save."),
31246
+ workflow_id: z3.string().optional().describe("Existing workflow identifier to save."),
31247
+ overrides: z3.record(z3.string(), z3.unknown()).optional().describe("Dot-notation overrides to persist before saving."),
31248
+ output_file_name: z3.string().optional().describe("Filename to write under ./workflows."),
31249
+ overwrite: z3.boolean().default(false).describe("Replace an existing local file when true."),
31250
+ include_prompt: z3.boolean().default(false).describe("Include saved API JSON in structuredContent.")
31251
+ });
31252
+ var WaitForWorkflowSchema = z3.object({
31253
+ prompt_id: z3.string().describe("The prompt_id of the submitted workflow."),
31254
+ timeout: z3.number().int().min(1).default(120).describe("Wait timeout in seconds.")
31255
+ });
31256
+ async function handleInspectNode(args) {
31567
31257
  try {
31568
- const workflows = (await listStoredWorkflowFiles()).map(
31569
- (workflow) => workflow.workflow
31258
+ const rawInfo = IS_MOCK ? MOCK_FIXTURES.object_info : await fetchComfyJson(
31259
+ "/object_info"
31570
31260
  );
31571
- const paginated = applyPagination(workflows, args);
31572
- const structuredContent = {
31573
- workflows: paginated.items,
31574
- pagination: {
31575
- total_count: paginated.total,
31576
- limit: paginated.limit,
31577
- offset: paginated.offset,
31578
- has_more: paginated.hasMore
31261
+ if (args.node_class) {
31262
+ const node = rawInfo[args.node_class];
31263
+ if (!node)
31264
+ return createNotFoundError(`Node class ${args.node_class}`);
31265
+ return {
31266
+ content: [
31267
+ { type: "text", text: JSON.stringify(node, null, 2) }
31268
+ ],
31269
+ structuredContent: node
31270
+ };
31271
+ }
31272
+ let classes = Object.keys(rawInfo);
31273
+ if (args.query) {
31274
+ const query = args.query.toLowerCase();
31275
+ classes = classes.filter((c) => {
31276
+ const node = rawInfo[c];
31277
+ const searchStr = `${c} ${node?.display_name || ""} ${node?.category || ""}`.toLowerCase();
31278
+ return searchStr.includes(query);
31279
+ });
31280
+ }
31281
+ const paginated = applyPagination(classes, args);
31282
+ const resultNodes = {};
31283
+ for (const c of paginated.items) {
31284
+ resultNodes[c] = rawInfo[c];
31285
+ }
31286
+ const text = `Available nodes (${paginated.total} total):
31287
+ ` + paginated.items.join(", ") + (paginated.hasMore ? `
31288
+
31289
+ ${formatPaginationFooter(
31290
+ paginated.offset,
31291
+ paginated.limit,
31292
+ paginated.total
31293
+ )}` : "");
31294
+ return {
31295
+ content: [{ type: "text", text }],
31296
+ structuredContent: {
31297
+ nodes: resultNodes,
31298
+ pagination: {
31299
+ total: paginated.total,
31300
+ offset: paginated.offset,
31301
+ limit: paginated.limit,
31302
+ has_more: paginated.hasMore
31303
+ }
31579
31304
  }
31580
31305
  };
31581
- return createAgentFacingResult(
31582
- summarizeWorkflowListText(
31583
- "# ComfyUI Stored Workflows",
31584
- structuredContent.workflows,
31585
- structuredContent.pagination
31586
- ),
31587
- structuredContent
31588
- );
31589
31306
  } catch (error48) {
31590
31307
  return handleComfyError(error48);
31591
31308
  }
31592
31309
  }
31593
- async function handleSearchWorkflows(args) {
31594
- try {
31595
- const normalizedQuery = args.query.trim().toLowerCase();
31596
- const matching = (await listStoredWorkflowFiles()).map((workflow) => workflow.workflow).filter(
31597
- (workflow) => [
31598
- workflow.id,
31599
- workflow.name,
31600
- workflow.description ?? "",
31601
- workflow.tags.join(" ")
31602
- ].join(" ").toLowerCase().includes(normalizedQuery)
31603
- );
31604
- const paginated = applyPagination(matching, args);
31605
- const structuredContent = {
31310
+ function getWorkflowId(absolutePath) {
31311
+ return crypto.createHash("sha256").update(absolutePath).digest("hex").substring(0, 8);
31312
+ }
31313
+ async function handleDiscoverWorkflows(args) {
31314
+ const pathsToScan = [
31315
+ path.join(
31316
+ homedir(),
31317
+ "comfy",
31318
+ "ComfyUI",
31319
+ "user",
31320
+ "default",
31321
+ "workflows"
31322
+ ),
31323
+ path.join(homedir(), ".fre4x-comfyui", "workflows"),
31324
+ path.join(process.cwd(), "workflows")
31325
+ ];
31326
+ if (args.directory_path) pathsToScan.push(args.directory_path);
31327
+ const foundWorkflows = [];
31328
+ for (const dir of pathsToScan) {
31329
+ try {
31330
+ const entries = await fs.readdir(dir, { withFileTypes: true });
31331
+ for (const entry of entries) {
31332
+ if (entry.isFile() && entry.name.endsWith(".json")) {
31333
+ const fullPath = path.join(dir, entry.name);
31334
+ const defaultId = getWorkflowId(fullPath);
31335
+ const defaultName = entry.name.replace(/\.json$/, "");
31336
+ try {
31337
+ const content = await fs.readFile(fullPath, "utf8");
31338
+ const json2 = JSON.parse(content);
31339
+ const id = defaultId;
31340
+ let name = defaultName;
31341
+ let isWebUI = false;
31342
+ if (typeof json2 === "object" && json2 !== null) {
31343
+ if (Array.isArray(json2.nodes) && Array.isArray(json2.links)) {
31344
+ isWebUI = true;
31345
+ }
31346
+ if (typeof json2.name === "string") name = json2.name;
31347
+ else if (json2.workflow && typeof json2.workflow.name === "string")
31348
+ name = json2.workflow.name;
31349
+ }
31350
+ foundWorkflows.push({
31351
+ id,
31352
+ name,
31353
+ path: fullPath,
31354
+ isWebUI
31355
+ });
31356
+ } catch (e) {
31357
+ foundWorkflows.push({
31358
+ id: defaultId,
31359
+ name: defaultName,
31360
+ path: fullPath,
31361
+ error: String(e)
31362
+ });
31363
+ }
31364
+ }
31365
+ }
31366
+ } catch (_e) {
31367
+ }
31368
+ }
31369
+ if (foundWorkflows.length === 0) {
31370
+ return {
31371
+ content: [
31372
+ {
31373
+ type: "text",
31374
+ text: `No workflows found in searched directories.`
31375
+ }
31376
+ ],
31377
+ structuredContent: {
31378
+ workflows: [],
31379
+ searched_paths: pathsToScan
31380
+ }
31381
+ };
31382
+ }
31383
+ const paginated = applyPagination(foundWorkflows, args);
31384
+ const text = `Found ${paginated.total} workflow files:
31385
+ ` + formatListItems(paginated.items, (w) => {
31386
+ const formatTag = w.isWebUI ? "\u{1F504} Web UI (Auto-converted on run)" : "\u2705 API Format";
31387
+ let line = `[ID: ${w.id}] [${formatTag}] Name: "${w.name}"`;
31388
+ if (w.error) line += ` (Error: ${w.error})`;
31389
+ return line;
31390
+ }) + (paginated.hasMore ? `
31391
+
31392
+ ${formatPaginationFooter(
31393
+ paginated.offset,
31394
+ paginated.limit,
31395
+ paginated.total
31396
+ )}` : "");
31397
+ return {
31398
+ content: [{ type: "text", text }],
31399
+ structuredContent: {
31606
31400
  workflows: paginated.items,
31607
31401
  pagination: {
31608
- total_count: paginated.total,
31609
- limit: paginated.limit,
31402
+ total: paginated.total,
31610
31403
  offset: paginated.offset,
31404
+ limit: paginated.limit,
31611
31405
  has_more: paginated.hasMore
31612
31406
  }
31613
- };
31614
- return createAgentFacingResult(
31615
- summarizeWorkflowListText(
31616
- `# ComfyUI Workflow Search
31617
-
31618
- Query: ${args.query}`,
31619
- structuredContent.workflows,
31620
- structuredContent.pagination
31621
- ),
31622
- structuredContent
31623
- );
31624
- } catch (error48) {
31625
- return handleComfyError(error48);
31626
- }
31407
+ }
31408
+ };
31627
31409
  }
31628
- async function handleGetWorkflow(args) {
31629
- try {
31630
- const workflow = await readStoredWorkflowFile(args.workflow_id);
31631
- if (!workflow) {
31632
- return createNotFoundError(`Workflow ${args.workflow_id}`);
31410
+ async function waitForWorkflowResult(prompt_id, timeout_seconds) {
31411
+ const start = Date.now();
31412
+ const end = start + timeout_seconds * 1e3;
31413
+ while (Date.now() < end) {
31414
+ const history = IS_MOCK ? MOCK_FIXTURES.history : await fetchComfyJson(
31415
+ `/history/${prompt_id}`
31416
+ ).catch(() => ({}));
31417
+ if (history?.[prompt_id]) {
31418
+ return history[prompt_id];
31633
31419
  }
31634
- return createAgentFacingResult(
31635
- summarizeStoredWorkflowText(workflow),
31636
- workflow
31637
- );
31638
- } catch (error48) {
31639
- return handleComfyError(error48);
31420
+ await new Promise((r) => setTimeout(r, 1e3));
31640
31421
  }
31422
+ throw new Error(`Timeout waiting for workflow ${prompt_id}`);
31641
31423
  }
31642
- async function handleSaveWorkflow(args) {
31424
+ async function handleWaitForWorkflow(args) {
31643
31425
  try {
31644
- const workflowId = args.workflow_id ? slugifyWorkflowId(args.workflow_id) : slugifyWorkflowId(args.name);
31645
- const existing = await readStoredWorkflowFile(workflowId);
31646
- if (existing && !args.overwrite) {
31647
- return createValidationError(
31648
- "workflow_id",
31649
- `Workflow ${workflowId} already exists. Pass overwrite=true to replace it.`
31650
- );
31651
- }
31652
- const workflow = await createStoredWorkflowRecord({
31653
- workflow_id: workflowId,
31654
- name: args.name,
31655
- description: args.description,
31656
- tags: args.tags,
31657
- prompt: args.prompt,
31658
- source: IS_MOCK ? "mock" : "local",
31659
- existing
31660
- });
31661
- await writeStoredWorkflowFile(workflow);
31662
- return createAgentFacingResult(
31663
- summarizeSavedWorkflowText(workflow),
31664
- workflow
31426
+ const historyEntry = await waitForWorkflowResult(
31427
+ args.prompt_id,
31428
+ args.timeout
31665
31429
  );
31430
+ const outputsRecord = historyEntry.outputs || {};
31431
+ const content = [];
31432
+ let summaryText = `Workflow ${args.prompt_id} completed.
31433
+ `;
31434
+ for (const [, output] of Object.entries(outputsRecord)) {
31435
+ if (output && Array.isArray(output.images)) {
31436
+ for (const image of output.images) {
31437
+ if (image.filename && image.type) {
31438
+ if (!IS_MOCK) {
31439
+ try {
31440
+ const base643 = await fetchImageBase64(
31441
+ image.filename,
31442
+ image.type,
31443
+ image.subfolder
31444
+ );
31445
+ content.push({
31446
+ type: "image",
31447
+ data: base643.data,
31448
+ mimeType: base643.mimeType
31449
+ });
31450
+ summaryText += `Fetched image: ${image.filename}
31451
+ `;
31452
+ } catch (e) {
31453
+ summaryText += `Failed to fetch image ${image.filename}: ${String(e)}
31454
+ `;
31455
+ }
31456
+ } else {
31457
+ summaryText += `Mock image: ${image.filename}
31458
+ `;
31459
+ }
31460
+ }
31461
+ }
31462
+ }
31463
+ }
31464
+ content.unshift({ type: "text", text: summaryText });
31465
+ return {
31466
+ content,
31467
+ structuredContent: historyEntry
31468
+ };
31666
31469
  } catch (error48) {
31667
31470
  return handleComfyError(error48);
31668
31471
  }
31669
31472
  }
31670
- async function handleRunWorkflow(args) {
31473
+ async function handleGetWorkflow(args) {
31671
31474
  try {
31672
- const workflow = await readStoredWorkflowFile(args.workflow_id);
31673
- if (!workflow) {
31674
- return createNotFoundError(`Workflow ${args.workflow_id}`);
31675
- }
31676
- const prompt = applyWorkflowOverrides(
31677
- workflow.prompt,
31678
- workflow.editable_inputs,
31679
- args.overrides
31680
- );
31681
- const submission = await submitWorkflow({
31682
- prompt,
31683
- client_id: args.client_id
31684
- });
31475
+ const loadedWorkflow = await loadWorkflow(args);
31476
+ const editableInputs = analyzeEditableInputs(loadedWorkflow.prompt);
31477
+ const workflowId = getWorkflowId(loadedWorkflow.targetPath);
31478
+ const text = [
31479
+ `Workflow ${workflowId} loaded from ${loadedWorkflow.targetPath}.`,
31480
+ loadedWorkflow.sourceFormat === "webui" ? "Source format: Web UI (normalized to API format)." : "Source format: API format.",
31481
+ `Editable inputs (${editableInputs.length}):`,
31482
+ formatEditableInputsText(editableInputs)
31483
+ ].join("\n");
31685
31484
  const structuredContent = {
31686
- prompt_id: submission.prompt_id,
31687
- number: submission.number,
31688
- node_errors: submission.node_errors,
31689
- workflow_id: workflow.workflow.id,
31690
- workflow_name: workflow.workflow.name,
31691
- applied_overrides: args.overrides
31485
+ workflow_id: workflowId,
31486
+ path: loadedWorkflow.targetPath,
31487
+ source_format: loadedWorkflow.sourceFormat,
31488
+ editable_inputs: editableInputs
31692
31489
  };
31693
- if (args.await && Object.keys(submission.node_errors).length === 0) {
31694
- const waitResult = await waitForWorkflowResult({
31695
- prompt_id: submission.prompt_id,
31696
- timeout_seconds: args.timeout,
31697
- poll_interval_ms: 1e3,
31698
- require_images: args.require_images
31699
- });
31700
- Object.assign(structuredContent, {
31701
- status: waitResult.status,
31702
- completed: waitResult.completed,
31703
- timed_out: waitResult.timed_out,
31704
- elapsed_seconds: waitResult.elapsed_seconds,
31705
- image_count: waitResult.image_count,
31706
- images: waitResult.images,
31707
- history_entry: waitResult.history_entry
31708
- });
31709
- return createAgentFacingResult(
31710
- [
31711
- "# ComfyUI Stored Workflow Run",
31712
- "",
31713
- "## Summary",
31714
- formatMarkdownBulletList([
31715
- `Workflow ID: ${workflow.workflow.id}`,
31716
- `Workflow Name: ${workflow.workflow.name}`,
31717
- `Prompt ID: ${submission.prompt_id}`,
31718
- `Applied Overrides: ${args.overrides.length}`
31719
- ]),
31720
- "",
31721
- summarizeWorkflowResponseText(structuredContent),
31722
- "",
31723
- "## Await Result",
31724
- summarizeWaitForWorkflowText(waitResult),
31725
- "",
31726
- "## JSON",
31727
- formatJsonCodeBlock(structuredContent)
31728
- ].join("\n"),
31729
- structuredContent
31730
- );
31490
+ if (args.include_prompt) {
31491
+ structuredContent.prompt = loadedWorkflow.prompt;
31731
31492
  }
31732
- return createAgentFacingResult(
31733
- [
31734
- "# ComfyUI Stored Workflow Run",
31735
- "",
31736
- "## Summary",
31737
- formatMarkdownBulletList([
31738
- `Workflow ID: ${workflow.workflow.id}`,
31739
- `Workflow Name: ${workflow.workflow.name}`,
31740
- `Prompt ID: ${submission.prompt_id}`,
31741
- `Applied Overrides: ${args.overrides.length}`
31742
- ]),
31743
- "",
31744
- summarizeWorkflowResponseText(structuredContent),
31745
- "",
31746
- "## JSON",
31747
- formatJsonCodeBlock(structuredContent)
31748
- ].join("\n"),
31493
+ return {
31494
+ content: [{ type: "text", text }],
31749
31495
  structuredContent
31750
- );
31496
+ };
31751
31497
  } catch (error48) {
31752
31498
  return handleComfyError(error48);
31753
31499
  }
31754
31500
  }
31755
- async function handleGetHistory(args) {
31501
+ async function handleSaveWorkflow(args) {
31756
31502
  try {
31757
- const rawHistory = IS_MOCK ? args.prompt_id ? MOCK_FIXTURES.history[args.prompt_id] : MOCK_FIXTURES.history : await fetchComfyJson(
31758
- args.prompt_id ? `/history/${encodeURIComponent(args.prompt_id)}` : "/history"
31759
- );
31760
- if (args.prompt_id) {
31761
- if (rawHistory === void 0) {
31762
- return createNotFoundError(`Prompt history ${args.prompt_id}`);
31503
+ let prompt;
31504
+ let sourcePath;
31505
+ if (args.workflow !== void 0) {
31506
+ const normalized = await normalizeWorkflowPrompt(args.workflow);
31507
+ prompt = normalized.prompt;
31508
+ } else {
31509
+ if (!args.workflow_file_path && !args.workflow_id) {
31510
+ return createValidationError(
31511
+ "workflow",
31512
+ "Provide workflow JSON or an existing workflow reference."
31513
+ );
31763
31514
  }
31764
- return createAgentFacingResult(
31765
- summarizeHistoryEntryText(args.prompt_id, rawHistory),
31766
- {
31767
- history: { [args.prompt_id]: rawHistory }
31768
- }
31515
+ const loadedWorkflow = await loadWorkflow(args);
31516
+ prompt = loadedWorkflow.prompt;
31517
+ sourcePath = loadedWorkflow.targetPath;
31518
+ }
31519
+ if (args.overrides) {
31520
+ applyOverrides(prompt, args.overrides);
31521
+ }
31522
+ const workflowsDir = path.join(process.cwd(), "workflows");
31523
+ await fs.mkdir(workflowsDir, { recursive: true });
31524
+ const outputFileName = deriveSavedWorkflowFileName(
31525
+ sourcePath,
31526
+ args.output_file_name
31527
+ );
31528
+ const outputPath = path.join(workflowsDir, outputFileName);
31529
+ let existedBeforeSave = false;
31530
+ try {
31531
+ await fs.access(outputPath);
31532
+ existedBeforeSave = true;
31533
+ } catch {
31534
+ existedBeforeSave = false;
31535
+ }
31536
+ if (existedBeforeSave && !args.overwrite) {
31537
+ return createValidationError(
31538
+ "output_file_name",
31539
+ `File already exists: ${outputFileName}. Set overwrite=true to replace it.`
31769
31540
  );
31770
31541
  }
31771
- const history = ensureRecord(rawHistory, "history");
31772
- const entries = Object.entries(history);
31773
- const paginated = applyPagination(entries, args);
31542
+ await fs.writeFile(
31543
+ outputPath,
31544
+ `${JSON.stringify(prompt, null, 2)}
31545
+ `,
31546
+ "utf8"
31547
+ );
31548
+ const editableInputs = analyzeEditableInputs(prompt);
31549
+ const workflowId = getWorkflowId(outputPath);
31774
31550
  const structuredContent = {
31775
- history: Object.fromEntries(paginated.items),
31776
- pagination: {
31777
- total_count: paginated.total,
31778
- limit: paginated.limit,
31779
- offset: paginated.offset,
31780
- has_more: paginated.hasMore
31781
- }
31551
+ workflow_id: workflowId,
31552
+ path: outputPath,
31553
+ source_path: sourcePath,
31554
+ overwritten: existedBeforeSave,
31555
+ editable_inputs: editableInputs
31782
31556
  };
31783
- return createAgentFacingResult(
31784
- [
31785
- "# ComfyUI History",
31786
- "",
31787
- "## Pagination",
31788
- formatMarkdownBulletList([
31789
- `Returned: ${paginated.items.length}`,
31790
- `Total: ${paginated.total}`,
31791
- `Limit: ${paginated.limit}`,
31792
- `Offset: ${paginated.offset}`,
31793
- `Has More: ${paginated.hasMore ? "yes" : "no"}`
31794
- ]),
31795
- "",
31796
- "## Prompt IDs In This Page",
31797
- formatMarkdownBulletList(
31798
- paginated.items.map(([promptId]) => promptId)
31799
- ),
31800
- "",
31801
- "## Next Step",
31802
- paginated.hasMore ? `Use offset=${paginated.offset + paginated.limit} to retrieve the next page.` : "This page contains the final set of history entries for the current query.",
31803
- "",
31804
- "## JSON",
31805
- formatJsonCodeBlock(structuredContent)
31806
- ].join("\n"),
31807
- structuredContent
31808
- );
31809
- } catch (error48) {
31810
- return handleComfyError(error48);
31811
- }
31812
- }
31813
- async function handleWaitForWorkflow(args) {
31814
- try {
31815
- const structuredContent = await waitForWorkflowResult(args);
31816
- return createAgentFacingResult(
31817
- summarizeWaitForWorkflowText(structuredContent),
31557
+ if (args.include_prompt) {
31558
+ structuredContent.prompt = prompt;
31559
+ }
31560
+ const persistedChanges = args.overrides && Object.keys(args.overrides).length > 0 ? `Persisted ${Object.keys(args.overrides).length} override(s).` : "No overrides were applied.";
31561
+ const text = [
31562
+ `Saved workflow ${workflowId} to ${outputPath}.`,
31563
+ persistedChanges,
31564
+ `Editable inputs (${editableInputs.length}):`,
31565
+ formatEditableInputsText(editableInputs)
31566
+ ].join("\n");
31567
+ return {
31568
+ content: [{ type: "text", text }],
31818
31569
  structuredContent
31819
- );
31570
+ };
31820
31571
  } catch (error48) {
31821
31572
  return handleComfyError(error48);
31822
31573
  }
31823
31574
  }
31824
- async function handleGetQueue() {
31575
+ async function handleWorkflowRun(args) {
31825
31576
  try {
31826
- const queue = IS_MOCK ? MOCK_FIXTURES.queue : await fetchComfyJson("/queue");
31827
- const queueRecord = ensureRecord(queue, "queue");
31828
- const structuredContent = {
31829
- queue_running: Array.isArray(queueRecord.queue_running) ? queueRecord.queue_running : [],
31830
- queue_pending: Array.isArray(queueRecord.queue_pending) ? queueRecord.queue_pending : []
31577
+ const { prompt } = await loadWorkflow(args);
31578
+ const promptObj = { ...prompt };
31579
+ if (args.overrides) {
31580
+ applyOverrides(promptObj, args.overrides);
31581
+ }
31582
+ const clientId = `mcp-${Date.now()}`;
31583
+ const result = IS_MOCK ? MOCK_FIXTURES.prompt_response : await fetchComfyJson("/prompt", {
31584
+ method: "POST",
31585
+ body: JSON.stringify({
31586
+ prompt: promptObj,
31587
+ client_id: clientId
31588
+ })
31589
+ });
31590
+ if (result.node_errors && Object.keys(result.node_errors).length > 0) {
31591
+ return {
31592
+ content: [
31593
+ {
31594
+ type: "text",
31595
+ text: formatWorkflowNodeErrors(result.node_errors)
31596
+ }
31597
+ ],
31598
+ structuredContent: result,
31599
+ isError: true
31600
+ };
31601
+ }
31602
+ if (args.await) {
31603
+ return await handleWaitForWorkflow({
31604
+ prompt_id: result.prompt_id,
31605
+ timeout: args.timeout
31606
+ });
31607
+ }
31608
+ return {
31609
+ content: [
31610
+ {
31611
+ type: "text",
31612
+ text: `Workflow submitted. prompt_id: ${result.prompt_id}, queue: ${result.number}`
31613
+ }
31614
+ ],
31615
+ structuredContent: result
31831
31616
  };
31832
- return createAgentFacingResult(
31833
- [
31834
- "# ComfyUI Queue",
31835
- "",
31836
- "## Summary",
31837
- formatMarkdownBulletList([
31838
- `Running Jobs: ${structuredContent.queue_running.length}`,
31839
- `Pending Jobs: ${structuredContent.queue_pending.length}`
31840
- ]),
31841
- "",
31842
- "## JSON",
31843
- formatJsonCodeBlock(structuredContent)
31844
- ].join("\n"),
31845
- structuredContent
31846
- );
31847
31617
  } catch (error48) {
31848
31618
  return handleComfyError(error48);
31849
31619
  }
31850
31620
  }
31851
- async function handleGetViewUrl(args) {
31852
- const url2 = buildViewUrl(args.filename, args.type, args.subfolder);
31853
- return createAgentFacingResult(
31854
- [
31855
- "# ComfyUI View URL",
31856
- "",
31857
- "## Summary",
31858
- formatMarkdownBulletList([
31859
- `Filename: ${args.filename}`,
31860
- `Subfolder: ${args.subfolder ?? "(root)"}`,
31861
- `Type: ${args.type}`,
31862
- `URL: ${url2}`
31863
- ]),
31864
- "",
31865
- "## JSON",
31866
- formatJsonCodeBlock({ url: url2 })
31867
- ].join("\n"),
31868
- { url: url2 }
31869
- );
31870
- }
31871
31621
  function createServer() {
31872
31622
  const server = new McpServer({
31873
31623
  name: "@fre4x/comfyui",
31874
31624
  version: PACKAGE_VERSION
31875
31625
  });
31876
- server.registerTool(
31877
- "comfyui_get_system_info",
31878
- {
31879
- title: "Get ComfyUI System Info",
31880
- description: "Get ComfyUI server status, hardware info, and VRAM usage.",
31881
- inputSchema: NoArgsSchema,
31882
- outputSchema: SystemInfoSchema,
31883
- annotations: {
31884
- readOnlyHint: true,
31885
- idempotentHint: true,
31886
- openWorldHint: true
31887
- }
31888
- },
31889
- handleGetSystemInfo
31626
+ server.tool(
31627
+ "comfyui_inspect_node",
31628
+ "Inspect a ComfyUI node or list all nodes.",
31629
+ InspectNodeSchema.shape,
31630
+ handleInspectNode
31890
31631
  );
31891
- server.registerTool(
31892
- "comfyui_list_object_info",
31893
- {
31894
- title: "List ComfyUI Node Definitions",
31895
- description: "Get definitions of available node classes, with optional node lookup and pagination.",
31896
- inputSchema: ListObjectInfoInputSchema,
31897
- outputSchema: ObjectInfoSchema,
31898
- annotations: {
31899
- readOnlyHint: true,
31900
- idempotentHint: true,
31901
- openWorldHint: true
31902
- }
31903
- },
31904
- handleListObjectInfo
31632
+ server.tool(
31633
+ "comfyui_discover_workflows",
31634
+ "Scan standard directories for .json workflow files.",
31635
+ DiscoverWorkflowsSchema.shape,
31636
+ handleDiscoverWorkflows
31905
31637
  );
31906
- server.registerTool(
31907
- "comfyui_execute_workflow",
31908
- {
31909
- title: "Execute ComfyUI Workflow",
31910
- description: "Submit a ComfyUI workflow in API JSON format and return its prompt_id.",
31911
- inputSchema: ExecuteWorkflowInputSchema,
31912
- outputSchema: WorkflowResponseSchema,
31913
- annotations: {
31914
- openWorldHint: true
31915
- }
31916
- },
31917
- handleExecuteWorkflow
31918
- );
31919
- server.registerTool(
31920
- "comfyui_list_workflows",
31921
- {
31922
- title: "List Stored Workflows",
31923
- description: "List stored ComfyUI workflows with pagination.",
31924
- inputSchema: ListWorkflowsInputSchema,
31925
- outputSchema: WorkflowListSchema,
31926
- annotations: {
31927
- readOnlyHint: true,
31928
- idempotentHint: true,
31929
- openWorldHint: true
31930
- }
31931
- },
31932
- handleListWorkflows
31933
- );
31934
- server.registerTool(
31935
- "comfyui_search_workflows",
31936
- {
31937
- title: "Search Stored Workflows",
31938
- description: "Search stored ComfyUI workflows by name, tags, or description.",
31939
- inputSchema: SearchWorkflowsInputSchema,
31940
- outputSchema: WorkflowListSchema,
31941
- annotations: {
31942
- readOnlyHint: true,
31943
- idempotentHint: true,
31944
- openWorldHint: true
31945
- }
31946
- },
31947
- handleSearchWorkflows
31638
+ server.tool(
31639
+ "comfyui_workflow_run",
31640
+ "Run a workflow from a file path or ID, applying overrides, and optionally returning images.",
31641
+ WorkflowRunSchema.shape,
31642
+ handleWorkflowRun
31948
31643
  );
31949
- server.registerTool(
31644
+ server.tool(
31950
31645
  "comfyui_get_workflow",
31951
- {
31952
- title: "Get Stored Workflow",
31953
- description: "Get a stored ComfyUI workflow and its editable inputs.",
31954
- inputSchema: GetWorkflowInputSchema,
31955
- outputSchema: StoredWorkflowSchema,
31956
- annotations: {
31957
- readOnlyHint: true,
31958
- idempotentHint: true,
31959
- openWorldHint: true
31960
- }
31961
- },
31646
+ "Inspect a stored workflow and list editable inputs.",
31647
+ GetWorkflowSchema.shape,
31962
31648
  handleGetWorkflow
31963
31649
  );
31964
- server.registerTool(
31650
+ server.tool(
31965
31651
  "comfyui_save_workflow",
31966
- {
31967
- title: "Save Workflow",
31968
- description: "Save a ComfyUI workflow for later reuse.",
31969
- inputSchema: SaveWorkflowInputSchema,
31970
- outputSchema: StoredWorkflowSchema,
31971
- annotations: {
31972
- openWorldHint: true
31973
- }
31974
- },
31652
+ "Save a workflow into ./workflows with optional edits.",
31653
+ SaveWorkflowSchema.shape,
31975
31654
  handleSaveWorkflow
31976
31655
  );
31977
- server.registerTool(
31978
- "comfyui_run_workflow",
31979
- {
31980
- title: "Run Stored Workflow",
31981
- description: "Run a stored workflow with optional overrides.",
31982
- inputSchema: RunWorkflowInputSchema,
31983
- outputSchema: RunStoredWorkflowSchema,
31984
- annotations: {
31985
- openWorldHint: true
31986
- }
31987
- },
31988
- handleRunWorkflow
31989
- );
31990
- server.registerTool(
31991
- "comfyui_get_history",
31992
- {
31993
- title: "Get ComfyUI History",
31994
- description: "Get execution history or a specific workflow result, with pagination for list mode.",
31995
- inputSchema: GetHistoryInputSchema,
31996
- outputSchema: HistorySchema,
31997
- annotations: {
31998
- readOnlyHint: true,
31999
- idempotentHint: true,
32000
- openWorldHint: true
32001
- }
32002
- },
32003
- handleGetHistory
32004
- );
32005
- server.registerTool(
31656
+ server.tool(
32006
31657
  "comfyui_wait_for_workflow",
32007
- {
32008
- title: "Wait for ComfyUI Workflow",
32009
- description: "Wait for a workflow result with timeout-based polling.",
32010
- inputSchema: WaitForWorkflowInputSchema,
32011
- outputSchema: WaitForWorkflowSchema,
32012
- annotations: {
32013
- openWorldHint: true
32014
- }
32015
- },
31658
+ "Wait for a submitted workflow to finish and fetch its images.",
31659
+ WaitForWorkflowSchema.shape,
32016
31660
  handleWaitForWorkflow
32017
31661
  );
32018
- server.registerTool(
32019
- "comfyui_get_queue",
32020
- {
32021
- title: "Get ComfyUI Queue",
32022
- description: "Get the current pending and running jobs in the ComfyUI queue.",
32023
- inputSchema: NoArgsSchema,
32024
- outputSchema: QueueSchema,
32025
- annotations: {
32026
- readOnlyHint: true,
32027
- idempotentHint: true,
32028
- openWorldHint: true
32029
- }
32030
- },
32031
- handleGetQueue
32032
- );
32033
- server.registerTool(
32034
- "comfyui_get_view_url",
32035
- {
32036
- title: "Get ComfyUI View URL",
32037
- description: "Build the direct URL for a generated file in ComfyUI.",
32038
- inputSchema: GetViewUrlInputSchema,
32039
- outputSchema: ViewUrlSchema,
32040
- annotations: {
32041
- readOnlyHint: true,
32042
- idempotentHint: true,
32043
- openWorldHint: true
32044
- }
32045
- },
32046
- handleGetViewUrl
32047
- );
32048
31662
  return server;
32049
31663
  }
32050
31664
  function isMainModule(url2) {