@metabrain-labs/comfyui-mcp-server 1.0.0

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 (88) hide show
  1. package/.env +179 -0
  2. package/.github/workflows/ci.yml +63 -0
  3. package/.husky/pre-commit +11 -0
  4. package/LICENSE +21 -0
  5. package/README-en.md +635 -0
  6. package/README.md +656 -0
  7. package/__tests__/services/task/execution.test.ts +272 -0
  8. package/__tests__/services/task/wait.test.ts +220 -0
  9. package/__tests__/types/result.test.ts +227 -0
  10. package/__tests__/utils/mcp-helpers.test.ts +137 -0
  11. package/__tests__/utils/special-node-handler.test.ts +186 -0
  12. package/comfyui-mcp-server-skill/SKILL.md +43 -0
  13. package/comfyui-mcp-server-skill/references/api-json.md +323 -0
  14. package/comfyui-mcp-server-skill/references/catalog.md +251 -0
  15. package/comfyui-mcp-server-skill/rules/api-json.md +94 -0
  16. package/comfyui-mcp-server-skill/rules/catalog.md +93 -0
  17. package/comfyui-mcp-server-skill/rules/comfyui.md +158 -0
  18. package/configs/inspector/dev.json +11 -0
  19. package/configs/inspector/prod.json +9 -0
  20. package/docs/en/content/public/inspector-example.png +0 -0
  21. package/docs/en/content/public/workflow_name_example.png +0 -0
  22. package/docs/en/content/public/workflow_parameter_example.png +0 -0
  23. package/docs/en/md/Project-Advantages.md +75 -0
  24. package/docs/zh-CN/content/public/inspector-example.png +0 -0
  25. package/docs/zh-CN/content/public/workflow_name_example.png +0 -0
  26. package/docs/zh-CN/content/public/workflow_parameter_example.png +0 -0
  27. package/docs/zh-CN/md/why-us.md +51 -0
  28. package/example.json +26 -0
  29. package/jest.config.js +45 -0
  30. package/locales/en.json +150 -0
  31. package/locales/zh.json +150 -0
  32. package/package.json +68 -0
  33. package/src/api/api.ts +123 -0
  34. package/src/api/http.ts +70 -0
  35. package/src/constants/common.ts +38 -0
  36. package/src/constants/index.ts +1 -0
  37. package/src/hooks/websocket.ts +93 -0
  38. package/src/i18n.ts +40 -0
  39. package/src/index.ts +268 -0
  40. package/src/scripts/comfy-ui/list-history.ts +31 -0
  41. package/src/scripts/comfy-ui/run-ws.ts +23 -0
  42. package/src/scripts/ws/send-feature-flags.ts +23 -0
  43. package/src/server-stdio.ts +23 -0
  44. package/src/services/business.ts +194 -0
  45. package/src/services/dynamic-tool.ts +303 -0
  46. package/src/services/index.ts +19 -0
  47. package/src/services/storage/asset-storage.ts +48 -0
  48. package/src/services/storage/index.ts +2 -0
  49. package/src/services/storage/workflow-storage.ts +192 -0
  50. package/src/services/task/execution.ts +69 -0
  51. package/src/services/task/fetch.ts +131 -0
  52. package/src/services/task/index.ts +3 -0
  53. package/src/services/task/wait.ts +294 -0
  54. package/src/services/workflow/executor.ts +134 -0
  55. package/src/services/workflow/index.ts +1 -0
  56. package/src/tools/handlers/core-manual.ts +28 -0
  57. package/src/tools/handlers/index.ts +22 -0
  58. package/src/tools/handlers/interrupt-prompt.ts +35 -0
  59. package/src/tools/handlers/list-models.ts +51 -0
  60. package/src/tools/handlers/mount-workflow.ts +88 -0
  61. package/src/tools/handlers/prompt-result.ts +35 -0
  62. package/src/tools/handlers/prompts.ts +61 -0
  63. package/src/tools/handlers/queue-custom-prompt.ts +164 -0
  64. package/src/tools/handlers/queue-prompt.ts +170 -0
  65. package/src/tools/handlers/save-custom-workflow.ts +67 -0
  66. package/src/tools/handlers/save-task-assets.ts +82 -0
  67. package/src/tools/handlers/system-status.ts +30 -0
  68. package/src/tools/handlers/task-detail.ts +35 -0
  69. package/src/tools/handlers/tool-status-map.ts +33 -0
  70. package/src/tools/handlers/upload-assets.ts +82 -0
  71. package/src/tools/handlers/workflow-api.ts +46 -0
  72. package/src/tools/handlers/workflows-catalog.ts +79 -0
  73. package/src/tools/index.ts +101 -0
  74. package/src/types/common.ts +74 -0
  75. package/src/types/dynamic-tool.ts +85 -0
  76. package/src/types/enums/result.ts +29 -0
  77. package/src/types/execute.ts +24 -0
  78. package/src/types/object-info.ts +43 -0
  79. package/src/types/result.ts +126 -0
  80. package/src/types/task.ts +118 -0
  81. package/src/types/workflow.ts +111 -0
  82. package/src/types/ws.ts +80 -0
  83. package/src/utils/format.ts +134 -0
  84. package/src/utils/mcp-helpers.ts +110 -0
  85. package/src/utils/special-node-handler.ts +36 -0
  86. package/src/utils/workflow-converter.ts +140 -0
  87. package/src/utils/ws.ts +219 -0
  88. package/tsconfig.json +18 -0
