@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.
- package/.env +179 -0
- package/.github/workflows/ci.yml +63 -0
- package/.husky/pre-commit +11 -0
- package/LICENSE +21 -0
- package/README-en.md +635 -0
- package/README.md +656 -0
- package/__tests__/services/task/execution.test.ts +272 -0
- package/__tests__/services/task/wait.test.ts +220 -0
- package/__tests__/types/result.test.ts +227 -0
- package/__tests__/utils/mcp-helpers.test.ts +137 -0
- package/__tests__/utils/special-node-handler.test.ts +186 -0
- package/comfyui-mcp-server-skill/SKILL.md +43 -0
- package/comfyui-mcp-server-skill/references/api-json.md +323 -0
- package/comfyui-mcp-server-skill/references/catalog.md +251 -0
- package/comfyui-mcp-server-skill/rules/api-json.md +94 -0
- package/comfyui-mcp-server-skill/rules/catalog.md +93 -0
- package/comfyui-mcp-server-skill/rules/comfyui.md +158 -0
- package/configs/inspector/dev.json +11 -0
- package/configs/inspector/prod.json +9 -0
- package/docs/en/content/public/inspector-example.png +0 -0
- package/docs/en/content/public/workflow_name_example.png +0 -0
- package/docs/en/content/public/workflow_parameter_example.png +0 -0
- package/docs/en/md/Project-Advantages.md +75 -0
- package/docs/zh-CN/content/public/inspector-example.png +0 -0
- package/docs/zh-CN/content/public/workflow_name_example.png +0 -0
- package/docs/zh-CN/content/public/workflow_parameter_example.png +0 -0
- package/docs/zh-CN/md/why-us.md +51 -0
- package/example.json +26 -0
- package/jest.config.js +45 -0
- package/locales/en.json +150 -0
- package/locales/zh.json +150 -0
- package/package.json +68 -0
- package/src/api/api.ts +123 -0
- package/src/api/http.ts +70 -0
- package/src/constants/common.ts +38 -0
- package/src/constants/index.ts +1 -0
- package/src/hooks/websocket.ts +93 -0
- package/src/i18n.ts +40 -0
- package/src/index.ts +268 -0
- package/src/scripts/comfy-ui/list-history.ts +31 -0
- package/src/scripts/comfy-ui/run-ws.ts +23 -0
- package/src/scripts/ws/send-feature-flags.ts +23 -0
- package/src/server-stdio.ts +23 -0
- package/src/services/business.ts +194 -0
- package/src/services/dynamic-tool.ts +303 -0
- package/src/services/index.ts +19 -0
- package/src/services/storage/asset-storage.ts +48 -0
- package/src/services/storage/index.ts +2 -0
- package/src/services/storage/workflow-storage.ts +192 -0
- package/src/services/task/execution.ts +69 -0
- package/src/services/task/fetch.ts +131 -0
- package/src/services/task/index.ts +3 -0
- package/src/services/task/wait.ts +294 -0
- package/src/services/workflow/executor.ts +134 -0
- package/src/services/workflow/index.ts +1 -0
- package/src/tools/handlers/core-manual.ts +28 -0
- package/src/tools/handlers/index.ts +22 -0
- package/src/tools/handlers/interrupt-prompt.ts +35 -0
- package/src/tools/handlers/list-models.ts +51 -0
- package/src/tools/handlers/mount-workflow.ts +88 -0
- package/src/tools/handlers/prompt-result.ts +35 -0
- package/src/tools/handlers/prompts.ts +61 -0
- package/src/tools/handlers/queue-custom-prompt.ts +164 -0
- package/src/tools/handlers/queue-prompt.ts +170 -0
- package/src/tools/handlers/save-custom-workflow.ts +67 -0
- package/src/tools/handlers/save-task-assets.ts +82 -0
- package/src/tools/handlers/system-status.ts +30 -0
- package/src/tools/handlers/task-detail.ts +35 -0
- package/src/tools/handlers/tool-status-map.ts +33 -0
- package/src/tools/handlers/upload-assets.ts +82 -0
- package/src/tools/handlers/workflow-api.ts +46 -0
- package/src/tools/handlers/workflows-catalog.ts +79 -0
- package/src/tools/index.ts +101 -0
- package/src/types/common.ts +74 -0
- package/src/types/dynamic-tool.ts +85 -0
- package/src/types/enums/result.ts +29 -0
- package/src/types/execute.ts +24 -0
- package/src/types/object-info.ts +43 -0
- package/src/types/result.ts +126 -0
- package/src/types/task.ts +118 -0
- package/src/types/workflow.ts +111 -0
- package/src/types/ws.ts +80 -0
- package/src/utils/format.ts +134 -0
- package/src/utils/mcp-helpers.ts +110 -0
- package/src/utils/special-node-handler.ts +36 -0
- package/src/utils/workflow-converter.ts +140 -0
- package/src/utils/ws.ts +219 -0
- package/tsconfig.json +18 -0
package/src/utils/ws.ts
ADDED
|
@@ -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
|
+
}
|