@bashcat/ai-image-chat-mcp 2.3.4 → 3.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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { z } from "zod";
5
5
  import dotenv from "dotenv";
6
6
  import axios from "axios";
7
7
  import * as fs from "fs";
@@ -13,972 +13,422 @@ const require = createRequire(import.meta.url);
13
13
  const packageJson = require("../package.json");
14
14
  // 載入環境變數
15
15
  dotenv.config();
16
- class AIImageChatMCPServer {
17
- server;
18
- apiKey;
19
- baseUrl;
20
- saveDirectory;
21
- dashScopeApiKey;
22
- constructor() {
23
- this.server = new Server({
24
- name: "ai-image-chat-mcp-server",
25
- version: packageJson.version,
26
- }, {
27
- capabilities: {
28
- tools: {},
29
- },
30
- });
31
- // 從環境變數獲取 API 配置
32
- this.apiKey = process.env.AI_API_KEY || "";
33
- this.baseUrl = process.env.AI_API_BASE_URL || "https://api.laozhang.ai/v1";
34
- this.saveDirectory = process.env.AI_IMAGE_SAVE_PATH || path.join(os.homedir(), "generated_images");
35
- // 支援 ALI_API_KEY 和 DASHSCOPE_API_KEY 兩種環境變數名稱
36
- this.dashScopeApiKey = process.env.ALI_API_KEY || process.env.DASHSCOPE_API_KEY || "";
37
- if (!this.apiKey) {
38
- throw new Error("AI_API_KEY 環境變數未設定");
39
- }
40
- // 確保保存目錄存在
41
- if (!fs.existsSync(this.saveDirectory)) {
42
- fs.mkdirSync(this.saveDirectory, { recursive: true });
43
- }
44
- this.setupToolHandlers();
16
+ // ===== 配置 =====
17
+ const apiKey = process.env.AI_API_KEY || "";
18
+ const baseUrl = process.env.AI_API_BASE_URL || "https://api.laozhang.ai/v1";
19
+ const saveDirectory = process.env.AI_IMAGE_SAVE_PATH || path.join(os.homedir(), "generated_images");
20
+ const dashScopeApiKey = process.env.ALI_API_KEY || process.env.DASHSCOPE_API_KEY || "";
21
+ if (!apiKey) {
22
+ throw new Error("AI_API_KEY 環境變數未設定");
23
+ }
24
+ if (!fs.existsSync(saveDirectory)) {
25
+ fs.mkdirSync(saveDirectory, { recursive: true });
26
+ }
27
+ // ===== 工具函式 =====
28
+ function generateFilename(prompt, outputFormat = "jpg") {
29
+ const cleanPrompt = prompt
30
+ .replace(/[^\w\s\u4e00-\u9fff]/g, "")
31
+ .replace(/\s+/g, "-")
32
+ .substring(0, 50);
33
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
34
+ const extension = outputFormat.toLowerCase() === "jpeg" ? "jpg" : outputFormat.toLowerCase();
35
+ return `${cleanPrompt}-${timestamp}.${extension}`;
36
+ }
37
+ function formatPathForDisplay(filePath) {
38
+ const homeDir = os.homedir();
39
+ return filePath.startsWith(homeDir) ? filePath.replace(homeDir, "~") : filePath;
40
+ }
41
+ async function downloadAndSaveImage(imageUrl, filename, outputFormat = "jpg") {
42
+ const response = await axios.get(imageUrl, { responseType: "arraybuffer" });
43
+ const filePath = path.join(saveDirectory, filename);
44
+ const fmt = outputFormat.toLowerCase() === "jpeg" ? "jpg" : outputFormat.toLowerCase();
45
+ const sharpInstance = sharp(Buffer.from(response.data));
46
+ if (fmt === "png") {
47
+ await sharpInstance.png().toFile(filePath);
45
48
  }
46
- generateFilename(prompt, outputFormat = 'jpg') {
47
- // 清理 prompt 作為檔名,移除特殊字符並限制長度
48
- const cleanPrompt = prompt
49
- .replace(/[^\w\s\u4e00-\u9fff]/g, '') // 保留字母、數字、空格和中文字符
50
- .replace(/\s+/g, '-') // 將空格替換為連字符
51
- .substring(0, 50); // 限制長度為50字符
52
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
53
- // 確保輸出格式為小寫
54
- const extension = outputFormat.toLowerCase() === 'jpeg' ? 'jpg' : outputFormat.toLowerCase();
55
- return `${cleanPrompt}-${timestamp}.${extension}`;
49
+ else if (fmt === "webp") {
50
+ await sharpInstance.webp({ quality: 90 }).toFile(filePath);
56
51
  }
57
- async downloadAndSaveImage(imageUrl, filename, outputFormat = 'jpg') {
58
- try {
59
- const response = await axios.get(imageUrl, { responseType: 'arraybuffer' });
60
- const filePath = path.join(this.saveDirectory, filename);
61
- // 根據輸出格式處理圖片
62
- const normalizedFormat = outputFormat.toLowerCase() === 'jpeg' ? 'jpg' : outputFormat.toLowerCase();
63
- if (normalizedFormat === 'jpg' || normalizedFormat === 'jpeg') {
64
- // 轉換為 JPG 格式
65
- await sharp(Buffer.from(response.data))
66
- .jpeg({ quality: 90 })
67
- .toFile(filePath);
68
- }
69
- else if (normalizedFormat === 'png') {
70
- // 保持 PNG 格式
71
- await sharp(Buffer.from(response.data))
72
- .png()
73
- .toFile(filePath);
74
- }
75
- else if (normalizedFormat === 'webp') {
76
- // 轉換為 WebP 格式
77
- await sharp(Buffer.from(response.data))
78
- .webp({ quality: 90 })
79
- .toFile(filePath);
80
- }
81
- else {
82
- // 預設轉換為 JPG
83
- await sharp(Buffer.from(response.data))
84
- .jpeg({ quality: 90 })
85
- .toFile(filePath);
86
- }
87
- return filePath;
88
- }
89
- catch (error) {
90
- throw new Error(`圖片下載或轉換失敗: ${error instanceof Error ? error.message : String(error)}`);
91
- }
52
+ else {
53
+ await sharpInstance.jpeg({ quality: 90 }).toFile(filePath);
92
54
  }
93
- async saveBase64Image(base64Data, filename, outputFormat = 'jpg') {
94
- try {
95
- // 解析 data URI 格式:data:image/png;base64,iVBORw0KGgoAAAANSU...
96
- const matches = base64Data.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/);
97
- if (!matches) {
98
- throw new Error('無效的 base64 圖片格式');
99
- }
100
- const [, , base64String] = matches;
101
- const buffer = Buffer.from(base64String, 'base64');
102
- const filePath = path.join(this.saveDirectory, filename);
103
- // 根據輸出格式處理圖片
104
- const normalizedFormat = outputFormat.toLowerCase() === 'jpeg' ? 'jpg' : outputFormat.toLowerCase();
105
- if (normalizedFormat === 'jpg' || normalizedFormat === 'jpeg') {
106
- // 轉換為 JPG 格式
107
- await sharp(buffer)
108
- .jpeg({ quality: 90 })
109
- .toFile(filePath);
110
- }
111
- else if (normalizedFormat === 'png') {
112
- // 保持 PNG 格式
113
- await sharp(buffer)
114
- .png()
115
- .toFile(filePath);
116
- }
117
- else if (normalizedFormat === 'webp') {
118
- // 轉換為 WebP 格式
119
- await sharp(buffer)
120
- .webp({ quality: 90 })
121
- .toFile(filePath);
122
- }
123
- else {
124
- // 預設轉換為 JPG
125
- await sharp(buffer)
126
- .jpeg({ quality: 90 })
127
- .toFile(filePath);
128
- }
129
- return filePath;
130
- }
131
- catch (error) {
132
- throw new Error(`Base64 圖片保存失敗: ${error instanceof Error ? error.message : String(error)}`);
133
- }
55
+ return filePath;
56
+ }
57
+ async function saveBase64Image(base64Data, filename, outputFormat = "jpg") {
58
+ const matches = base64Data.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/);
59
+ if (!matches) {
60
+ throw new Error("無效的 base64 圖片格式");
134
61
  }
135
- extractImageUrlFromContent(content) {
136
- // 嘗試從回應中提取圖片 URL
137
- const urlPatterns = [
138
- /https?:\/\/[^\s\)]+\.(?:jpg|jpeg|png|gif|webp)/gi,
139
- /!\[.*?\]\((https?:\/\/[^\)]+)\)/gi,
140
- /https?:\/\/[^\s\)]+/gi
141
- ];
142
- for (const pattern of urlPatterns) {
143
- const matches = content.match(pattern);
144
- if (matches && matches.length > 0) {
145
- const url = matches[0].replace(/^\!\[.*?\]\(/, '').replace(/\)$/, '');
146
- if (url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) {
147
- return url;
148
- }
149
- }
150
- }
151
- return null;
62
+ const buffer = Buffer.from(matches[2], "base64");
63
+ const filePath = path.join(saveDirectory, filename);
64
+ const fmt = outputFormat.toLowerCase() === "jpeg" ? "jpg" : outputFormat.toLowerCase();
65
+ const sharpInstance = sharp(buffer);
66
+ if (fmt === "png") {
67
+ await sharpInstance.png().toFile(filePath);
152
68
  }
153
- extractMultipleImageUrls(content) {
154
- // 提取所有圖片 URL
155
- const urlPatterns = [
156
- /https?:\/\/[^\s\)]+\.(?:jpg|jpeg|png|gif|webp)/gi,
157
- /!\[.*?\]\((https?:\/\/[^\)]+)\)/gi,
158
- /https?:\/\/[^\s\)]+/gi
159
- ];
160
- const urls = [];
161
- for (const pattern of urlPatterns) {
162
- const matches = content.match(pattern);
163
- if (matches) {
164
- for (const match of matches) {
165
- const url = match.replace(/^\!\[.*?\]\(/, '').replace(/\)$/, '');
166
- if (url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) {
167
- urls.push(url);
168
- }
169
- }
170
- }
171
- }
172
- // 去重並返回
173
- return [...new Set(urls)];
69
+ else if (fmt === "webp") {
70
+ await sharpInstance.webp({ quality: 90 }).toFile(filePath);
174
71
  }