@@ -0,0 +1,219 @@
1
+ import { randomUUID } from "crypto";
2
+ import "dotenv/config";
3
+ import WebSocket from "ws";
4
+ import { ComfyClientHook } from "../hooks/websocket";
5
+
6
+ export class ComfyClient {
7
+ private ws: WebSocket | null = null;
8
+ private clientId: string;
9
+ private serverAddress: string;
10
+ private reconnectAttempts: number = 0;
11
+ private maxReconnectAttempts: number = 5;
12
+ private reconnectDelay: number = 3000;
13
+ private isManualClose: boolean = false;
14
+
15
+ public hook: ComfyClientHook = new ComfyClientHook();
16
+
17
+ constructor() {
18
+ this.clientId = randomUUID().replace(/-/g, "");
19
+ const serverHost = process.env.COMFY_UI_SERVER_HOST || "127.0.0.1";
20
+ const serverPort = process.env.COMFY_UI_SERVER_PORT || "8188";
21
+ this.serverAddress = `${serverHost}:${serverPort}`;
22
+ }
23
+
24
+ /**
25
+ * @METHOD
26
+ * @description 连接到 ComfyUI WebSocket 服务器
27
+ */
28
+ public async connect(): Promise<void> {
29
+ const serverIp = process.env.COMFY_UI_SERVER_IP || "http://127.0.0.1:8188";
30
+ const urlObj = new URL(serverIp);
31
+ const protocol = urlObj.protocol === "https:" ? "wss" : "ws";
32
+
33
+ const wsUrl = `${protocol}://${this.serverAddress}/ws?clientId=${this.clientId}`;
34
+
35
+ console.error(`Connecting to ComfyUI: ${wsUrl}`);
36
+
37
+ return new Promise((resolve, reject) => {
38
+ this.ws = new WebSocket(wsUrl);
39
+
40
+ this.ws.on("open", () => {
41
+ this.sendJson({
42
+ type: "feature_flags",
43
+ data: {
44
+ supports_preview_metadata: true,
45
+ supports_manager_v4_ui: true,
46
+ },
47
+ });
48
+ this.reconnectAttempts = 0;
49
+ this.isManualClose = false;
50
+ resolve();
51
+ });
52
+
53
+ this.ws.on("message", (data: WebSocket.RawData) => {
54
+ this.handleMessage(data);
55
+ });
56
+
57
+ this.ws.on("error", (err) => {
58
+ console.error("WebSocket Error:", err.message);
59
+ reject(err);
60
+ });
61
+
62
+ this.ws.on("close", (code, reason) => {
63
+ console.error(
64
+ `Disconnected from ComfyUI (Code: ${code}, Reason: ${reason})`,
65
+ );
66
+
67
+ if (
68
+ !this.isManualClose &&
69
+ this.reconnectAttempts < this.maxReconnectAttempts
70
+ ) {
71
+ this.attemptReconnect();
72
+ }
73
+ });
74
+ });
75
+ }
76
+
77
+ /**
78
+ * @METHOD
79
+ * @description 自动重连逻辑
80
+ */
81
+ private attemptReconnect(): void {
82
+ this.reconnectAttempts++;
83
+ console.error(
84
+ `Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`,
85
+ );
86
+
87
+ setTimeout(() => {
88
+ this.connect().catch((err) => {
89
+ console.error(`Reconnect failed:`, err.message);
90
+ });
91
+ }, this.reconnectDelay);
92
+ }
93
+
94
+ /**
95
+ * @METHOD
96
+ * @description 处理接收到的消息
97
+ */
98
+ private handleMessage(rawData: WebSocket.RawData): void {
99
+ try {
100
+ const msgString = rawData.toString();
101
+ const msg = JSON.parse(msgString);
102
+
103
+ const { type, data } = msg;
104
+
105
+ this.dispatchMessage(type, data);
106
+ } catch (error) {
107
+ console.error("Failed to process message:", error);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * @METHOD
113
+ * @description 消息分发逻辑
114
+ */
115
+ private dispatchMessage(type: string, data: any): void {
116
+ switch (type) {
117
+ case "status":
118
+ this.hook.onStatus(data);
119
+ break;
120
+ case "execution_start":
121
+ this.hook.onExecutionStart(data);
122
+ break;
123
+ case "execution_cached":
124
+ this.hook.onExecutionCached(data);
125
+ break;
126
+ case "executing":
127
+ this.hook.onNodeExecuting(data);
128
+ break;
129
+ case "progress_state":
130
+ this.hook.onProgressState(data);
131
+ break;
132
+ case "progress":
133
+ this.hook.onProgress(data);
134
+ break;
135
+ case "executed":
136
+ this.hook.onExecuted(data);
137
+ break;
138
+ case "execution_success":
139
+ this.hook.onExecutionSuccess(data);
140
+ break;
141
+ case "execution_error":
142
+ this.hook.onExecutionError(data);
143
+ break;
144
+ case "execution_interrupted":
145
+ this.hook.onExecutionInterrupted(data);
146
+ break;
147
+ case "feature_flags":
148
+ this.hook.handleFeatureFlags(data);
149
+ break;
150
+ default:
151
+ this.hook.onUnhandledMessage(type, data);
152
+ break;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * @METHOD
158
+ * @description 发送 JSON 消息
159
+ */
160
+ public sendJson(data: any): boolean {
161
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
162
+ try {
163
+ const jsonString = JSON.stringify(data);
164
+ this.ws.send(jsonString);
165
+ console.error("Message sent:", data);
166
+ return true;
167
+ } catch (error) {
168
+ console.error("Failed to send message:", error);
169
+ return false;
170
+ }
171
+ } else {
172
+ console.error("Cannot send message: WebSocket is not open");
173
+ return false;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @METHOD
179
+ * @description 发送文本消息
180
+ */
181
+ public sendText(text: string): boolean {
182
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
183
+ this.ws.send(text);
184
+ console.error("Text sent:", text);
185
+ return true;
186
+ } else {
187
+ console.error("Cannot send text: WebSocket is not open");
188
+ return false;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * @METHOD
194
+ * @description 手动关闭连接
195
+ */
196
+ public close(code: number = 1000, reason: string = "Manual close"): void {
197
+ this.isManualClose = true;
198
+ if (this.ws) {
199
+ this.ws.close(code, reason);
200
+ this.ws = null;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * @METHOD
206
+ * @description 检查连接状态
207
+ */
208
+ public isConnected(): boolean {
209
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
210
+ }
211
+
212
+ /**
213
+ * @METHOD
214
+ * @description 获取客户端 ID
215
+ */
216
+ public getClientId(): string {
217
+ return this.clientId;
218
+ }
219
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "./build",
7
+ "rootDir": ".",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "resolveJsonModule": true,
14
+ "types": ["jest", "node"]
15
+ },
16
+ "include": ["src/**/*", "__tests__/**/*"],
17
+ "exclude": ["node_modules", "build"]
18
+ }