175
- extractBase64Images(content) {
176
- // 提取所有 base64 圖片
177
- const base64Pattern = /data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+/gi;
178
- const matches = content.match(base64Pattern);
179
- return matches || [];
72
+ else {
73
+ await sharpInstance.jpeg({ quality: 90 }).toFile(filePath);
180
74
  }
181
- async processImageContent(content, prompt, outputFormat = 'jpg') {
182
- const imageUrls = this.extractMultipleImageUrls(content);
183
- const base64Images = this.extractBase64Images(content);
184
- const savedImages = [];
185
- // 處理 base64 圖片
186
- if (base64Images.length > 0) {
187
- for (let i = 0; i < base64Images.length; i++) {
188
- try {
189
- const filename = this.generateFilename(`${prompt}-${i + 1}`, outputFormat);
190
- const savedPath = await this.saveBase64Image(base64Images[i], filename, outputFormat);
191
- savedImages.push(savedPath);
192
- }
193
- catch (error) {
194
- console.error(`保存第 ${i + 1} base64 圖片失敗:`, error);
75
+ return filePath;
76
+ }
77
+ function extractMultipleImageUrls(content) {
78
+ const urlPatterns = [
79
+ /https?:\/\/[^\s\)]+\.(?:jpg|jpeg|png|gif|webp)/gi,
80
+ /!\[.*?\]\((https?:\/\/[^\)]+)\)/gi,
81
+ /https?:\/\/[^\s\)]+/gi,
82
+ ];
83
+ const urls = [];
84
+ for (const pattern of urlPatterns) {
85
+ const matches = content.match(pattern);
86
+ if (matches) {
87
+ for (const match of matches) {
88
+ const url = match.replace(/^\!\[.*?\]\(/, "").replace(/\)$/, "");
89
+ if (url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) {
90
+ urls.push(url);
195
91
  }
196
92
  }
197
93
  }
198
- return {
199
- imageUrls,
200
- savedImages,
201
- hasBase64: base64Images.length > 0
202
- };
203
94
  }
204
- // 阿里雲 DashScope 創建任務
205
- async createDashScopeTask(prompt, negativePrompt, options) {
206
- if (!this.dashScopeApiKey) {
207
- throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
208
- }
209
- const requestData = {
210
- model: options?.model || "wanx2.1-t2i-turbo",
211
- input: {
212
- prompt,
213
- negative_prompt: negativePrompt || "人物",
214
- },
215
- parameters: {
216
- size: options?.size || "1024*1024",
217
- n: options?.n || 1,
218
- seed: options?.seed,
219
- prompt_extend: options?.promptExtend !== false,
220
- watermark: options?.watermark || false,
221
- },
222
- };
223
- try {
224
- const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis", requestData, {
225
- headers: {
226
- "Content-Type": "application/json",
227
- "Authorization": `Bearer ${this.dashScopeApiKey}`,
228
- "X-DashScope-Async": "enable",
229
- },
230
- });
231
- return response.data;
232
- }
233
- catch (error) {
234
- if (axios.isAxiosError(error)) {
235
- const axiosError = error;
236
- const errorMessage = axiosError.response?.data?.message || axiosError.message;
237
- throw new Error(`阿里雲 DashScope 創建任務失敗: ${errorMessage}`);
238
- }
239
- throw new Error(`創建任務時發生未知錯誤: ${error}`);
240
- }
241
- }
242
- // 阿里雲 DashScope 查詢任務結果
243
- async queryDashScopeTask(taskId) {
244
- if (!this.dashScopeApiKey) {
245
- throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
246
- }
95
+ return [...new Set(urls)];
96
+ }
97
+ function extractBase64Images(content) {
98
+ const matches = content.match(/data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+/gi);
99
+ return matches || [];
100
+ }
101
+ async function processImageContent(content, prompt, outputFormat = "jpg") {
102
+ const imageUrls = extractMultipleImageUrls(content);
103
+ const base64Images = extractBase64Images(content);
104
+ const savedImages = [];
105
+ for (let i = 0; i < base64Images.length; i++) {
247
106
  try {
248
- const response = await axios.get(`https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`, {
249
- headers: {
250
- "Authorization": `Bearer ${this.dashScopeApiKey}`,
251
- },
252
- });
253
- return response.data;
107
+ const filename = generateFilename(`${prompt}-${i + 1}`, outputFormat);
108
+ const savedPath = await saveBase64Image(base64Images[i], filename, outputFormat);
109
+ savedImages.push(savedPath);
254
110
  }
255
111
  catch (error) {
256
- if (axios.isAxiosError(error)) {
257
- const axiosError = error;
258
- const errorMessage = axiosError.response?.data?.message || axiosError.message;
259
- throw new Error(`阿里雲 DashScope 查詢任務失敗: ${errorMessage}`);
260
- }
261
- throw new Error(`查詢任務時發生未知錯誤: ${error}`);
112
+ console.error(`保存第 ${i + 1} 張 base64 圖片失敗:`, error);
262
113
  }
263
114
  }
264
- // 阿里雲 DashScope 完整生圖流程
265
- async generateImageWithDashScope(prompt, negativePrompt, options) {
266
- // 步驟1: 創建任務
267
- const createResponse = await this.createDashScopeTask(prompt, negativePrompt, options);
268
- const taskId = createResponse.output.task_id;
269
- if (!taskId) {
270
- throw new Error("創建任務失敗,未獲取到任務ID");
271
- }
272
- // 步驟2: 輪詢查詢結果
273
- const maxWaitMinutes = options?.maxWaitMinutes || 5; // 預設最多等待5分鐘
274
- const maxAttempts = maxWaitMinutes * 6; // 每10秒查詢一次
275
- let attempts = 0;
276
- while (attempts < maxAttempts) {
277
- const queryResponse = await this.queryDashScopeTask(taskId);
278
- const taskStatus = queryResponse.output.task_status;
279
- if (taskStatus === "SUCCEEDED") {
280
- const results = queryResponse.output.results || [];
281
- return results;
282
- }
283
- else if (taskStatus === "FAILED") {
284
- throw new Error(`圖片生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
285
- }
286
- else if (taskStatus === "CANCELED") {
287
- throw new Error("任務已被取消");
288
- }
289
- // 等待10秒後再次查詢
290
- await new Promise(resolve => setTimeout(resolve, 10000));
291
- attempts++;
292
- }
293
- throw new Error(`圖片生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
115
+ return { imageUrls, savedImages, hasBase64: base64Images.length > 0 };
116
+ }
117
+ // ===== DashScope API =====
118
+ async function createDashScopeTask(prompt, negativePrompt, options) {
119
+ if (!dashScopeApiKey) {
120
+ throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
294
121
  }
295
- // 阿里雲 DashScope 視頻生成創建任務
296
- async createVideoGenerationTask(imgUrl, prompt, options) {
297
- if (!this.dashScopeApiKey) {
298
- throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
299
- }
300
- const requestData = {
301
- model: options?.model || "wanx2.1-i2v-turbo",
302
- input: {
303
- img_url: imgUrl,
304
- prompt: prompt,
305
- template: options?.template,
306
- },
307
- parameters: {
308
- resolution: options?.resolution || "720P",
309
- duration: options?.duration || 5,
310
- prompt_extend: options?.promptExtend !== false,
311
- seed: options?.seed,
122
+ const requestData = {
123
+ model: options?.model || "wanx2.1-t2i-turbo",
124
+ input: {
125
+ prompt,
126
+ negative_prompt: negativePrompt || "人物",
127
+ },
128
+ parameters: {
129
+ size: options?.size || "1024*1024",
130
+ n: options?.n || 1,
131
+ seed: options?.seed,
132
+ prompt_extend: options?.promptExtend !== false,
133
+ watermark: options?.watermark || false,
134
+ },
135
+ };
136
+ try {
137
+ const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis", requestData, {
138
+ headers: {
139
+ "Content-Type": "application/json",
140
+ Authorization: `Bearer ${dashScopeApiKey}`,
141
+ "X-DashScope-Async": "enable",
312
142
  },
313
- };
314
- try {
315
- const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", requestData, {
316
- headers: {
317
- "Content-Type": "application/json",
318
- "Authorization": `Bearer ${this.dashScopeApiKey}`,
319
- "X-DashScope-Async": "enable",
320
- },
321
- });
322
- return response.data;
323
- }
324
- catch (error) {
325
- if (axios.isAxiosError(error)) {
326
- const axiosError = error;
327
- const errorMessage = axiosError.response?.data?.message || axiosError.message;
328
- throw new Error(`阿里雲 DashScope 視頻生成任務創建失敗: ${errorMessage}`);
329
- }
330
- throw new Error(`創建視頻生成任務時發生未知錯誤: ${error}`);
331
- }
143
+ });
144
+ return response.data;
332
145
  }
333
- // 阿里雲 DashScope 視頻生成完整流程
334
- async generateVideoWithDashScope(imgUrl, prompt, options) {
335
- // 步驟1: 創建任務
336
- const createResponse = await this.createVideoGenerationTask(imgUrl, prompt, options);
337
- const taskId = createResponse.output.task_id;
338
- if (!taskId) {
339
- throw new Error("創建視頻生成任務失敗,未獲取到任務ID");
340
- }
341
- // 步驟2: 輪詢查詢結果
342
- const maxWaitMinutes = options?.maxWaitMinutes || 15; // 預設最多等待15分鐘(視頻生成較慢)
343
- const maxAttempts = maxWaitMinutes * 6; // 每10秒查詢一次
344
- let attempts = 0;
345
- while (attempts < maxAttempts) {
346
- const queryResponse = await this.queryDashScopeTask(taskId);
347
- const taskStatus = queryResponse.output.task_status;
348
- if (taskStatus === "SUCCEEDED") {
349
- const videoUrl = queryResponse.output.video_url;
350
- if (!videoUrl) {
351
- throw new Error("任務完成但未獲取到視頻URL");
352
- }
353
- return videoUrl;
354
- }
355
- else if (taskStatus === "FAILED") {
356
- throw new Error(`視頻生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
357
- }
358
- else if (taskStatus === "CANCELED") {
359
- throw new Error("視頻生成任務已被取消");
360
- }
361
- // 等待10秒後再次查詢
362
- await new Promise(resolve => setTimeout(resolve, 10000));
363
- attempts++;
146
+ catch (error) {
147
+ if (axios.isAxiosError(error)) {
148
+ const errorMessage = error.response?.data?.message || error.message;
149
+ throw new Error(`阿里雲 DashScope 創建任務失敗: ${errorMessage}`);
364
150
  }
365
- throw new Error(`視頻生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
151
+ throw new Error(`創建任務時發生未知錯誤: ${error}`);
366
152
  }
367
- setupToolHandlers() {
368
- // 列出可用工具
369
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
370
- return {
371
- tools: [
372
- {
373
- name: "generate_image",
374
- description: "統一圖像生成工具,支援通義萬相和老張API多種模型",
375
- inputSchema: {
376
- type: "object",
377
- properties: {
378
- prompt: {
379
- type: "string",
380
- description: "圖像生成的提示詞",
381
- },
382
- model: {
383
- type: "string",
384
- description: "選擇圖像生成模型",
385
- enum: [
386
- // 通義萬相模型
387
- "wanx2.1-t2i-turbo",
388
- "wanx2.1-t2i-plus",
389
- "wanx2.0-t2i-turbo",
390
- // 老張API模型
391
- "sora_image",
392
- "gpt-4o-image",
393
- "gemini-2.5-flash-image-preview"
394
- ],
395
- default: "gemini-2.5-flash-image-preview",
396
- },
397
- negative_prompt: {
398
- type: "string",
399
- description: "反向提示詞,僅通義萬相模型支援(可選,預設為「人物」)",
400
- default: "人物",
401
- },
402
- size: {
403
- type: "string",
404
- description: "輸出圖像的分辨率,僅通義萬相模型支援(可選,預設為 1024*1024)",
405
- default: "1024*1024",
406
- },
407
- n: {
408
- type: "number",
409
- description: "生成圖片的數量,僅通義萬相模型支援(可選,範圍 1-4,預設為 1)",
410
- minimum: 1,
411
- maximum: 4,
412
- default: 1,
413
- },
414
- seed: {
415
- type: "number",
416
- description: "隨機數種子,僅通義萬相模型支援(可選)",
417
- minimum: 0,
418
- maximum: 2147483647,
419
- },
420
- prompt_extend: {
421
- type: "boolean",
422
- description: "是否開啟 prompt 智能改寫,僅通義萬相模型支援(可選,預設為 true)",
423
- default: true,
424
- },
425
- watermark: {
426
- type: "boolean",
427
- description: "是否添加水印標識,僅通義萬相模型支援(可選,預設為 false)",
428
- default: false,
429
- },
430
- output_format: {
431
- type: "string",
432
- description: "輸出圖片格式(可選,預設為 jpg)",
433
- enum: ["jpg", "jpeg", "png", "webp"],
434
- default: "jpg",
435
- },
436
- system_prompt: {
437
- type: "string",
438
- description: "系統提示詞,僅老張API模型支援(可選)",
439
- },
440
- aspect_ratio: {
441
- type: "string",
442
- description: "圖片比例,僅老張API模型支援(可選),格式如:3:2, 16:9, 1:1",
443
- },
444
- max_wait_minutes: {
445
- type: "number",
446
- description: "最大等待時間(分鐘,可選,通義萬相預設5分鐘,老張API即時回應)",
447
- minimum: 1,
448
- maximum: 10,
449
- default: 5,
450
- },
451
- },
452
- required: ["prompt"],
453
- },
454
- },
455
- {
456
- name: "tongyi_wanxiang_generate_video",
457
- description: "使用通義萬相圖生視頻完整流程(創建任務 + 等待完成 + 返回視頻URL)",
458
- inputSchema: {
459
- type: "object",
460
- properties: {
461
- img_url: {
462
- type: "string",
463
- description: "首幀圖像的 URL(必需,需為公網可訪問地址)",
464
- },
465
- prompt: {
466
- type: "string",
467
- description: "文本提示詞,支持中英文,長度不超過800字符(可選)",
468
- },
469
- model: {
470
- type: "string",
471
- description: "模型名稱 - turbo生成快(3-5分鐘),plus品質高(7-10分鐘)",
472
- enum: ["wanx2.1-i2v-turbo", "wanx2.1-i2v-plus"],
473
- default: "wanx2.1-i2v-turbo",
474
- },
475
- template: {
476
- type: "string",
477
- description: "視頻特效模板(可選):squish(解壓捏捏)、flying(魔法懸浮)、carousel(時光木馬)",
478
- enum: ["squish", "flying", "carousel"],
479
- },
480
- resolution: {
481
- type: "string",
482
- description: "視頻分辨率檔位",
483
- enum: ["480P", "720P"],
484
- default: "720P",
485
- },
486
- duration: {
487
- type: "number",
488
- description: "視頻時長(秒)- turbo模型支持3-5秒,plus模型固定5秒",
489
- minimum: 3,
490
- maximum: 5,
491
- default: 5,
492
- },
493
- prompt_extend: {
494
- type: "boolean",
495
- description: "是否開啟 prompt 智能改寫",
496
- default: true,
497
- },
498
- seed: {
499
- type: "number",
500
- description: "隨機數種子",
501
- minimum: 0,
502
- maximum: 2147483647,
503
- },
504
- max_wait_minutes: {
505
- type: "number",
506
- description: "最大等待時間(分鐘,預設15分鐘)",
507
- minimum: 5,
508
- maximum: 30,
509
- default: 15,
510
- },
511
- },
512
- required: ["img_url"],
513
- },
514
- },
515
- {
516
- name: "chat_completion",
517
- description: "使用 AI 進行對話",
518
- inputSchema: {
519
- type: "object",
520
- properties: {
521
- message: {
522
- type: "string",
523
- description: "用戶訊息",
524
- },
525
- system_prompt: {
526
- type: "string",
527
- description: "系統提示詞(可選,如未提供將使用預設值)",
528
- },
529
- model: {
530
- type: "string",
531
- description: "使用的模型名稱",
532
- default: "gpt-4",
533
- },
534
- },
535
- required: ["message"],
536
- },
537
- },
538
- {
539
- name: "get_usage_guide",
540
- description: "獲取工具使用指南,包含所有可用模型的詳細說明",
541
- inputSchema: {
542
- type: "object",
543
- properties: {},
544
- required: [],
545
- },
546
- },
547
- ],
548
- };
549
- });
550
- // 處理工具調用
551
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
552
- const { name, arguments: args } = request.params;
553
- try {
554
- switch (name) {
555
- case "generate_image":
556
- return await this.handleUnifiedImageGeneration(args);
557
- case "tongyi_wanxiang_generate_video":
558
- return await this.handleTongyiWanxiangGenerateVideo(args);
559
- case "chat_completion":
560
- return await this.handleChatCompletion(args);
561
- case "get_usage_guide":
562
- return await this.handleUsageGuide(args);
563
- // 保持向後兼容
564
- case "tongyi_wanxiang_generate_image":
565
- return await this.handleTongyiWanxiangGenerateImage(args);
566
- case "tongyi_wanxiang_create_task":
567
- case "dashscope_create_task":
568
- return await this.handleTongyiWanxiangCreateTask(args);
569
- case "tongyi_wanxiang_query_task":
570
- case "dashscope_query_task":
571
- return await this.handleTongyiWanxiangQueryTask(args);
572
- case "dashscope_generate_image":
573
- return await this.handleTongyiWanxiangGenerateImage(args);
574
- case "tongyi_wanxiang_create_video_task":
575
- return await this.handleTongyiWanxiangCreateVideoTask(args);
576
- default:
577
- throw new McpError(ErrorCode.MethodNotFound, `未知的工具: ${name}`);
578
- }
579
- }
580
- catch (error) {
581
- if (error instanceof McpError) {
582
- throw error;
583
- }
584
- throw new McpError(ErrorCode.InternalError, `工具執行錯誤: ${error instanceof Error ? error.message : String(error)}`);
585
- }
153
+ }
154
+ async function queryDashScopeTask(taskId) {
155
+ if (!dashScopeApiKey) {
156
+ throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
157
+ }
158
+ try {
159
+ const response = await axios.get(`https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`, {
160
+ headers: { Authorization: `Bearer ${dashScopeApiKey}` },
586
161
  });
162
+ return response.data;
587
163
  }
588
- async handleImageGeneration(args) {
589
- const { prompt, system_prompt, model = "gpt-4o-image", output_format = "jpg", n = 1, aspect_ratio } = args;
590
- if (!prompt) {
591
- throw new McpError(ErrorCode.InvalidParams, "需要提供 prompt 參數");
592
- }
593
- // 模型資訊
594
- const modelInfo = {
595
- 'sora_image': { name: 'Sora圖像生成', provider: '老張API', feature: '基於Sora技術的圖像生成,$0.01/次' },
596
- 'gpt-4o-image': { name: 'GPT-4o圖像生成', provider: '老張API', feature: 'GPT-4o視覺模型,$0.01/次' },
597
- 'gemini-2.5-flash-image-preview': { name: 'Gemini 2.5 Flash圖像生成', provider: '老張API', feature: 'Google Gemini 2.5 Flash預覽版,高速生成,$0.01/次' }
598
- };
599
- const modelDesc = modelInfo[model];
600
- // 根據老張API文檔格式,構建正確的請求
601
- let finalPrompt = prompt;
602
- // 如果有比例要求,添加到提示詞末尾(如:【3:2】)
603
- if (aspect_ratio) {
604
- finalPrompt = `${prompt}【${aspect_ratio}】`;
605
- }
606
- const messages = [
607
- {
608
- role: "user",
609
- content: [
610
- {
611
- type: "text",
612
- text: finalPrompt
613
- }
614
- ]
615
- }
616
- ];
617
- const requestData = {
618
- model,
619
- n: Math.min(Math.max(n, 1), 4), // 限制在1-4之間
620
- messages
621
- };
622
- try {
623
- const response = await axios.post(`${this.baseUrl}/chat/completions`, requestData, {
624
- headers: {
625
- "Content-Type": "application/json",
626
- "Authorization": `Bearer ${this.apiKey}`,
627
- },
628
- });
629
- const result = response.data;
630
- const generatedContent = result.choices?.[0]?.message?.content || "未生成內容";
631
- // 使用新的圖片處理邏輯,同時支援 URL 和 base64
632
- const imageData = await this.processImageContent(generatedContent, prompt, output_format);
633
- let responseText = `${modelDesc.name}圖像生成完成!\n\n`;
634
- responseText += `🎨 使用模型: ${modelDesc.name}\n`;
635
- responseText += `💰 費用: ${modelDesc.feature}\n`;
636
- responseText += `📝 提示詞: ${finalPrompt}\n`;
637
- responseText += `🖼️ 生成數量: ${n}張\n`;
638
- if (system_prompt) {
639
- responseText += `🔧 系統提示: ${system_prompt}\n`;
640
- }
641
- // 優先顯示 base64 圖片的保存結果
642
- if (imageData.savedImages.length > 0) {
643
- responseText += `\n💾 圖片已保存:\n`;
644
- imageData.savedImages.forEach((filePath, index) => {
645
- responseText += `${index + 1}. ${filePath}\n`;
646
- });
647
- }
648
- // 如果有 URL 圖片,也顯示
649
- if (imageData.imageUrls.length > 0) {
650
- responseText += `\n🔗 圖片URLs:\n`;
651
- imageData.imageUrls.forEach((url, index) => {
652
- responseText += `${index + 1}. ${url}\n`;
653
- });
654
- }
655
- // 如果沒有找到任何圖片(URL 或 base64),顯示原始回應
656
- if (!imageData.hasBase64 && imageData.imageUrls.length === 0) {
657
- responseText += `\n📄 API回應內容:\n${generatedContent}`;
658
- }
659
- return {
660
- content: [
661
- {
662
- type: "text",
663
- text: responseText,
664
- },
665
- ],
666
- };
667
- }
668
- catch (error) {
669
- if (axios.isAxiosError(error)) {
670
- const axiosError = error;
671
- const errorMessage = axiosError.response?.data?.error?.message || axiosError.message;
672
- // 根據文檔建議,如果sora_image失敗,建議切換到其他模型
673
- let suggestionText = "";
674
- if (model === "sora_image") {
675
- suggestionText = "\n💡 提示:如果sora_image持續失敗,建議切換到gpt-4o-image或gemini-2.5-flash-image-preview模型重試";
676
- }
677
- throw new McpError(ErrorCode.InternalError, `老張API請求失敗: ${errorMessage}${suggestionText}`);
678
- }
679
- throw new McpError(ErrorCode.InternalError, `未知錯誤: ${error instanceof Error ? error.message : String(error)}`);
164
+ catch (error) {
165
+ if (axios.isAxiosError(error)) {
166
+ const errorMessage = error.response?.data?.message || error.message;
167
+ throw new Error(`阿里雲 DashScope 查詢任務失敗: ${errorMessage}`);
680
168
  }
169
+ throw new Error(`查詢任務時發生未知錯誤: ${error}`);
681
170
  }
682
- async handleTongyiWanxiangCreateTask(args) {
683
- const { prompt, model, negative_prompt, size, n, seed, prompt_extend, watermark } = args;
684
- if (!prompt) {
685
- throw new McpError(ErrorCode.InvalidParams, "需要提供 prompt 參數");
686
- }
687
- try {
688
- const response = await this.createDashScopeTask(prompt, negative_prompt, {
689
- model,
690
- size,
691
- n,
692
- seed,
693
- promptExtend: prompt_extend,
694
- watermark,
695
- });
696
- return {
697
- content: [
698
- {
699
- type: "text",
700
- text: `通義萬相文生圖任務創建成功!\n\n任務ID: ${response.output.task_id}\n任務狀態: ${response.output.task_status}\n使用模型: ${model || 'wanx2.1-t2i-turbo'}\n請求ID: ${response.request_id}\n\n請使用 tongyi_wanxiang_query_task 工具查詢任務結果。`,
701
- },
702
- ],
703
- };
704
- }
705
- catch (error) {
706
- throw new McpError(ErrorCode.InternalError, `創建任務失敗: ${error instanceof Error ? error.message : String(error)}`);
707
- }
171
+ }
172
+ async function generateImageWithDashScope(prompt, negativePrompt, options) {
173
+ const createResponse = await createDashScopeTask(prompt, negativePrompt, options);
174
+ const taskId = createResponse.output.task_id;
175
+ if (!taskId)
176
+ throw new Error("創建任務失敗,未獲取到任務ID");
177
+ const maxWaitMinutes = options?.maxWaitMinutes || 5;
178
+ const maxAttempts = maxWaitMinutes * 6;
179
+ let attempts = 0;
180
+ while (attempts < maxAttempts) {
181
+ const queryResponse = await queryDashScopeTask(taskId);
182
+ const status = queryResponse.output.task_status;
183
+ if (status === "SUCCEEDED")
184
+ return queryResponse.output.results || [];
185
+ if (status === "FAILED")
186
+ throw new Error(`圖片生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
187
+ if (status === "CANCELED")
188
+ throw new Error("任務已被取消");
189
+ await new Promise((resolve) => setTimeout(resolve, 10000));
190
+ attempts++;
708
191
  }
709
- async handleTongyiWanxiangQueryTask(args) {
710
- const { task_id } = args;
711
- if (!task_id) {
712
- throw new McpError(ErrorCode.InvalidParams, "需要提供 task_id 參數");
713
- }
714
- try {
715
- const response = await this.queryDashScopeTask(task_id);
716
- const { output } = response;
717
- let responseText = `通義萬相文生圖任務查詢結果:\n\n`;
718
- responseText += `任務ID: ${output.task_id}\n`;
719
- responseText += `任務狀態: ${output.task_status}\n`;
720
- responseText += `請求ID: ${response.request_id}\n`;
721
- if (output.submit_time) {
722
- responseText += `提交時間: ${output.submit_time}\n`;
723
- }
724
- if (output.scheduled_time) {
725
- responseText += `開始時間: ${output.scheduled_time}\n`;
726
- }
727
- if (output.end_time) {
728
- responseText += `完成時間: ${output.end_time}\n`;
729
- }
730
- if (output.task_status === "SUCCEEDED" && output.results) {
731
- responseText += `\n✅ 任務完成!生成了 ${output.results.length} 張圖片:\n`;
732
- output.results.forEach((result, index) => {
733
- responseText += `\n圖片 ${index + 1}:\n`;
734
- responseText += `原始提示詞: ${result.orig_prompt}\n`;
735
- if (result.actual_prompt) {
736
- responseText += `優化後提示詞: ${result.actual_prompt}\n`;
737
- }
738
- responseText += `圖片URL: ${result.url}\n`;
739
- });
740
- if (output.task_metrics) {
741
- responseText += `\n📊 任務統計:\n`;
742
- responseText += `總計: ${output.task_metrics.TOTAL}\n`;
743
- responseText += `成功: ${output.task_metrics.SUCCEEDED}\n`;
744
- responseText += `失敗: ${output.task_metrics.FAILED}\n`;
745
- }
746
- }
747
- else if (output.task_status === "FAILED") {
748
- responseText += `\n❌ 任務失敗\n`;
749
- if (output.message) {
750
- responseText += `錯誤訊息: ${output.message}\n`;
751
- }
752
- }
753
- else if (output.task_status === "PENDING") {
754
- responseText += `\n⏳ 任務排隊中,請稍後再次查詢\n`;
755
- }
756
- else if (output.task_status === "RUNNING") {
757
- responseText += `\n🔄 任務處理中,請稍後再次查詢\n`;
758
- }
759
- return {
760
- content: [
761
- {
762
- type: "text",
763
- text: responseText,
764
- },
765
- ],
766
- };
767
- }
768
- catch (error) {
769
- throw new McpError(ErrorCode.InternalError, `查詢任務失敗: ${error instanceof Error ? error.message : String(error)}`);
770
- }
192
+ throw new Error(`圖片生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
193
+ }
194
+ async function createVideoGenerationTask(imgUrl, prompt, options) {
195
+ if (!dashScopeApiKey) {
196
+ throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
771
197
  }
772
- async handleTongyiWanxiangGenerateImage(args) {
773
- const { prompt, model, negative_prompt, size, n, seed, prompt_extend, watermark, output_format, max_wait_minutes } = args;
774
- if (!prompt) {
775
- throw new McpError(ErrorCode.InvalidParams, "需要提供 prompt 參數");
776
- }
777
- // 模型資訊
778
- const modelInfo = {
779
- 'wanx2.1-t2i-turbo': { name: '通義萬相2.1-Turbo', price: '0.14元/張', feature: '生成速度更快' },
780
- 'wanx2.1-t2i-plus': { name: '通義萬相2.1-Plus', price: '0.20元/張', feature: '圖像細節更豐富' },
781
- 'wanx2.0-t2i-turbo': { name: '通義萬相2.0-Turbo', price: '0.04元/張', feature: '質感人像與創意設計' }
782
- };
783
- const selectedModel = model || 'wanx2.1-t2i-turbo';
784
- const modelDesc = modelInfo[selectedModel];
785
- try {
786
- const results = await this.generateImageWithDashScope(prompt, negative_prompt, {
787
- model: selectedModel,
788
- size,
789
- n,
790
- seed,
791
- promptExtend: prompt_extend,
792
- watermark,
793
- outputFormat: output_format,
794
- maxWaitMinutes: max_wait_minutes,
795
- });
796
- let responseText = `通義萬相文生圖像生成完成!\n\n`;
797
- responseText += `🎨 使用模型: ${modelDesc.name} (${modelDesc.feature})\n`;
798
- responseText += `💰 計費: ${modelDesc.price}\n`;
799
- responseText += `✅ 成功生成 ${results.length} 張圖片\n\n`;
800
- results.forEach((result, index) => {
801
- responseText += `圖片 ${index + 1}:\n`;
802
- responseText += `原始提示詞: ${result.orig_prompt}\n`;
803
- if (result.actual_prompt) {
804
- responseText += `優化後提示詞: ${result.actual_prompt}\n`;
805
- }
806
- responseText += `圖片URL: ${result.url}\n\n`;
807
- });
808
- return {
809
- content: [
810
- {
811
- type: "text",
812
- text: responseText,
813
- },
814
- ],
815
- };
816
- }
817
- catch (error) {
818
- throw new McpError(ErrorCode.InternalError, `生成圖片失敗: ${error instanceof Error ? error.message : String(error)}`);
819
- }
198
+ const requestData = {
199
+ model: options?.model || "wanx2.1-i2v-turbo",
200
+ input: {
201
+ img_url: imgUrl,
202
+ prompt,
203
+ template: options?.template,
204
+ },
205
+ parameters: {
206
+ resolution: options?.resolution || "720P",
207
+ duration: options?.duration || 5,
208
+ prompt_extend: options?.promptExtend !== false,
209
+ seed: options?.seed,
210
+ },
211
+ };
212
+ try {
213
+ const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", requestData, {
214
+ headers: {
215
+ "Content-Type": "application/json",
216
+ Authorization: `Bearer ${dashScopeApiKey}`,
217
+ "X-DashScope-Async": "enable",
218
+ },
219
+ });
220
+ return response.data;
820
221
  }
821
- async handleTongyiWanxiangCreateVideoTask(args) {
822
- const { img_url, prompt, model, template, resolution, duration, prompt_extend, seed } = args;
823
- if (!img_url) {
824
- throw new McpError(ErrorCode.InvalidParams, "需要提供 img_url 參數");
825
- }
826
- try {
827
- const response = await this.createVideoGenerationTask(img_url, prompt, {
828
- model,
829
- template,
830
- resolution,
831
- duration,
832
- promptExtend: prompt_extend,
833
- seed,
834
- });
835
- return {
836
- content: [
837
- {
838
- type: "text",
839
- text: `通義萬相圖生視頻任務創建成功!\n\n任務ID: ${response.output.task_id}\n任務狀態: ${response.output.task_status}\n使用模型: ${model || 'wanx2.1-i2v-turbo'}\n首幀圖像: ${img_url}\n請求ID: ${response.request_id}\n\n請使用 tongyi_wanxiang_query_task 工具查詢任務結果。`,
840
- },
841
- ],
842
- };
843
- }
844
- catch (error) {
845
- throw new McpError(ErrorCode.InternalError, `創建視頻生成任務失敗: ${error instanceof Error ? error.message : String(error)}`);
222
+ catch (error) {
223
+ if (axios.isAxiosError(error)) {
224
+ const errorMessage = error.response?.data?.message || error.message;
225
+ throw new Error(`阿里雲 DashScope 視頻生成任務創建失敗: ${errorMessage}`);
846
226
  }
227
+ throw new Error(`創建視頻生成任務時發生未知錯誤: ${error}`);
847
228
  }
848
- async handleTongyiWanxiangGenerateVideo(args) {
849
- const { img_url, prompt, model, template, resolution, duration, prompt_extend, seed, max_wait_minutes } = args;
850
- if (!img_url) {
851
- throw new McpError(ErrorCode.InvalidParams, "需要提供 img_url 參數");
852
- }
853
- // 模型資訊
854
- const modelInfo = {
855
- 'wanx2.1-i2v-turbo': { name: '通義萬相2.1圖生視頻-Turbo', time: '3-5分鐘', feature: '生成速度快' },
856
- 'wanx2.1-i2v-plus': { name: '通義萬相2.1圖生視頻-Plus', time: '7-10分鐘', feature: '視頻品質高' }
857
- };
858
- const selectedModel = model || 'wanx2.1-i2v-turbo';
859
- const modelDesc = modelInfo[selectedModel];
860
- try {
861
- const videoUrl = await this.generateVideoWithDashScope(img_url, prompt, {
862
- model: selectedModel,
863
- template,
864
- resolution,
865
- duration,
866
- promptExtend: prompt_extend,
867
- seed,
868
- maxWaitMinutes: max_wait_minutes,
869
- });
870
- let responseText = `通義萬相圖生視頻生成完成!\n\n`;
871
- responseText += `🎬 使用模型: ${modelDesc.name} (${modelDesc.feature})\n`;
872
- responseText += `⏱️ 預計生成時間: ${modelDesc.time}\n`;
873
- responseText += `🖼️ 首幀圖像: ${img_url}\n`;
874
- responseText += `📐 分辨率: ${resolution || '720P'}\n`;
875
- responseText += `⏰ 視頻時長: ${duration || 5}秒\n`;
876
- if (prompt) {
877
- responseText += `💭 提示詞: ${prompt}\n`;
878
- }
879
- if (template) {
880
- responseText += `✨ 特效模板: ${template}\n`;
881
- }
882
- responseText += `\n✅ 視頻生成成功!\n`;
883
- responseText += `🔗 視頻URL: ${videoUrl}\n`;
884
- responseText += `\n⚠️ 視頻URL有效期24小時,請及時保存`;
885
- return {
886
- content: [
887
- {
888
- type: "text",
889
- text: responseText,
890
- },
891
- ],
892
- };
893
- }
894
- catch (error) {
895
- throw new McpError(ErrorCode.InternalError, `視頻生成失敗: ${error instanceof Error ? error.message : String(error)}`);
896
- }
229
+ }
230
+ async function generateVideoWithDashScope(imgUrl, prompt, options) {
231
+ const createResponse = await createVideoGenerationTask(imgUrl, prompt, options);
232
+ const taskId = createResponse.output.task_id;
233
+ if (!taskId)
234
+ throw new Error("創建視頻生成任務失敗,未獲取到任務ID");
235
+ const maxWaitMinutes = options?.maxWaitMinutes || 15;
236
+ const maxAttempts = maxWaitMinutes * 6;
237
+ let attempts = 0;
238
+ while (attempts < maxAttempts) {
239
+ const queryResponse = await queryDashScopeTask(taskId);
240
+ const status = queryResponse.output.task_status;
241
+ if (status === "SUCCEEDED") {
242
+ const videoUrl = queryResponse.output.video_url;
243
+ if (!videoUrl)
244
+ throw new Error("任務完成但未獲取到視頻URL");
245
+ return videoUrl;
246
+ }
247
+ if (status === "FAILED")
248
+ throw new Error(`視頻生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
249
+ if (status === "CANCELED")
250
+ throw new Error("視頻生成任務已被取消");
251
+ await new Promise((resolve) => setTimeout(resolve, 10000));
252
+ attempts++;
897
253
  }
898
- async handleChatCompletion(args) {
899
- const { message, system_prompt = "You are a helpful assistant.", model = "gpt-4" } = args;
900
- if (!message) {
901
- throw new McpError(ErrorCode.InvalidParams, "需要提供 message 參數");
902
- }
903
- const messages = [
254
+ throw new Error(`視頻生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
255
+ }
256
+ // ===== MCP Server =====
257
+ const TONGYI_MODELS = ["wanx2.1-t2i-turbo", "wanx2.1-t2i-plus", "wanx2.0-t2i-turbo"];
258
+ const LAOZHANG_MODELS = ["sora_image", "gpt-4o-image", "gemini-2.5-flash-image-preview"];
259
+ const ALL_IMAGE_MODELS = [...TONGYI_MODELS, ...LAOZHANG_MODELS];
260
+ const server = new McpServer({ name: "ai-image-chat-mcp-server", version: packageJson.version }, { capabilities: { logging: {} } });
261
+ // ----- generate_image -----
262
+ server.tool("generate_image", "統一圖像生成工具,支援通義萬相和老張API多種模型。預設使用 sora_image。", {
263
+ prompt: z.string().describe("圖像生成的提示詞"),
264
+ model: z.enum(ALL_IMAGE_MODELS).default("sora_image").describe("選擇圖像生成模型(預設: sora_image)"),
265
+ negative_prompt: z.string().default("人物").describe("反向提示詞,僅通義萬相模型支援").optional(),
266
+ size: z.string().default("1024*1024").describe("輸出圖像的分辨率,僅通義萬相模型支援").optional(),
267
+ n: z.number().min(1).max(4).default(1).describe("生成圖片數量(1-4)").optional(),
268
+ seed: z.number().min(0).max(2147483647).describe("隨機數種子,僅通義萬相模型支援").optional(),
269
+ prompt_extend: z.boolean().default(true).describe("是否開啟 prompt 智能改寫,僅通義萬相模型支援").optional(),
270
+ watermark: z.boolean().default(false).describe("是否添加水印標識,僅通義萬相模型支援").optional(),
271
+ output_format: z.enum(["jpg", "jpeg", "png", "webp"]).default("jpg").describe("輸出圖片格式").optional(),
272
+ system_prompt: z.string().describe("系統提示詞,僅老張API模型支援").optional(),
273
+ aspect_ratio: z.string().describe("圖片比例,僅老張API模型支援,格式如:3:2, 16:9, 1:1").optional(),
274
+ max_wait_minutes: z.number().min(1).max(10).default(5).describe("最大等待時間(分鐘,通義萬相預設5分鐘,老張API即時回應)").optional(),
275
+ }, async (args) => {
276
+ const { prompt, model = "sora_image", negative_prompt, size, n = 1, seed, prompt_extend, watermark, output_format = "jpg", system_prompt, aspect_ratio, max_wait_minutes, } = args;
277
+ if (TONGYI_MODELS.includes(model)) {
278
+ return handleTongyiImageGeneration({
279
+ prompt, model, negative_prompt, size, n, seed,
280
+ prompt_extend, watermark, output_format, max_wait_minutes,
281
+ });
282
+ }
283
+ return handleLaozhangImageGeneration({
284
+ prompt, model, system_prompt, output_format, n, aspect_ratio,
285
+ });
286
+ });
287
+ // ----- tongyi_wanxiang_generate_video -----
288
+ server.tool("tongyi_wanxiang_generate_video", "使用通義萬相圖生視頻完整流程(創建任務 + 等待完成 + 返回視頻URL)", {
289
+ img_url: z.string().describe("首幀圖像的 URL(必需,需為公網可訪問地址)"),
290
+ prompt: z.string().describe("文本提示詞,支持中英文,長度不超過800字符").optional(),
291
+ model: z.enum(["wanx2.1-i2v-turbo", "wanx2.1-i2v-plus"]).default("wanx2.1-i2v-turbo").describe("模型名稱 - turbo生成快(3-5分鐘),plus品質高(7-10分鐘)").optional(),
292
+ template: z.enum(["squish", "flying", "carousel"]).describe("視頻特效模板:squish(解壓捏捏)、flying(魔法懸浮)、carousel(時光木馬)").optional(),
293
+ resolution: z.enum(["480P", "720P"]).default("720P").describe("視頻分辨率檔位").optional(),
294
+ duration: z.number().min(3).max(5).default(5).describe("視頻時長(秒)- turbo模型支持3-5秒,plus模型固定5秒").optional(),
295
+ prompt_extend: z.boolean().default(true).describe("是否開啟 prompt 智能改寫").optional(),
296
+ seed: z.number().min(0).max(2147483647).describe("隨機數種子").optional(),
297
+ max_wait_minutes: z.number().min(5).max(30).default(15).describe("最大等待時間(分鐘,預設15分鐘)").optional(),
298
+ }, async (args) => {
299
+ const { img_url, prompt, model = "wanx2.1-i2v-turbo", template, resolution = "720P", duration = 5, prompt_extend, seed, max_wait_minutes = 15, } = args;
300
+ const modelInfo = {
301
+ "wanx2.1-i2v-turbo": { name: "通義萬相2.1圖生視頻-Turbo", time: "3-5分鐘", feature: "生成速度快" },
302
+ "wanx2.1-i2v-plus": { name: "通義萬相2.1圖生視頻-Plus", time: "7-10分鐘", feature: "視頻品質高" },
303
+ };
304
+ const desc = modelInfo[model];
305
+ const videoUrl = await generateVideoWithDashScope(img_url, prompt, {
306
+ model, template, resolution, duration,
307
+ promptExtend: prompt_extend, seed, maxWaitMinutes: max_wait_minutes,
308
+ });
309
+ let text = `通義萬相圖生視頻生成完成!\n\n`;
310
+ text += `🎬 使用模型: ${desc.name} (${desc.feature})\n`;
311
+ text += `⏱️ 預計生成時間: ${desc.time}\n`;
312
+ text += `🖼️ 首幀圖像: ${img_url}\n`;
313
+ text += `📐 分辨率: ${resolution}\n`;
314
+ text += `⏰ 視頻時長: ${duration}秒\n`;
315
+ if (prompt)
316
+ text += `💭 提示詞: ${prompt}\n`;
317
+ if (template)
318
+ text += `✨ 特效模板: ${template}\n`;
319
+ text += `\n✅ 視頻生成成功!\n`;
320
+ text += `🔗 視頻URL: ${videoUrl}\n`;
321
+ text += `\n⚠️ 視頻URL有效期24小時,請及時保存`;
322
+ return { content: [{ type: "text", text }] };
323
+ });
324
+ // ----- chat_completion -----
325
+ server.tool("chat_completion", "使用 AI 進行對話", {
326
+ message: z.string().describe("用戶訊息"),
327
+ system_prompt: z.string().default("You are a helpful assistant.").describe("系統提示詞").optional(),
328
+ model: z.string().default("gpt-4o").describe("使用的模型名稱(預設: gpt-4o)").optional(),
329
+ }, async (args) => {
330
+ const { message, system_prompt = "You are a helpful assistant.", model = "gpt-4o" } = args;
331
+ const response = await axios.post(`${baseUrl}/chat/completions`, {
332
+ model,
333
+ messages: [
904
334
  { role: "system", content: system_prompt },
905
335
  { role: "user", content: message },
906
- ];
907
- const requestData = {
908
- model,
909
- messages,
910
- };
911
- try {
912
- const response = await axios.post(`${this.baseUrl}/chat/completions`, requestData, {
913
- headers: {
914
- "Content-Type": "application/json",
915
- "Authorization": `Bearer ${this.apiKey}`,
916
- },
917
- });
918
- const result = response.data;
919
- const assistantMessage = result.choices[0]?.message?.content || "未收到回覆";
920
- return {
921
- content: [
922
- {
923
- type: "text",
924
- text: `助手回覆:\n\n${assistantMessage}`,
925
- },
926
- ],
927
- };
928
- }
929
- catch (error) {
930
- if (axios.isAxiosError(error)) {
931
- const axiosError = error;
932
- const errorMessage = axiosError.response?.data?.error?.message || axiosError.message;
933
- throw new McpError(ErrorCode.InternalError, `API 請求失敗: ${errorMessage}`);
934
- }
935
- throw new McpError(ErrorCode.InternalError, `未知錯誤: ${error instanceof Error ? error.message : String(error)}`);
936
- }
336
+ ],
337
+ }, {
338
+ headers: {
339
+ "Content-Type": "application/json",
340
+ Authorization: `Bearer ${apiKey}`,
341
+ },
342
+ });
343
+ const assistantMessage = response.data.choices[0]?.message?.content || "未收到回覆";
344
+ return { content: [{ type: "text", text: `助手回覆:\n\n${assistantMessage}` }] };
345
+ });
346
+ // ----- get_usage_guide -----
347
+ server.tool("get_usage_guide", "獲取工具使用指南,包含所有可用模型的詳細說明", {}, async () => {
348
+ return { content: [{ type: "text", text: buildUsageGuide() }] };
349
+ });
350
+ // ===== 處理函式 =====
351
+ async function handleTongyiImageGeneration(args) {
352
+ const modelInfo = {
353
+ "wanx2.1-t2i-turbo": { name: "通義萬相2.1-Turbo", price: "0.14元/張", feature: "生成速度更快" },
354
+ "wanx2.1-t2i-plus": { name: "通義萬相2.1-Plus", price: "0.20元/張", feature: "圖像細節更豐富" },
355
+ "wanx2.0-t2i-turbo": { name: "通義萬相2.0-Turbo", price: "0.04元/張", feature: "質感人像與創意設計" },
356
+ };
357
+ const desc = modelInfo[args.model];
358
+ const results = await generateImageWithDashScope(args.prompt, args.negative_prompt, {
359
+ model: args.model,
360
+ size: args.size,
361
+ n: args.n,
362
+ seed: args.seed,
363
+ promptExtend: args.prompt_extend,
364
+ watermark: args.watermark,
365
+ maxWaitMinutes: args.max_wait_minutes,
366
+ });
367
+ let text = `通義萬相文生圖像生成完成!\n\n`;
368
+ text += `🎨 使用模型: ${desc.name} (${desc.feature})\n`;
369
+ text += `💰 計費: ${desc.price}\n`;
370
+ text += `✅ 成功生成 ${results.length} 張圖片\n\n`;
371
+ for (const [i, result] of results.entries()) {
372
+ text += `圖片 ${i + 1}:\n`;
373
+ text += `原始提示詞: ${result.orig_prompt}\n`;
374
+ if (result.actual_prompt)
375
+ text += `優化後提示詞: ${result.actual_prompt}\n`;
376
+ text += `圖片URL: ${result.url}\n\n`;
937
377
  }
938
- async handleUnifiedImageGeneration(args) {
939
- const { prompt, model = "gemini-2.5-flash-image-preview", negative_prompt, size, n, seed, prompt_extend, watermark, output_format = "jpg", system_prompt, aspect_ratio, max_wait_minutes } = args;
940
- if (!prompt) {
941
- throw new McpError(ErrorCode.InvalidParams, "需要提供 prompt 參數");
942
- }
943
- // 判斷是通義萬相模型還是老張API模型
944
- const tongyiModels = ["wanx2.1-t2i-turbo", "wanx2.1-t2i-plus", "wanx2.0-t2i-turbo"];
945
- const laozhangModels = ["sora_image", "gpt-4o-image", "gemini-2.5-flash-image-preview"];
946
- if (tongyiModels.includes(model)) {
947
- // 使用通義萬相API
948
- return await this.handleTongyiWanxiangGenerateImage({
949
- prompt,
950
- model,
951
- negative_prompt,
952
- size,
953
- n,
954
- seed,
955
- prompt_extend,
956
- watermark,
957
- output_format,
958
- max_wait_minutes
959
- });
960
- }
961
- else if (laozhangModels.includes(model)) {
962
- // 使用老張API
963
- return await this.handleImageGeneration({
964
- prompt,
965
- model,
966
- system_prompt,
967
- output_format,
968
- n,
969
- aspect_ratio
970
- });
378
+ return { content: [{ type: "text", text }] };
379
+ }
380
+ async function handleLaozhangImageGeneration(args) {
381
+ const { prompt, model, system_prompt, output_format = "jpg", n = 1, aspect_ratio } = args;
382
+ const modelInfo = {
383
+ sora_image: { name: "Sora圖像生成", feature: "基於Sora技術的圖像生成,$0.01/次" },
384
+ "gpt-4o-image": { name: "GPT-4o圖像生成", feature: "GPT-4o視覺模型,$0.01/次" },
385
+ "gemini-2.5-flash-image-preview": { name: "Gemini 2.5 Flash圖像生成", feature: "Google Gemini 2.5 Flash預覽版,高速生成,$0.01/次" },
386
+ };
387
+ const desc = modelInfo[model];
388
+ const finalPrompt = aspect_ratio ? `${prompt}【${aspect_ratio}】` : prompt;
389
+ const response = await axios.post(`${baseUrl}/chat/completions`, {
390
+ model,
391
+ n: Math.min(Math.max(n, 1), 4),
392
+ messages: [{ role: "user", content: [{ type: "text", text: finalPrompt }] }],
393
+ }, {
394
+ headers: {
395
+ "Content-Type": "application/json",
396
+ Authorization: `Bearer ${apiKey}`,
397
+ },
398
+ });
399
+ const generatedContent = response.data.choices?.[0]?.message?.content || "未生成內容";
400
+ const imageData = await processImageContent(generatedContent, prompt, output_format);
401
+ let text = `${desc.name}圖像生成完成!\n\n`;
402
+ text += `🎨 使用模型: ${desc.name}\n`;
403
+ text += `💰 費用: ${desc.feature}\n`;
404
+ text += `📝 提示詞: ${finalPrompt}\n`;
405
+ text += `🖼️ 生成數量: ${n}張\n`;
406
+ if (system_prompt)
407
+ text += `🔧 系統提示: ${system_prompt}\n`;
408
+ if (imageData.savedImages.length > 0) {
409
+ text += `\n💾 圖片已保存到 ${formatPathForDisplay(saveDirectory)}:\n`;
410
+ for (const [i, filePath] of imageData.savedImages.entries()) {
411
+ text += `${i + 1}. ${path.basename(filePath)}\n 完整路徑: ${formatPathForDisplay(filePath)}\n`;
971
412
  }
972
- else {
973
- throw new McpError(ErrorCode.InvalidParams, `不支援的模型: ${model}`);
413
+ }
414
+ if (imageData.imageUrls.length > 0) {
415
+ text += `\n🔗 圖片URLs:\n`;
416
+ for (const [i, url] of imageData.imageUrls.entries()) {
417
+ text += `${i + 1}. ${url}\n`;
974
418
  }
975
419
  }
976
- async handleUsageGuide(args) {
977
- const guideText = `
420
+ if (!imageData.hasBase64 && imageData.imageUrls.length === 0) {
421
+ text += `\n📄 API回應內容:\n${generatedContent}`;
422
+ }
423
+ return { content: [{ type: "text", text }] };
424
+ }
425
+ // ===== 使用指南 =====
426
+ function buildUsageGuide() {
427
+ return `
978
428
  # AI Image Chat MCP 工具使用指南
979
429
 
980
430
  **版本**: v${packageJson.version}
981
- **最後更新**: ${new Date().toLocaleDateString('zh-TW')}
431
+ **最後更新**: ${new Date().toLocaleDateString("zh-TW")}
982
432
  **支援模型**: 6 種圖像生成模型 + 2 種視頻生成模型
983
433
 
984
434
  ## 🎨 統一圖像生成工具
@@ -988,50 +438,52 @@ class AIImageChatMCPServer {
988
438
 
989
439
  **可用模型**:
990
440
 
441
+ ### 老張API模型(即時回應,$0.01/次)
442
+
443
+ 1. **sora_image** ⭐ (預設)
444
+ - 提供商: 老張API
445
+ - 特點: 基於Sora技術的圖像生成
446
+ - 生成時間: 即時回應
447
+ - 適合: 快速圖像生成、創意探索、首選模型
448
+
449
+ 2. **gpt-4o-image**
450
+ - 提供商: 老張API
451
+ - 特點: GPT-4o視覺模型
452
+ - 生成時間: 即時回應
453
+ - 適合: 智能圖像理解和生成
454
+
455
+ 3. **gemini-2.5-flash-image-preview**
456
+ - 提供商: 老張API
457
+ - 特點: Google Gemini 2.5 Flash預覽版,高速生成
458
+ - 生成時間: 即時回應
459
+ - 適合: 高速生成、高效批量生成
460
+
991
461
  ### 通義萬相模型(阿里雲DashScope)
992
- 1. **wanx2.1-t2i-turbo**
462
+
463
+ 4. **wanx2.1-t2i-turbo**
993
464
  - 提供商: 阿里雲
994
465
  - 價格: 0.14元/張
995
466
  - 特點: 生成速度更快
996
467
  - 生成時間: 1-3分鐘
997
468
  - 適合: 快速原型和測試
998
469
 
999
- 2. **wanx2.1-t2i-plus**
470
+ 5. **wanx2.1-t2i-plus**
1000
471
  - 提供商: 阿里雲
1001
472
  - 價格: 0.20元/張
1002
473
  - 特點: 圖像細節更豐富
1003
474
  - 生成時間: 1-3分鐘
1004
475
  - 適合: 高品質作品和商業用途
1005
476
 
1006
- 3. **wanx2.0-t2i-turbo**
477
+ 6. **wanx2.0-t2i-turbo**
1007
478
  - 提供商: 阿里雲
1008
479
  - 價格: 0.04元/張
1009
480
  - 特點: 質感人像與創意設計
1010
481
  - 生成時間: 1-3分鐘
1011
482
  - 適合: 成本控制和批量生成
1012
483
 
1013
- ### 老張API模型(即時回應,$0.01/次)
1014
- 4. **gemini-2.5-flash-image-preview** ⭐ (預設)
1015
- - 提供商: 老張API
1016
- - 特點: Google Gemini 2.5 Flash預覽版,高速生成
1017
- - 生成時間: 即時回應
1018
- - 適合: 快速原型設計、高效批量生成、首選模型
1019
-
1020
- 5. **sora_image**
1021
- - 提供商: 老張API
1022
- - 特點: 基於Sora技術的圖像生成
1023
- - 生成時間: 即時回應
1024
- - 適合: 快速圖像生成、創意探索
1025
-
1026
- 6. **gpt-4o-image**
1027
- - 提供商: 老張API
1028
- - 特點: GPT-4o視覺模型
1029
- - 生成時間: 即時回應
1030
- - 適合: 智能圖像理解和生成
1031
-
1032
484
  **主要參數**:
1033
485
  - \`prompt\`: 圖像生成提示詞 (必需)
1034
- - \`model\`: 模型選擇 (可選,預設: "gemini-2.5-flash-image-preview")
486
+ - \`model\`: 模型選擇 (可選,預設: "sora_image")
1035
487
 
1036
488
  **通義萬相模型專用參數**:
1037
489
  - \`negative_prompt\`: 反向提示詞 (可選,預設: "人物")
@@ -1044,6 +496,7 @@ class AIImageChatMCPServer {
1044
496
 
1045
497
  **老張API模型專用參數**:
1046
498
  - \`system_prompt\`: 系統提示詞 (可選)
499
+ - \`aspect_ratio\`: 圖片比例 (可選,格式如 "3:2", "16:9")
1047
500
  - \`output_format\`: 圖片格式 (可選,預設: "jpg")
1048
501
 
1049
502
  ---
@@ -1054,39 +507,10 @@ class AIImageChatMCPServer {
1054
507
  **功能**: 使用通義萬相圖生視頻完整流程(創建任務 + 等待完成 + 返回視頻URL)
1055
508
 
1056
509
  **可用模型**:
1057
- 1. **wanx2.1-i2v-turbo** (預設)
1058
- - 生成時間: 3-5分鐘
1059
- - 特點: 生成速度快
1060
- - 支援時長: 3-5秒
1061
- - 支援分辨率: 480P, 720P
510
+ 1. **wanx2.1-i2v-turbo** (預設) - 生成快(3-5分鐘),支援3-5秒
511
+ 2. **wanx2.1-i2v-plus** - 品質高(7-10分鐘),固定5
1062
512
 
1063
- 2. **wanx2.1-i2v-plus**
1064
- - 生成時間: 7-10分鐘
1065
- - 特點: 視頻品質高
1066
- - 支援時長: 固定5秒
1067
- - 支援分辨率: 僅720P
1068
-
1069
- **主要參數**:
1070
- - \`img_url\`: 首幀圖像URL (必需,需為公網可訪問地址)
1071
- - \`prompt\`: 文本提示詞 (可選,長度不超過800字符)
1072
- - \`model\`: 模型選擇 (可選)
1073
- - \`template\`: 視頻特效模板 (可選)
1074
- - \`resolution\`: 分辨率檔位 (可選,預設: "720P")
1075
- - \`duration\`: 視頻時長 (可選,預設: 5秒)
1076
- - \`prompt_extend\`: 智能改寫 (可選,預設: true)
1077
- - \`seed\`: 隨機種子 (可選)
1078
- - \`max_wait_minutes\`: 最大等待時間 (可選,預設: 15分鐘)
1079
-
1080
- **視頻特效模板**:
1081
- - \`squish\`: 解壓捏捏效果
1082
- - \`flying\`: 魔法懸浮效果
1083
- - \`carousel\`: 時光木馬效果
1084
-
1085
- **圖像要求**:
1086
- - 格式: JPEG、JPG、PNG、BMP、WEBP
1087
- - 尺寸: 360-2000像素(寬度和高度)
1088
- - 檔案大小: 不超過10MB
1089
- - URL: 必須為公網可訪問地址
513
+ **視頻特效模板**: squish(解壓捏捏)、flying(魔法懸浮)、carousel(時光木馬)
1090
514
 
1091
515
  ---
1092
516
 
@@ -1094,276 +518,107 @@ class AIImageChatMCPServer {
1094
518
 
1095
519
  ### chat_completion
1096
520
  **功能**: 使用 AI 進行對話
1097
-
1098
- **主要參數**:
1099
521
  - \`message\`: 用戶訊息 (必需)
1100
522
  - \`system_prompt\`: 系統提示詞 (可選)
1101
- - \`model\`: 模型名稱 (可選,預設: "gpt-4")
523
+ - \`model\`: 模型名稱 (可選,預設: "gpt-4o")
1102
524
 
1103
525
  ---
1104
526
 
1105
527
  ## 📋 使用範例
1106
528
 
1107
- ### 📋 圖像生成模型詳細調用範例
1108
-
1109
- #### 1. gemini-2.5-flash-image-preview (預設) - 高速生成
1110
- **特點**: Gemini 2.5 Flash預覽版 | **價格**: $0.01/次 | **時間**: 即時回應
1111
- \`\`\`json
1112
- {
1113
- "name": "generate_image",
1114
- "arguments": {
1115
- "prompt": "一隻可愛的橘貓在陽光下睡覺",
1116
- "model": "gemini-2.5-flash-image-preview",
1117
- "aspect_ratio": "16:9",
1118
- "n": 2
1119
- }
1120
- }
1121
- \`\`\`
1122
-
1123
- #### 2. wanx2.1-t2i-turbo - 速度優先
1124
- **特點**: 生成速度快 | **價格**: 0.14元/張 | **時間**: 1-3分鐘
1125
- \`\`\`json
1126
- {
1127
- "name": "generate_image",
1128
- "arguments": {
1129
- "prompt": "一隻可愛的橘貓在陽光下睡覺",
1130
- "model": "wanx2.1-t2i-turbo",
1131
- "size": "1024*1024",
1132
- "n": 1,
1133
- "negative_prompt": "人物,文字,低質量",
1134
- "prompt_extend": true,
1135
- "watermark": false,
1136
- "max_wait_minutes": 5
1137
- }
1138
- }
1139
- \`\`\`
1140
-
1141
- #### 2. wanx2.1-t2i-turbo - 速度優先
1142
- **特點**: 生成速度快 | **價格**: 0.14元/張 | **時間**: 1-3分鐘
1143
- \`\`\`json
1144
- {
1145
- "name": "generate_image",
1146
- "arguments": {
1147
- "prompt": "一隻可愛的橘貓在陽光下睡覺",
1148
- "model": "wanx2.1-t2i-turbo",
1149
- "size": "1024*1024",
1150
- "n": 1,
1151
- "negative_prompt": "人物,文字,低質量",
1152
- "prompt_extend": true,
1153
- "watermark": false,
1154
- "max_wait_minutes": 5
1155
- }
1156
- }
1157
- \`\`\`
1158
-
1159
- #### 3. wanx2.1-t2i-plus - 品質優先
1160
- **特點**: 圖像細節豐富 | **價格**: 0.20元/張 | **時間**: 1-3分鐘
1161
- \`\`\`json
1162
- {
1163
- "name": "generate_image",
1164
- "arguments": {
1165
- "prompt": "精美的日式庭院,櫻花飄落,寧靜祥和",
1166
- "model": "wanx2.1-t2i-plus",
1167
- "size": "1024*1024",
1168
- "n": 2,
1169
- "negative_prompt": "人物,文字,模糊",
1170
- "seed": 12345,
1171
- "prompt_extend": true,
1172
- "output_format": "jpg"
1173
- }
1174
- }
1175
- \`\`\`
1176
-
1177
- #### 4. wanx2.0-t2i-turbo - 性價比首選
1178
- **特點**: 質感人像與創意設計 | **價格**: 0.04元/張 | **時間**: 1-3分鐘
1179
- \`\`\`json
1180
- {
1181
- "name": "generate_image",
1182
- "arguments": {
1183
- "prompt": "時尚的年輕女性肖像,現代都市背景",
1184
- "model": "wanx2.0-t2i-turbo",
1185
- "size": "1024*1024",
1186
- "n": 4,
1187
- "negative_prompt": "醜陋,變形,低分辨率",
1188
- "max_wait_minutes": 3
1189
- }
1190
- }
1191
- \`\`\`
1192
-
1193
- #### 5. sora_image - 即時生成
1194
- **特點**: 基於Sora技術 | **價格**: $0.01/次 | **時間**: 即時回應
529
+ #### 1. sora_image (預設) - 即時生成
1195
530
  \`\`\`json
1196
531
  {
1197
532
  "name": "generate_image",
1198
533
  "arguments": {
1199
534
  "prompt": "夢幻般的未來城市,科技感十足,霓虹燈光",
1200
- "model": "sora_image",
1201
- "n": 4,
1202
535
  "aspect_ratio": "16:9"
1203
536
  }
1204
537
  }
1205
538
  \`\`\`
1206
539
 
1207
- #### 6. gpt-4o-image - 智能生成
1208
- **特點**: GPT-4o視覺模型 | **價格**: $0.01/次 | **時間**: 即時回應
540
+ #### 2. gpt-4o-image - 智能生成
1209
541
  \`\`\`json
1210
542
  {
1211
543
  "name": "generate_image",
1212
544
  "arguments": {
1213
545
  "prompt": "可愛的卡通動物們在森林裡開派對",
1214
546
  "model": "gpt-4o-image",
1215
- "n": 2,
1216
547
  "aspect_ratio": "3:2",
1217
548
  "system_prompt": "Create a colorful, family-friendly cartoon style image"
1218
549
  }
1219
550
  }
1220
551
  \`\`\`
1221
552
 
1222
- **特點**: Gemini 2.5 Flash預覽版 | **價格**: $0.01/次 | **時間**: 即時回應
553
+ #### 3. gemini-2.5-flash-image-preview - 高速生成
1223
554
  \`\`\`json
1224
555
  {
1225
556
  "name": "generate_image",
1226
557
  "arguments": {
1227
558
  "prompt": "賽博朋克風格的東京街道,雨夜霓虹",
1228
559
  "model": "gemini-2.5-flash-image-preview",
1229
- "n": 3,
1230
560
  "aspect_ratio": "16:9"
1231
561
  }
1232
562
  }
1233
563
  \`\`\`
1234
564
 
1235
- #### 6. 老張API比例控制範例
1236
- **展示不同比例效果**
565
+ #### 4. wanx2.1-t2i-plus - 品質優先
1237
566
  \`\`\`json
1238
567
  {
1239
568
  "name": "generate_image",
1240
569
  "arguments": {
1241
- "prompt": "一隻貓咪坐在窗台上看風景",
1242
- "model": "sora_image",
1243
- "aspect_ratio": "5:3",
1244
- "n": 1
570
+ "prompt": "精美的日式庭院,櫻花飄落,寧靜祥和",
571
+ "model": "wanx2.1-t2i-plus",
572
+ "size": "1024*1024",
573
+ "n": 2,
574
+ "negative_prompt": "人物,文字,模糊"
1245
575
  }
1246
576
  }
1247
577
  \`\`\`
1248
578
 
1249
- ### 📋 詳細參數說明
1250
-
1251
- #### 🔧 通用參數 (所有模型)
1252
- - \`**prompt**\` (必需): 圖像生成的描述詞
1253
- - \`**model**\` (可選): 選擇生成模型,預設 "gemini-2.5-flash-image-preview"
1254
- - \`**output_format**\` (可選): 圖片格式 ["jpg", "jpeg", "png", "webp"],預設 "jpg"
1255
-
1256
- #### ⚙️ 通義萬相專用參數 (wanx2.x 模型)
1257
- - \`**negative_prompt**\` (可選): 反向提示詞,描述不希望出現的內容,預設 "人物"
1258
- - \`**size**\` (可選): 圖像尺寸,預設 "1024*1024"
1259
- - 支援: "512*512", "768*768", "1024*1024", "1280*720", "720*1280"
1260
- - \`**n**\` (可選): 生成圖片數量,範圍 1-4,預設 1
1261
- - \`**seed**\` (可選): 隨機種子 (0-2147483647),用於重現相同結果
1262
- - \`**prompt_extend**\` (可選): 智能改寫提示詞,預設 true
1263
- - \`**watermark**\` (可選): 添加水印標識,預設 false
1264
- - \`**max_wait_minutes**\` (可選): 最大等待時間 (1-10分鐘),預設 5分鐘
1265
-
1266
- #### 🤖 老張API專用參數 (sora_image, gpt-4o-image, gemini-2.5-flash-image-preview)
1267
- - \`**system_prompt**\` (可選): 系統提示詞,用於指導AI生成風格
1268
- - \`**aspect_ratio**\` (可選): 圖片比例,格式如 "3:2", "16:9", "1:1" (會添加到提示詞末尾)
1269
- - \`**n**\` (可選): 生成圖片數量,範圍 1-4,預設 1 (注意:老張API按次計費,每次$0.01)
1270
- - \`**max_wait_minutes**\` (可選): 最大等待時間,預設 5分鐘 (通常即時回應)
1271
-
1272
- ### 💰 模型價格對比表
1273
-
1274
- | 模型 | 提供商 | 價格 | 生成時間 | 適用場景 |
1275
- |------|---------|------|----------|----------|
1276
- | **wanx2.0-t2i-turbo** | 阿里雲 | **0.04元/張** | 1-3分鐘 | 💰 批量生成、成本控制 |
1277
- | **wanx2.1-t2i-turbo** | 阿里雲 | 0.14元/張 | 1-3分鐘 | ⚡ 快速原型、測試 |
1278
- | **wanx2.1-t2i-plus** | 阿里雲 | 0.20元/張 | 1-3分鐘 | 🎨 高品質、商業用途 |
1279
- | **sora_image** | 老張API | **$0.01/次** | 即時 | 🚀 即時生成、創意探索 |
1280
- | **gpt-4o-image** | 老張API | **$0.01/次** | 即時 | 🤖 智能理解、複雜場景 |
1281
- | **gemini-2.5-flash-image-preview** | 老張API | **$0.01/次** | 即時 | ⚡ 高速生成、高效處理 |
1282
-
1283
- ### 💡 老張API使用說明
1284
- - **計費方式**: 按次計費,每次調用 $0.01,不論生成幾張圖片
1285
- - **比例控制**: 在提示詞末尾自動添加【比例】格式,如【3:2】
1286
- - **重試機制**: 如果 sora_image 失敗,建議切換到 gpt-4o-image 或 gemini-2.5-flash-image-preview
1287
- - **數量控制**: 使用 n 參數控制生成數量 (1-4張)
1288
- - **格式特點**: 基於 ChatGPT PLUS 用戶的生圖請求模擬
1289
-
1290
- ### 🎯 模型選擇建議
1291
-
1292
- **追求速度**: \`gemini-2.5-flash-image-preview\`, \`sora_image\`, \`gpt-4o-image\` (即時回應)
1293
- **注重成本**: \`wanx2.0-t2i-turbo\` (最便宜,0.04元/張)
1294
- **平衡選擇**: \`wanx2.1-t2i-turbo\` (速度與品質平衡)
1295
- **追求品質**: \`wanx2.1-t2i-plus\` (最高品質)
1296
- **創意實驗**: \`sora_image\` (Sora技術,獨特風格)
1297
-
1298
- ### 視頻生成範例
579
+ #### 5. 視頻生成範例
1299
580
  \`\`\`json
1300
581
  {
1301
582
  "name": "tongyi_wanxiang_generate_video",
1302
583
  "arguments": {
1303
584
  "img_url": "https://example.com/cat.jpg",
1304
585
  "prompt": "小貓在草地上慢慢伸懶腰",
1305
- "model": "wanx2.1-i2v-turbo",
1306
586
  "duration": 4,
1307
587
  "resolution": "720P"
1308
588
  }
1309
589
  }
1310
590
  \`\`\`
1311
591
 
1312
- ### 特效視頻範例
1313
- \`\`\`json
1314
- {
1315
- "name": "tongyi_wanxiang_generate_video",
1316
- "arguments": {
1317
- "img_url": "https://example.com/object.jpg",
1318
- "template": "flying",
1319
- "resolution": "720P"
1320
- }
1321
- }
1322
- \`\`\`
1323
-
1324
592
  ---
1325
593
 
1326
- ## ⚠️ 注意事項
594
+ ## 💰 模型價格對比表
1327
595
 
1328
- 1. **圖像生成**: 通常需要1-3分鐘完成
1329
- 2. **視頻生成**: turbo模型3-5分鐘,plus模型7-10分鐘
1330
- 3. **URL有效期**: 生成的圖片和視頻URL有效期24小時
1331
- 4. **等待機制**: 工具會自動輪詢任務狀態直到完成
1332
- 5. **錯誤處理**: 任務失敗時會提供詳細錯誤信息
1333
- 6. **環境變數**: 需要設置 ALI_API_KEY DASHSCOPE_API_KEY
596
+ | 模型 | 提供商 | 價格 | 生成時間 | 適用場景 |
597
+ |------|---------|------|----------|----------|
598
+ | **sora_image** ⭐ | 老張API | **$0.01/次** | 即時 | 🚀 首選,即時生成 |
599
+ | **gpt-4o-image** | 老張API | **$0.01/次** | 即時 | 🤖 智能理解、複雜場景 |
600
+ | **gemini-2.5-flash-image-preview** | 老張API | **$0.01/次** | 即時 | ⚡ 高速生成 |
601
+ | **wanx2.0-t2i-turbo** | 阿里雲 | **0.04元/張** | 1-3分鐘 | 💰 批量生成、成本控制 |
602
+ | **wanx2.1-t2i-turbo** | 阿里雲 | 0.14元/張 | 1-3分鐘 | ⚡ 快速原型、測試 |
603
+ | **wanx2.1-t2i-plus** | 阿里雲 | 0.20元/張 | 1-3分鐘 | 🎨 高品質、商業用途 |
1334
604
 
1335
605
  ---
1336
606
 
1337
607
  ## 🔧 環境配置
1338
608
 
1339
- 確保設置以下環境變數:
1340
- - \`ALI_API_KEY\` 或 \`DASHSCOPE_API_KEY\`: 阿里雲DashScope API密鑰(通義萬相模型需要)
1341
- - \`AI_API_KEY\`: 老張API密鑰(老張API模型和chat_completion需要)
609
+ - \`AI_API_KEY\`: 老張API密鑰(必需)
1342
610
  - \`AI_API_BASE_URL\`: 老張API基礎URL(可選,預設: https://api.laozhang.ai/v1)
1343
-
1344
- **API密鑰配置說明**:
1345
- - 使用通義萬相模型時,需要配置阿里雲API密鑰
1346
- - 使用老張API模型時,需要配置老張API密鑰
1347
- - 如果同時使用兩種模型,需要配置兩個API密鑰
1348
- `;
1349
- return {
1350
- content: [
1351
- {
1352
- type: "text",
1353
- text: guideText.trim(),
1354
- },
1355
- ],
1356
- };
1357
- }
1358
- async run() {
1359
- const transport = new StdioServerTransport();
1360
- await this.server.connect(transport);
1361
- console.error(`AI Image Chat MCP 服務器已啟動 - v${packageJson.version}`);
1362
- }
611
+ - \`ALI_API_KEY\` 或 \`DASHSCOPE_API_KEY\`: 阿里雲DashScope API密鑰(使用通義萬相時需要)
612
+ - \`AI_IMAGE_SAVE_PATH\`: 圖片保存路徑(可選)
613
+ `.trim();
614
+ }
615
+ // ===== 啟動 =====
616
+ async function main() {
617
+ const transport = new StdioServerTransport();
618
+ await server.connect(transport);
619
+ console.error(`AI Image Chat MCP 服務器已啟動 - v${packageJson.version}`);
1363
620
  }
1364
- // 啟動服務器
1365
- const server = new AIImageChatMCPServer();
1366
- server.run().catch((error) => {
621
+ main().catch((error) => {
1367
622
  console.error("服務器啟動失敗:", error);
1368
623
  process.exit(1);
1369
624
  });