@infimind/video-mcp-cli 1.0.12 → 1.0.13
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/README.md +40 -28
- package/dist/auth/credentialWriter.js +2 -1
- package/dist/auth/exchange.js +8 -2
- package/dist/auth/installationId.js +1 -1
- package/dist/auth/login.js +2 -2
- package/dist/auth/loopback.js +6 -4
- package/dist/auth/profile.js +1 -1
- package/dist/auth/status.js +5 -1
- package/dist/creativeTools.js +413 -79
- package/dist/index.js +3 -3
- package/dist/server.js +619 -170
- package/dist/videoRules.js +81 -17
- package/package.json +3 -3
package/dist/server.js
CHANGED
|
@@ -8,8 +8,17 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { readFileSync, existsSync, statSync } from "fs";
|
|
10
10
|
import { extname, basename } from "path";
|
|
11
|
-
import { MCP_SERVER_VERSION, getMcpDefaultTaskMode, getMcpEngineName, getMcpVideoStatusHint, isMcpRoute, isValidMcpTaskMode,
|
|
11
|
+
import { MCP_PUBLIC_MODELS, MCP_SERVER_VERSION, getMcpDefaultTaskMode, getMcpEngineName, getMcpVideoStatusHint, isMcpRoute, isRetiredSaModel, isRetiredSaRoute, isValidMcpTaskMode, resolveMcpEngine, isMcpModel, } from "./videoRules.js";
|
|
12
12
|
import { MCP_CREATIVE_TOOLS } from "./creativeTools.js";
|
|
13
|
+
const WAN3_OUTPUT_SECONDS = [5, 10, 15, 20, 25, 30];
|
|
14
|
+
const WAN3_OUTPUT_SECONDS_LABEL = WAN3_OUTPUT_SECONDS.join("、");
|
|
15
|
+
function isWan3OutputSeconds(value, mode) {
|
|
16
|
+
if (mode === "edit")
|
|
17
|
+
return value === -1;
|
|
18
|
+
return (typeof value === "number" &&
|
|
19
|
+
Number.isInteger(value) &&
|
|
20
|
+
WAN3_OUTPUT_SECONDS.includes(value));
|
|
21
|
+
}
|
|
13
22
|
class McpToolError extends Error {
|
|
14
23
|
code;
|
|
15
24
|
route;
|
|
@@ -64,7 +73,7 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
64
73
|
{
|
|
65
74
|
name: "create_video_task",
|
|
66
75
|
title: "生成 AI 视频",
|
|
67
|
-
description: "根据提示词和可选的图片、视频或音频素材创建 AI 视频生成任务,可选择
|
|
76
|
+
description: "根据提示词和可选的图片、视频或音频素材创建 AI 视频生成任务,可选择 WA、SD、SD25、HA 或 H3 专业版;素材可使用本地文件路径或公网 URL。" +
|
|
68
77
|
"本工具返回 taskId,不直接返回最终视频;创建后请调用 get_video_status 并原样传入 taskId,轮询生成进度和结果。",
|
|
69
78
|
inputSchema: {
|
|
70
79
|
type: "object",
|
|
@@ -79,7 +88,7 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
79
88
|
sourceImage: {
|
|
80
89
|
type: "string",
|
|
81
90
|
title: "单张参考图片",
|
|
82
|
-
description: "可选。本地图片路径或公网 URL;
|
|
91
|
+
description: "可选。本地图片路径或公网 URL;HA 专业版标准模式单图生成时使用。",
|
|
83
92
|
},
|
|
84
93
|
sourceImages: {
|
|
85
94
|
type: "array",
|
|
@@ -135,19 +144,45 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
135
144
|
type: "string",
|
|
136
145
|
title: "输出尺寸",
|
|
137
146
|
description: "可选。视频的输出尺寸,默认 1280x720;可选择横屏、竖屏、正方形、4:3 或 3:4 尺寸。",
|
|
138
|
-
enum: [
|
|
147
|
+
enum: [
|
|
148
|
+
"1280x720",
|
|
149
|
+
"720x1280",
|
|
150
|
+
"1024x1024",
|
|
151
|
+
"1024x768",
|
|
152
|
+
"768x1024",
|
|
153
|
+
],
|
|
139
154
|
},
|
|
140
155
|
seconds: {
|
|
141
156
|
type: "number",
|
|
142
157
|
title: "视频时长",
|
|
143
|
-
description: "
|
|
144
|
-
|
|
158
|
+
description: "可选。WA 普通模式仅支持 5、10、15、20、25、30 秒,默认 5 秒;WA edit 视频编辑仅支持 -1(智能时长),可省略;其他模型按各自规则。",
|
|
159
|
+
// JSON Schema 无法在此共享字段上可靠表达按 model 分支的 enum。
|
|
160
|
+
// 精确离散时长规则由 validateCreateVideoArgs 按模型统一校验。
|
|
161
|
+
minimum: -1,
|
|
162
|
+
maximum: 30,
|
|
163
|
+
multipleOf: 1,
|
|
145
164
|
},
|
|
146
165
|
language: {
|
|
147
166
|
type: "string",
|
|
148
167
|
title: "提示词优化语言",
|
|
149
168
|
description: "可选。用于理解和优化视频提示词的语言区域代码,默认 zh-CN。",
|
|
150
|
-
enum: [
|
|
169
|
+
enum: [
|
|
170
|
+
"zh-CN",
|
|
171
|
+
"en-US",
|
|
172
|
+
"ja-JP",
|
|
173
|
+
"ko-KR",
|
|
174
|
+
"es-ES",
|
|
175
|
+
"fr-FR",
|
|
176
|
+
"th-TH",
|
|
177
|
+
"id-ID",
|
|
178
|
+
"de-DE",
|
|
179
|
+
"pt-PT",
|
|
180
|
+
"bn-BD",
|
|
181
|
+
"ru-RU",
|
|
182
|
+
"ar-SA",
|
|
183
|
+
"zh-HK",
|
|
184
|
+
"hi-IN",
|
|
185
|
+
],
|
|
151
186
|
},
|
|
152
187
|
count: {
|
|
153
188
|
type: "number",
|
|
@@ -159,14 +194,15 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
159
194
|
model: {
|
|
160
195
|
type: "string",
|
|
161
196
|
title: "生成模型",
|
|
162
|
-
description: "可选。
|
|
163
|
-
enum: [
|
|
197
|
+
description: "可选。WA=极睿 WA 专业版(默认,非 VIP 可用),SD=极睿 SD 专业版,SD25=极睿 SD2.5 专业版,HA=极睿 HA 专业版,H3=极睿 H3 专业版;除 WA 外仅限 VIP 用户使用。",
|
|
198
|
+
enum: [...MCP_PUBLIC_MODELS],
|
|
199
|
+
default: "WA",
|
|
164
200
|
},
|
|
165
201
|
route: {
|
|
166
202
|
type: "string",
|
|
167
203
|
title: "兼容模型参数",
|
|
168
|
-
description: "已废弃的兼容参数,仅供旧客户端使用;新调用请使用 model
|
|
169
|
-
enum: ["
|
|
204
|
+
description: "已废弃的兼容参数,仅供旧客户端使用;新调用请使用 model。",
|
|
205
|
+
enum: ["S5", "S6", "S7", "S8", "S9"],
|
|
170
206
|
deprecated: true,
|
|
171
207
|
},
|
|
172
208
|
taskMode: {
|
|
@@ -178,24 +214,30 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
178
214
|
resolution: {
|
|
179
215
|
type: "string",
|
|
180
216
|
title: "输出清晰度",
|
|
181
|
-
description: "可选。SD
|
|
182
|
-
enum: ["720p", "1080p"],
|
|
217
|
+
description: "可选。SD/HA 支持 720p 或 1080p;SD2.5 与 WA 支持 480p、720p 或 1080p(WA 480p 仅管理员);默认 720p。",
|
|
218
|
+
enum: ["480p", "720p", "1080p"],
|
|
219
|
+
},
|
|
220
|
+
outputFormat: {
|
|
221
|
+
type: "string",
|
|
222
|
+
title: "输出格式",
|
|
223
|
+
description: "可选。默认 mp4;mov 仅适用于 SD2.5 专业版。",
|
|
224
|
+
enum: ["mp4", "mov"],
|
|
183
225
|
},
|
|
184
226
|
ratio: {
|
|
185
227
|
type: "string",
|
|
186
228
|
title: "画面比例",
|
|
187
|
-
description: "可选。
|
|
188
|
-
enum: ["16:9", "9:16", "1:1", "4:3", "3:4"],
|
|
229
|
+
description: "可选。WA 支持 adaptive、16:9、4:3、1:1、3:4、9:16,默认 adaptive。",
|
|
230
|
+
enum: ["adaptive", "16:9", "9:16", "1:1", "4:3", "3:4"],
|
|
189
231
|
},
|
|
190
232
|
generateAudio: {
|
|
191
233
|
type: "boolean",
|
|
192
234
|
title: "生成音频",
|
|
193
|
-
description: "可选。SD / SD2.5 专业版是否同时生成音频,默认 true。",
|
|
235
|
+
description: "可选。SD / SD2.5 / WA 专业版是否同时生成音频,默认 true。",
|
|
194
236
|
},
|
|
195
237
|
webSearchEnabled: {
|
|
196
238
|
type: "boolean",
|
|
197
239
|
title: "启用内容增强",
|
|
198
|
-
description: "可选。SD
|
|
240
|
+
description: "可选。SD / SD2.5 专业版是否启用联网内容增强,默认 false。",
|
|
199
241
|
},
|
|
200
242
|
dryRun: {
|
|
201
243
|
type: "boolean",
|
|
@@ -211,27 +253,107 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
211
253
|
title: "AI 视频任务创建结果",
|
|
212
254
|
description: "返回任务标识、所用配置、积分信息和后续查询提示;预检或失败时部分字段可能不返回。",
|
|
213
255
|
properties: {
|
|
214
|
-
success: {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
256
|
+
success: {
|
|
257
|
+
type: "boolean",
|
|
258
|
+
title: "是否成功",
|
|
259
|
+
description: "表示任务创建或预检请求是否成功。",
|
|
260
|
+
},
|
|
261
|
+
taskId: {
|
|
262
|
+
type: "number",
|
|
263
|
+
title: "任务 ID",
|
|
264
|
+
description: "创建成功后返回的视频任务 ID;dryRun 或失败时可能不返回,请用于调用 get_video_status。",
|
|
265
|
+
},
|
|
266
|
+
model: {
|
|
267
|
+
type: "string",
|
|
268
|
+
title: "生成模型",
|
|
269
|
+
description: "实际使用的模型短码:WA、SD、SD25、HA 或 H3。",
|
|
270
|
+
},
|
|
271
|
+
route: {
|
|
272
|
+
type: "string",
|
|
273
|
+
title: "兼容模型标识",
|
|
274
|
+
description: "为旧客户端返回的兼容模型标识。",
|
|
275
|
+
},
|
|
276
|
+
engineName: {
|
|
277
|
+
type: "string",
|
|
278
|
+
title: "模型名称",
|
|
279
|
+
description: "实际使用的极睿专业版模型名称。",
|
|
280
|
+
},
|
|
281
|
+
usedRouteAlias: {
|
|
282
|
+
type: "boolean",
|
|
283
|
+
title: "是否使用兼容参数",
|
|
284
|
+
description: "表示本次调用是否通过已废弃的 route 参数选择模型。",
|
|
285
|
+
},
|
|
286
|
+
taskMode: {
|
|
287
|
+
type: "string",
|
|
288
|
+
title: "生成模式",
|
|
289
|
+
description: "本次任务实际使用的生成模式。",
|
|
290
|
+
},
|
|
291
|
+
videoCount: {
|
|
292
|
+
type: "number",
|
|
293
|
+
title: "视频数量",
|
|
294
|
+
description: "本次任务计划生成的视频数量。",
|
|
295
|
+
},
|
|
296
|
+
dryRun: {
|
|
297
|
+
type: "boolean",
|
|
298
|
+
title: "是否仅预检",
|
|
299
|
+
description: "表示本次请求是否只进行了预检而未创建任务。",
|
|
300
|
+
},
|
|
301
|
+
creditEstimateKind: {
|
|
302
|
+
type: "string",
|
|
303
|
+
title: "积分估算类型",
|
|
304
|
+
enum: ["reservation"],
|
|
305
|
+
description: "WA 视频编辑返回 reservation,表示积分预留上限。",
|
|
306
|
+
},
|
|
307
|
+
reservationCeilingCredits: {
|
|
308
|
+
type: "number",
|
|
309
|
+
title: "积分预留上限",
|
|
310
|
+
description: "积分预留上限;完成后按可信输入和实际输出时长结算。",
|
|
311
|
+
},
|
|
312
|
+
settlementDescription: {
|
|
313
|
+
type: "string",
|
|
314
|
+
title: "结算说明",
|
|
315
|
+
description: "实际积分结算说明。",
|
|
316
|
+
},
|
|
317
|
+
estimatedCredits: {
|
|
318
|
+
type: "number",
|
|
319
|
+
title: "预估积分",
|
|
320
|
+
description: "完成本次任务预计需要的积分;无法估算时可能不返回。",
|
|
321
|
+
},
|
|
322
|
+
estimateSource: {
|
|
323
|
+
type: "string",
|
|
324
|
+
title: "估算依据",
|
|
325
|
+
description: "预估积分所依据的素材检测结果或时长提示。",
|
|
326
|
+
},
|
|
327
|
+
remainingCredits: {
|
|
328
|
+
type: "number",
|
|
329
|
+
title: "剩余积分",
|
|
330
|
+
description: "请求处理后的账户剩余积分。",
|
|
331
|
+
},
|
|
332
|
+
statusHint: {
|
|
333
|
+
type: "string",
|
|
334
|
+
title: "后续操作提示",
|
|
335
|
+
description: "提示是否应使用 taskId 调用 get_video_status 查询任务进度。",
|
|
336
|
+
},
|
|
227
337
|
validationWarnings: {
|
|
228
338
|
type: "array",
|
|
229
339
|
title: "预检警告",
|
|
230
340
|
description: "参数或素材预检产生的非阻断性提示列表。",
|
|
231
|
-
items: {
|
|
341
|
+
items: {
|
|
342
|
+
type: "string",
|
|
343
|
+
title: "预检警告项",
|
|
344
|
+
description: "一条需要关注但不阻断请求的预检提示。",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
code: {
|
|
348
|
+
type: "string",
|
|
349
|
+
title: "错误代码",
|
|
350
|
+
description: "请求失败时返回的机器可读错误代码,成功时可能不返回。",
|
|
351
|
+
},
|
|
352
|
+
message: {
|
|
353
|
+
type: "string",
|
|
354
|
+
title: "结果消息",
|
|
355
|
+
description: "请求结果或失败原因的可读说明。",
|
|
232
356
|
},
|
|
233
|
-
code: { type: "string", title: "错误代码", description: "请求失败时返回的机器可读错误代码,成功时可能不返回。" },
|
|
234
|
-
message: { type: "string", title: "结果消息", description: "请求结果或失败原因的可读说明。" },
|
|
235
357
|
},
|
|
236
358
|
required: ["success"],
|
|
237
359
|
},
|
|
@@ -258,26 +380,106 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
258
380
|
title: "AI 视频生成状态与结果",
|
|
259
381
|
description: "返回任务进度、生成结果和错误信息;任务未完成时结果链接和完成时间可能为空。",
|
|
260
382
|
properties: {
|
|
261
|
-
success: {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
383
|
+
success: {
|
|
384
|
+
type: "boolean",
|
|
385
|
+
title: "是否成功",
|
|
386
|
+
description: "表示查询请求是否成功。",
|
|
387
|
+
},
|
|
388
|
+
taskId: {
|
|
389
|
+
type: "number",
|
|
390
|
+
title: "任务 ID",
|
|
391
|
+
description: "当前查询的视频任务 ID。",
|
|
392
|
+
},
|
|
393
|
+
status: {
|
|
394
|
+
type: "string",
|
|
395
|
+
title: "任务状态",
|
|
396
|
+
description: "视频任务当前的处理状态;未完成时应根据 statusHint 稍后重试。",
|
|
397
|
+
},
|
|
398
|
+
progress: {
|
|
399
|
+
type: "number",
|
|
400
|
+
title: "总体进度",
|
|
401
|
+
description: "任务总体处理进度,通常为 0–100 的数值。",
|
|
402
|
+
},
|
|
403
|
+
totalVideos: {
|
|
404
|
+
type: "number",
|
|
405
|
+
title: "视频总数",
|
|
406
|
+
description: "任务计划生成的视频总数。",
|
|
407
|
+
},
|
|
408
|
+
completedVideos: {
|
|
409
|
+
type: "number",
|
|
410
|
+
title: "已完成数量",
|
|
411
|
+
description: "当前已经完成生成的视频数量。",
|
|
412
|
+
},
|
|
413
|
+
estimatedCredits: {
|
|
414
|
+
type: "number",
|
|
415
|
+
title: "预估积分",
|
|
416
|
+
description: "任务预计消耗的积分;无法估算时可能不返回。",
|
|
417
|
+
},
|
|
418
|
+
taskMode: {
|
|
419
|
+
type: ["string", "null"],
|
|
420
|
+
title: "生成模式",
|
|
421
|
+
description: "任务使用的生成模式;旧任务或尚未确定时可能为空。",
|
|
422
|
+
},
|
|
423
|
+
statusHint: {
|
|
424
|
+
type: "string",
|
|
425
|
+
title: "状态提示",
|
|
426
|
+
description: "面向用户的当前状态和下一步轮询提示。",
|
|
427
|
+
},
|
|
428
|
+
createdAt: {
|
|
429
|
+
type: ["string", "null"],
|
|
430
|
+
title: "创建时间",
|
|
431
|
+
description: "任务创建时间;不可用时为空。",
|
|
432
|
+
},
|
|
433
|
+
updatedAt: {
|
|
434
|
+
type: ["string", "null"],
|
|
435
|
+
title: "更新时间",
|
|
436
|
+
description: "任务最近更新时间;不可用时为空。",
|
|
437
|
+
},
|
|
438
|
+
completedAt: {
|
|
439
|
+
type: ["string", "null"],
|
|
440
|
+
title: "完成时间",
|
|
441
|
+
description: "任务完成时间;未完成时为空。",
|
|
442
|
+
},
|
|
443
|
+
errorMessage: {
|
|
444
|
+
type: "string",
|
|
445
|
+
title: "错误信息",
|
|
446
|
+
description: "任务失败时的可读原因,未失败时可能不返回。",
|
|
447
|
+
},
|
|
448
|
+
errorCode: {
|
|
449
|
+
type: "string",
|
|
450
|
+
title: "错误代码",
|
|
451
|
+
description: "任务失败时的机器可读错误代码,未失败时可能不返回。",
|
|
452
|
+
},
|
|
453
|
+
errorCategory: {
|
|
454
|
+
type: "string",
|
|
455
|
+
title: "错误分类",
|
|
456
|
+
description: "任务失败时的错误类别,未失败时可能不返回。",
|
|
457
|
+
},
|
|
458
|
+
retryable: {
|
|
459
|
+
type: "boolean",
|
|
460
|
+
title: "是否可重试",
|
|
461
|
+
description: "表示失败后是否建议重新发起任务,未失败时可能不返回。",
|
|
462
|
+
},
|
|
463
|
+
code: {
|
|
464
|
+
type: "string",
|
|
465
|
+
title: "查询错误代码",
|
|
466
|
+
description: "查询请求失败时的机器可读错误代码,成功时可能不返回。",
|
|
467
|
+
},
|
|
468
|
+
message: {
|
|
469
|
+
type: "string",
|
|
470
|
+
title: "查询消息",
|
|
471
|
+
description: "查询结果或失败原因的可读说明。",
|
|
472
|
+
},
|
|
473
|
+
route: {
|
|
474
|
+
type: "string",
|
|
475
|
+
title: "兼容模型标识",
|
|
476
|
+
description: "任务对应的兼容模型标识。",
|
|
477
|
+
},
|
|
478
|
+
engineName: {
|
|
479
|
+
type: "string",
|
|
480
|
+
title: "模型名称",
|
|
481
|
+
description: "任务实际使用的极睿专业版模型名称。",
|
|
482
|
+
},
|
|
281
483
|
videos: {
|
|
282
484
|
type: "array",
|
|
283
485
|
title: "视频结果列表",
|
|
@@ -287,18 +489,64 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
287
489
|
title: "视频结果",
|
|
288
490
|
description: "一个视频生成结果及其状态、链接、时间和错误信息。",
|
|
289
491
|
properties: {
|
|
290
|
-
id: {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
492
|
+
id: {
|
|
493
|
+
type: "number",
|
|
494
|
+
title: "视频结果 ID",
|
|
495
|
+
description: "该视频结果在任务中的唯一标识。",
|
|
496
|
+
},
|
|
497
|
+
status: {
|
|
498
|
+
type: "string",
|
|
499
|
+
title: "视频状态",
|
|
500
|
+
description: "该视频当前的生成状态。",
|
|
501
|
+
},
|
|
502
|
+
videoUrl: {
|
|
503
|
+
type: ["string", "null"],
|
|
504
|
+
title: "视频 URL",
|
|
505
|
+
description: "生成完成后的视频访问 URL;未完成或失败时为空。",
|
|
506
|
+
},
|
|
507
|
+
thumbnailUrl: {
|
|
508
|
+
type: ["string", "null"],
|
|
509
|
+
title: "缩略图 URL",
|
|
510
|
+
description: "视频缩略图访问 URL;尚未生成时为空。",
|
|
511
|
+
},
|
|
512
|
+
errorMessage: {
|
|
513
|
+
type: ["string", "null"],
|
|
514
|
+
title: "错误信息",
|
|
515
|
+
description: "该视频生成失败时的可读原因;未失败时为空。",
|
|
516
|
+
},
|
|
517
|
+
errorCode: {
|
|
518
|
+
type: ["string", "null"],
|
|
519
|
+
title: "错误代码",
|
|
520
|
+
description: "该视频生成失败时的机器可读错误代码;未失败时为空。",
|
|
521
|
+
},
|
|
522
|
+
errorCategory: {
|
|
523
|
+
type: ["string", "null"],
|
|
524
|
+
title: "错误分类",
|
|
525
|
+
description: "该视频生成失败时的错误类别;未失败时为空。",
|
|
526
|
+
},
|
|
527
|
+
retryable: {
|
|
528
|
+
type: ["boolean", "null"],
|
|
529
|
+
title: "是否可重试",
|
|
530
|
+
description: "表示该视频失败后是否建议重新创建任务;未失败时为空。",
|
|
531
|
+
},
|
|
532
|
+
createdAt: {
|
|
533
|
+
type: ["string", "null"],
|
|
534
|
+
title: "创建时间",
|
|
535
|
+
description: "该视频结果的创建时间;不可用时为空。",
|
|
536
|
+
},
|
|
537
|
+
completedAt: {
|
|
538
|
+
type: ["string", "null"],
|
|
539
|
+
title: "完成时间",
|
|
540
|
+
description: "该视频的完成时间;未完成时为空。",
|
|
541
|
+
},
|
|
300
542
|
},
|
|
301
|
-
required: [
|
|
543
|
+
required: [
|
|
544
|
+
"id",
|
|
545
|
+
"status",
|
|
546
|
+
"videoUrl",
|
|
547
|
+
"thumbnailUrl",
|
|
548
|
+
"errorMessage",
|
|
549
|
+
],
|
|
302
550
|
},
|
|
303
551
|
},
|
|
304
552
|
},
|
|
@@ -328,13 +576,41 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
328
576
|
title: "视频分析任务创建结果",
|
|
329
577
|
description: "返回分析任务标识、积分信息和后续查询提示;失败时部分字段可能不返回。",
|
|
330
578
|
properties: {
|
|
331
|
-
success: {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
579
|
+
success: {
|
|
580
|
+
type: "boolean",
|
|
581
|
+
title: "是否成功",
|
|
582
|
+
description: "表示视频分析任务是否创建成功。",
|
|
583
|
+
},
|
|
584
|
+
analysisId: {
|
|
585
|
+
type: "number",
|
|
586
|
+
title: "分析任务 ID",
|
|
587
|
+
description: "创建成功后返回的分析任务 ID,请用于调用 get_video_analysis_status。",
|
|
588
|
+
},
|
|
589
|
+
creditsUsed: {
|
|
590
|
+
type: "number",
|
|
591
|
+
title: "消耗积分",
|
|
592
|
+
description: "创建本次视频分析任务实际扣除的积分。",
|
|
593
|
+
},
|
|
594
|
+
remainingCredits: {
|
|
595
|
+
type: "number",
|
|
596
|
+
title: "剩余积分",
|
|
597
|
+
description: "任务创建后的账户剩余积分。",
|
|
598
|
+
},
|
|
599
|
+
statusHint: {
|
|
600
|
+
type: "string",
|
|
601
|
+
title: "后续操作提示",
|
|
602
|
+
description: "提示使用 analysisId 调用 get_video_analysis_status 查询分析进度。",
|
|
603
|
+
},
|
|
604
|
+
code: {
|
|
605
|
+
type: "string",
|
|
606
|
+
title: "错误代码",
|
|
607
|
+
description: "创建失败时返回的机器可读错误代码,成功时可能不返回。",
|
|
608
|
+
},
|
|
609
|
+
message: {
|
|
610
|
+
type: "string",
|
|
611
|
+
title: "结果消息",
|
|
612
|
+
description: "创建结果或失败原因的可读说明。",
|
|
613
|
+
},
|
|
338
614
|
},
|
|
339
615
|
required: ["success"],
|
|
340
616
|
},
|
|
@@ -361,27 +637,85 @@ export function createServer(token, apiBaseUrl, options = {}) {
|
|
|
361
637
|
title: "视频分析状态与结果",
|
|
362
638
|
description: "返回分析进度和结构化内容;任务未完成或失败时分析内容可能为空。",
|
|
363
639
|
properties: {
|
|
364
|
-
success: {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
640
|
+
success: {
|
|
641
|
+
type: "boolean",
|
|
642
|
+
title: "是否成功",
|
|
643
|
+
description: "表示查询请求是否成功。",
|
|
644
|
+
},
|
|
645
|
+
analysisId: {
|
|
646
|
+
type: "number",
|
|
647
|
+
title: "分析任务 ID",
|
|
648
|
+
description: "当前查询的视频分析任务 ID。",
|
|
649
|
+
},
|
|
650
|
+
status: {
|
|
651
|
+
type: "string",
|
|
652
|
+
title: "分析状态",
|
|
653
|
+
description: "视频分析任务当前的处理状态;未完成时应稍后继续查询。",
|
|
654
|
+
},
|
|
655
|
+
conversionProgress: {
|
|
656
|
+
type: ["number", "null"],
|
|
657
|
+
title: "处理进度",
|
|
658
|
+
description: "视频转换和分析进度,通常为 0–100;尚未开始时可能为空。",
|
|
659
|
+
},
|
|
660
|
+
script: {
|
|
661
|
+
type: ["string", "null"],
|
|
662
|
+
title: "视频脚本",
|
|
663
|
+
description: "从参考视频中提取的脚本内容;分析未完成时为空。",
|
|
664
|
+
},
|
|
665
|
+
style: {
|
|
666
|
+
type: ["string", "null"],
|
|
667
|
+
title: "视频风格",
|
|
668
|
+
description: "识别出的视觉、叙事和表达风格;分析未完成时为空。",
|
|
669
|
+
},
|
|
670
|
+
sellingPoints: {
|
|
671
|
+
type: ["string", "null"],
|
|
672
|
+
title: "卖点分析",
|
|
673
|
+
description: "从视频中识别出的商品或内容卖点;分析未完成时为空。",
|
|
674
|
+
},
|
|
675
|
+
viralFormula: {
|
|
676
|
+
type: ["string", "null"],
|
|
677
|
+
title: "创作公式",
|
|
678
|
+
description: "总结的视频结构和内容创作公式;分析未完成时为空。",
|
|
679
|
+
},
|
|
680
|
+
generatedPrompt: {
|
|
681
|
+
type: ["string", "null"],
|
|
682
|
+
title: "生成提示词",
|
|
683
|
+
description: "基于分析结果生成的后续视频创作提示词;分析未完成时为空。",
|
|
684
|
+
},
|
|
685
|
+
errorMessage: {
|
|
686
|
+
type: ["string", "null"],
|
|
687
|
+
title: "错误信息",
|
|
688
|
+
description: "分析失败时的可读原因;未失败时为空。",
|
|
689
|
+
},
|
|
690
|
+
originalVideoUrl: {
|
|
691
|
+
type: ["string", "null"],
|
|
692
|
+
title: "原视频 URL",
|
|
693
|
+
description: "本次分析所使用的视频访问 URL;不可用时为空。",
|
|
694
|
+
},
|
|
695
|
+
videoFileName: {
|
|
696
|
+
type: ["string", "null"],
|
|
697
|
+
title: "视频文件名",
|
|
698
|
+
description: "参考视频的文件名;不可用时为空。",
|
|
699
|
+
},
|
|
700
|
+
code: {
|
|
701
|
+
type: "string",
|
|
702
|
+
title: "查询错误代码",
|
|
703
|
+
description: "查询请求失败时返回的机器可读错误代码,成功时可能不返回。",
|
|
704
|
+
},
|
|
705
|
+
message: {
|
|
706
|
+
type: "string",
|
|
707
|
+
title: "查询消息",
|
|
708
|
+
description: "查询结果或失败原因的可读说明。",
|
|
709
|
+
},
|
|
378
710
|
},
|
|
379
711
|
required: ["success"],
|
|
380
712
|
},
|
|
381
713
|
},
|
|
382
714
|
...MCP_CREATIVE_TOOLS,
|
|
383
715
|
];
|
|
384
|
-
const filtered = options.filterTools
|
|
716
|
+
const filtered = options.filterTools
|
|
717
|
+
? options.filterTools(allTools)
|
|
718
|
+
: allTools;
|
|
385
719
|
return { tools: filtered };
|
|
386
720
|
});
|
|
387
721
|
/**
|
|
@@ -512,7 +846,12 @@ async function processFileInput(fileInput, expectedType, maxBytes) {
|
|
|
512
846
|
throw new McpToolError(`文件 ${basename(filePath)} 类型不匹配:当前是 ${mimeType},但该字段需要 ${expectedType} 文件。`, "INVALID_FILE_TYPE");
|
|
513
847
|
}
|
|
514
848
|
const size = statSync(filePath).size;
|
|
515
|
-
const effectiveMaxBytes = maxBytes ??
|
|
849
|
+
const effectiveMaxBytes = maxBytes ??
|
|
850
|
+
(expectedType === "video"
|
|
851
|
+
? 50 * 1024 * 1024
|
|
852
|
+
: expectedType === "audio"
|
|
853
|
+
? 15 * 1024 * 1024
|
|
854
|
+
: undefined);
|
|
516
855
|
if (effectiveMaxBytes !== undefined && size > effectiveMaxBytes) {
|
|
517
856
|
throw new McpToolError(`文件 ${basename(filePath)} 超过 ${Math.floor(effectiveMaxBytes / 1024 / 1024)}MB 限制。`, "MEDIA_FILE_TOO_LARGE");
|
|
518
857
|
}
|
|
@@ -537,43 +876,50 @@ async function processFileInputs(fileInputs, expectedType) {
|
|
|
537
876
|
return processed.length > 0 ? processed : undefined;
|
|
538
877
|
}
|
|
539
878
|
function hasItems(value) {
|
|
540
|
-
return Array.isArray(value) &&
|
|
879
|
+
return (Array.isArray(value) &&
|
|
880
|
+
value.some(item => typeof item === "string" && item.length > 0));
|
|
541
881
|
}
|
|
542
882
|
function itemCount(value) {
|
|
543
|
-
return Array.isArray(value)
|
|
883
|
+
return Array.isArray(value)
|
|
884
|
+
? value.filter(item => typeof item === "string" && item.length > 0).length
|
|
885
|
+
: 0;
|
|
544
886
|
}
|
|
545
887
|
function normalizeRoute(args) {
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
};
|
|
888
|
+
// CLI 只做本地预检;最终模型与线路由 HTTP 服务端裁定并在响应里回传,
|
|
889
|
+
// 避免双配置源发生漂移。
|
|
890
|
+
const model = args && typeof args === "object" ? args.model : undefined;
|
|
891
|
+
if (isRetiredSaModel(model)) {
|
|
892
|
+
throw new McpToolError("SA 模型(sora-2)已下线,请改用 WA。", "MODEL_RETIRED");
|
|
893
|
+
}
|
|
894
|
+
if (isMcpModel(model)) {
|
|
895
|
+
return resolveMcpEngine({ model });
|
|
896
|
+
}
|
|
897
|
+
if (model !== undefined && model !== null) {
|
|
898
|
+
throw new McpToolError("model 必须是 WA、SD、SD25、HA 或 H3。", "INVALID_ENGINE");
|
|
554
899
|
}
|
|
555
900
|
const raw = args && typeof args === "object" ? args.route : undefined;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
throw new McpToolError("model 必须是 SA、SD、SD25、HA 或 H3;route 只接受 S2、S5、S6、S7、S8(已废弃),请改用 model。", "INVALID_ENGINE");
|
|
901
|
+
if (isRetiredSaRoute(raw)) {
|
|
902
|
+
throw new McpToolError("SA 模型(sora-2)已下线,请改用 WA。", "MODEL_RETIRED");
|
|
559
903
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
904
|
+
if (raw === undefined || raw === null) {
|
|
905
|
+
// 省略 model/route 时统一回退到 WA,与 resolveMcpEngine 的默认值一致。
|
|
906
|
+
return resolveMcpEngine({});
|
|
907
|
+
}
|
|
908
|
+
if (typeof raw !== "string" || !isMcpRoute(raw)) {
|
|
909
|
+
throw new McpToolError("route 只接受 S5、S6、S7、S8、S9(已废弃),请改用 model。", "INVALID_ENGINE");
|
|
910
|
+
}
|
|
911
|
+
return resolveMcpEngine({ route: raw });
|
|
566
912
|
}
|
|
567
913
|
function normalizeTaskMode(route, value) {
|
|
568
914
|
const mode = typeof value === "string" ? value : getMcpDefaultTaskMode(route);
|
|
569
|
-
if (route === "S2") {
|
|
570
|
-
if (value !== undefined) {
|
|
571
|
-
throw new McpToolError("taskMode 仅适用于极睿SD专业版或极睿HA专业版;S2 极睿SA专业版无需传 taskMode。", "UNSUPPORTED_FIELD", route);
|
|
572
|
-
}
|
|
573
|
-
return getMcpDefaultTaskMode(route);
|
|
574
|
-
}
|
|
575
915
|
if (!isValidMcpTaskMode(route, mode)) {
|
|
576
|
-
const allowedModes = route === "S5" || route === "S8"
|
|
916
|
+
const allowedModes = route === "S5" || route === "S8"
|
|
917
|
+
? "allinone、firstlast 或 edit"
|
|
918
|
+
: route === "S9"
|
|
919
|
+
? "allinone、firstlast 或 edit"
|
|
920
|
+
: route === "S7"
|
|
921
|
+
? "allinone、firstlast 或 reference"
|
|
922
|
+
: "standard、reference 或 edit";
|
|
577
923
|
throw new McpToolError(`${getMcpEngineName(route)} 的 taskMode 只能是 ${allowedModes}。`, "INVALID_TASK_MODE", route);
|
|
578
924
|
}
|
|
579
925
|
return mode;
|
|
@@ -585,49 +931,69 @@ export function validateCreateVideoArgs(args) {
|
|
|
585
931
|
if (!args.prompt || typeof args.prompt !== "string" || !args.prompt.trim()) {
|
|
586
932
|
throw new McpToolError("prompt 不能为空。");
|
|
587
933
|
}
|
|
588
|
-
if (args.count !== undefined &&
|
|
934
|
+
if (args.count !== undefined &&
|
|
935
|
+
(!Number.isInteger(args.count) || args.count < 1 || args.count > 12)) {
|
|
589
936
|
throw new McpToolError("count 必须是 1-12 的整数。", "INVALID_COUNT");
|
|
590
937
|
}
|
|
591
938
|
const norm = normalizeRoute(args);
|
|
592
939
|
const route = norm.route;
|
|
593
940
|
const taskMode = normalizeTaskMode(route, args.taskMode);
|
|
594
941
|
const resolution = typeof args.resolution === "string" ? args.resolution : "720p";
|
|
942
|
+
const outputFormat = typeof args.outputFormat === "string" ? args.outputFormat : "mp4";
|
|
595
943
|
const dryRun = args.dryRun === true;
|
|
596
|
-
if (route === "S8" &&
|
|
597
|
-
|
|
944
|
+
if (route === "S8" &&
|
|
945
|
+
resolution !== "480p" &&
|
|
946
|
+
resolution !== "720p" &&
|
|
947
|
+
resolution !== "1080p") {
|
|
948
|
+
throw new McpToolError(`${getMcpEngineName(route)} 仅支持 480p、720p 或 1080p。`, "INVALID_RESOLUTION", route);
|
|
949
|
+
}
|
|
950
|
+
if (outputFormat !== "mp4" && outputFormat !== "mov") {
|
|
951
|
+
throw new McpToolError("outputFormat 只能是 mp4 或 mov。", "UNSUPPORTED_FIELD", route);
|
|
952
|
+
}
|
|
953
|
+
if (outputFormat === "mov" && route !== "S8") {
|
|
954
|
+
throw new McpToolError(`MOV 仅适用于${getMcpEngineName("S8")}。`, "UNSUPPORTED_FIELD", route);
|
|
955
|
+
}
|
|
956
|
+
if (route === "S9" &&
|
|
957
|
+
resolution !== "480p" &&
|
|
958
|
+
resolution !== "720p" &&
|
|
959
|
+
resolution !== "1080p") {
|
|
960
|
+
throw new McpToolError(`${getMcpEngineName(route)} 仅支持 480p、720p 或 1080p。`, "INVALID_RESOLUTION", route);
|
|
598
961
|
}
|
|
599
|
-
if ((route === "S5" || route === "S6") &&
|
|
962
|
+
if ((route === "S5" || route === "S6") &&
|
|
963
|
+
resolution !== "720p" &&
|
|
964
|
+
resolution !== "1080p") {
|
|
600
965
|
throw new McpToolError(`${getMcpEngineName(route)} 仅支持 720p 或 1080p。`, "INVALID_RESOLUTION", route);
|
|
601
966
|
}
|
|
602
|
-
if (route === "
|
|
603
|
-
|
|
967
|
+
if (route === "S9" &&
|
|
968
|
+
args.seconds !== undefined &&
|
|
969
|
+
!isWan3OutputSeconds(args.seconds, taskMode)) {
|
|
970
|
+
throw new McpToolError(taskMode === "edit"
|
|
971
|
+
? "WA 视频编辑仅支持智能时长(-1)。"
|
|
972
|
+
: `${getMcpEngineName(route)} 视频时长仅支持 ${WAN3_OUTPUT_SECONDS_LABEL} 秒。`, "INVALID_SECONDS", route);
|
|
604
973
|
}
|
|
605
974
|
// S5/S6/S7/S8 的 seconds 白名单
|
|
606
975
|
if (route === "S5" || route === "S6" || route === "S7" || route === "S8") {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
976
|
+
if ((route === "S5" || route === "S8") && taskMode === "edit") {
|
|
977
|
+
const maxEditSeconds = route === "S8" ? 30 : 15;
|
|
978
|
+
if (args.seconds !== undefined &&
|
|
979
|
+
(!Number.isInteger(args.seconds) ||
|
|
980
|
+
args.seconds < 4 ||
|
|
981
|
+
args.seconds > maxEditSeconds)) {
|
|
982
|
+
throw new McpToolError(`${getMcpEngineName(route)} Edit 参考视频时长必须为 4–${maxEditSeconds} 秒。`, "INVALID_SECONDS", route);
|
|
983
|
+
}
|
|
612
984
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
args.generateAudio !== undefined ? "generateAudio" : undefined,
|
|
622
|
-
args.webSearchEnabled !== undefined ? "webSearchEnabled" : undefined,
|
|
623
|
-
].filter(Boolean);
|
|
624
|
-
if (unsupported.length > 0) {
|
|
625
|
-
throw new McpToolError(`这些字段不适用于极睿SA专业版:${unsupported.join("、")}。S2 单图请使用 sourceImage。`, "UNSUPPORTED_FIELD", route);
|
|
985
|
+
else {
|
|
986
|
+
const allowedSeconds = route === "S8" ? [5, 10, 15, 20, 25, 30] : [5, 10, 15];
|
|
987
|
+
if (args.seconds !== undefined &&
|
|
988
|
+
!allowedSeconds.includes(args.seconds)) {
|
|
989
|
+
throw new McpToolError(route === "S8"
|
|
990
|
+
? `${getMcpEngineName(route)} 视频时长仅支持 5、10、15、20、25、30 秒。`
|
|
991
|
+
: `${getMcpEngineName(route)} 视频时长仅支持 5、10、15 秒。`, "INVALID_SECONDS", route);
|
|
992
|
+
}
|
|
626
993
|
}
|
|
627
994
|
}
|
|
628
995
|
if (route === "S5" || route === "S8") {
|
|
629
|
-
// S8 = SD2.5 独立通道:素材上限放宽(30 图 / 10 视频 / 10
|
|
630
|
-
// 不支持联网搜索;文案与上限按通道区分。
|
|
996
|
+
// S8 = SD2.5 独立通道:素材上限放宽(30 图 / 10 视频 / 10 音频)。
|
|
631
997
|
const isSd25 = route === "S8";
|
|
632
998
|
const sdEngine = getMcpEngineName(route);
|
|
633
999
|
if (args.sourceImage !== undefined) {
|
|
@@ -636,13 +1002,11 @@ export function validateCreateVideoArgs(args) {
|
|
|
636
1002
|
if (args.ratio !== undefined) {
|
|
637
1003
|
throw new McpToolError(`ratio 仅适用于极睿HA专业版;${sdEngine}请使用 size 控制画面比例。`, "UNSUPPORTED_FIELD", route);
|
|
638
1004
|
}
|
|
639
|
-
if (isSd25 && args.webSearchEnabled !== undefined) {
|
|
640
|
-
throw new McpToolError(`webSearchEnabled 不适用于${sdEngine}(SD2.5 不支持联网搜索)。`, "UNSUPPORTED_FIELD", route);
|
|
641
|
-
}
|
|
642
1005
|
if (taskMode === "firstlast" && !hasItems(args.sourceImages)) {
|
|
643
1006
|
throw new McpToolError(`${sdEngine}首尾帧模式需要在 sourceImages 中提供至少 1 张首帧图片。`, "MISSING_SOURCE_IMAGE", route);
|
|
644
1007
|
}
|
|
645
|
-
if (taskMode === "firstlast" &&
|
|
1008
|
+
if (taskMode === "firstlast" &&
|
|
1009
|
+
(hasItems(args.sourceVideos) || hasItems(args.sourceAudios))) {
|
|
646
1010
|
throw new McpToolError(`sourceVideos/sourceAudios 仅适用于${sdEngine}全能参考模式;首尾帧模式只使用 sourceImages。`, "UNSUPPORTED_FIELD", route);
|
|
647
1011
|
}
|
|
648
1012
|
if (taskMode === "firstlast" && itemCount(args.sourceImages) > 2) {
|
|
@@ -651,19 +1015,59 @@ export function validateCreateVideoArgs(args) {
|
|
|
651
1015
|
const maxImages = isSd25 ? 30 : 9;
|
|
652
1016
|
const maxVideos = isSd25 ? 10 : 3;
|
|
653
1017
|
const maxAudios = isSd25 ? 10 : 3;
|
|
654
|
-
if (taskMode === "allinone"
|
|
1018
|
+
if ((taskMode === "allinone" || taskMode === "edit") &&
|
|
1019
|
+
itemCount(args.sourceImages) > maxImages) {
|
|
655
1020
|
throw new McpToolError(`${sdEngine}全能参考模式最多支持 ${maxImages} 张参考图片。`, "MEDIA_COUNT_EXCEEDED", route);
|
|
656
1021
|
}
|
|
657
|
-
if (taskMode === "allinone"
|
|
1022
|
+
if ((taskMode === "allinone" || taskMode === "edit") &&
|
|
1023
|
+
itemCount(args.sourceVideos) > maxVideos) {
|
|
658
1024
|
throw new McpToolError(`${sdEngine}全能参考模式最多支持 ${maxVideos} 个参考视频。`, "MEDIA_COUNT_EXCEEDED", route);
|
|
659
1025
|
}
|
|
660
|
-
if (taskMode === "allinone"
|
|
1026
|
+
if ((taskMode === "allinone" || taskMode === "edit") &&
|
|
1027
|
+
itemCount(args.sourceAudios) > maxAudios) {
|
|
661
1028
|
throw new McpToolError(`${sdEngine}全能参考模式最多支持 ${maxAudios} 个参考音频。`, "MEDIA_COUNT_EXCEEDED", route);
|
|
662
1029
|
}
|
|
663
1030
|
// 纯音频参考:S5 2.0 要求音频搭配图片/视频;S8(SD2.5)支持纯音频参考,豁免
|
|
664
|
-
if (!isSd25 &&
|
|
1031
|
+
if (!isSd25 &&
|
|
1032
|
+
taskMode === "allinone" &&
|
|
1033
|
+
hasItems(args.sourceAudios) &&
|
|
1034
|
+
!hasItems(args.sourceImages) &&
|
|
1035
|
+
!hasItems(args.sourceVideos)) {
|
|
665
1036
|
throw new McpToolError(`${sdEngine}全能参考模式中,音频素材必须搭配至少一张参考图片或一个参考视频。`, "MISSING_VISUAL_REFERENCE", route);
|
|
666
1037
|
}
|
|
1038
|
+
if (taskMode === "edit" && !hasItems(args.sourceVideos)) {
|
|
1039
|
+
throw new McpToolError(`${sdEngine} Edit 至少 1 个参考视频。`, "MISSING_SOURCE_VIDEO", route);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
if (route === "S9") {
|
|
1043
|
+
if (args.sourceImage !== undefined) {
|
|
1044
|
+
throw new McpToolError("sourceImage 不适用于极睿WA专业版;请使用 sourceImages。", "UNSUPPORTED_FIELD", route);
|
|
1045
|
+
}
|
|
1046
|
+
if (args.webSearchEnabled !== undefined) {
|
|
1047
|
+
throw new McpToolError("webSearchEnabled 不适用于极睿WA专业版。", "UNSUPPORTED_FIELD", route);
|
|
1048
|
+
}
|
|
1049
|
+
if (args.ratio !== undefined &&
|
|
1050
|
+
!["adaptive", "16:9", "4:3", "1:1", "3:4", "9:16"].includes(args.ratio)) {
|
|
1051
|
+
throw new McpToolError("WA 不支持该画面比例。", "INVALID_RATIO", route);
|
|
1052
|
+
}
|
|
1053
|
+
if (taskMode === "firstlast") {
|
|
1054
|
+
const images = itemCount(args.sourceImages);
|
|
1055
|
+
if (images < 1 || images > 2) {
|
|
1056
|
+
throw new McpToolError("WA 首尾帧模式必须提供 1 张首帧,最多再提供 1 张尾帧。", "MISSING_SOURCE_IMAGE", route);
|
|
1057
|
+
}
|
|
1058
|
+
if (hasItems(args.sourceVideos) || hasItems(args.sourceAudios)) {
|
|
1059
|
+
throw new McpToolError("WA 参考素材与首尾帧模式严格互斥。", "UNSUPPORTED_FIELD", route);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (taskMode === "edit" && !hasItems(args.sourceVideos)) {
|
|
1063
|
+
throw new McpToolError("WA 视频编辑至少需要一个参考视频。", "MISSING_SOURCE_VIDEO", route);
|
|
1064
|
+
}
|
|
1065
|
+
if (taskMode !== "firstlast" && itemCount(args.sourceImages) > 10) {
|
|
1066
|
+
throw new McpToolError("WA 参考图片最多支持 10 张。", "MEDIA_COUNT_EXCEEDED", route);
|
|
1067
|
+
}
|
|
1068
|
+
if (itemCount(args.sourceVideos) > 5 || itemCount(args.sourceAudios) > 5) {
|
|
1069
|
+
throw new McpToolError("WA 参考视频和参考音频各最多支持 5 个。", "MEDIA_COUNT_EXCEEDED", route);
|
|
1070
|
+
}
|
|
667
1071
|
}
|
|
668
1072
|
if (route === "S6") {
|
|
669
1073
|
const unsupported = [
|
|
@@ -674,10 +1078,12 @@ export function validateCreateVideoArgs(args) {
|
|
|
674
1078
|
if (unsupported.length > 0) {
|
|
675
1079
|
throw new McpToolError(`这些字段仅适用于极睿SD专业版,不能用于极睿HA专业版:${unsupported.join("、")}。`, "UNSUPPORTED_FIELD", route);
|
|
676
1080
|
}
|
|
677
|
-
if (taskMode === "standard" &&
|
|
1081
|
+
if (taskMode === "standard" &&
|
|
1082
|
+
(hasItems(args.sourceImages) || hasItems(args.sourceVideos))) {
|
|
678
1083
|
throw new McpToolError("极睿HA专业版标准模式只支持 sourceImage 作为单图输入;多图请使用 reference 模式,视频请使用 edit 模式。", "UNSUPPORTED_FIELD", route);
|
|
679
1084
|
}
|
|
680
|
-
if ((taskMode === "reference" || taskMode === "edit") &&
|
|
1085
|
+
if ((taskMode === "reference" || taskMode === "edit") &&
|
|
1086
|
+
args.sourceImage !== undefined) {
|
|
681
1087
|
throw new McpToolError("sourceImage 仅适用于极睿HA专业版标准模式;reference/edit 模式请使用 sourceImages。", "UNSUPPORTED_FIELD", route);
|
|
682
1088
|
}
|
|
683
1089
|
if (taskMode === "reference" && !hasItems(args.sourceImages)) {
|
|
@@ -704,7 +1110,8 @@ export function validateCreateVideoArgs(args) {
|
|
|
704
1110
|
if (args.sourceImage !== undefined) {
|
|
705
1111
|
throw new McpToolError(`sourceImage 不适用于${getMcpEngineName(route)};请使用 sourceImages 传参考图或首尾帧。`, "UNSUPPORTED_FIELD", route);
|
|
706
1112
|
}
|
|
707
|
-
if (args.generateAudio !== undefined ||
|
|
1113
|
+
if (args.generateAudio !== undefined ||
|
|
1114
|
+
args.webSearchEnabled !== undefined) {
|
|
708
1115
|
throw new McpToolError(`generateAudio/webSearchEnabled 不适用于${getMcpEngineName(route)}。`, "UNSUPPORTED_FIELD", route);
|
|
709
1116
|
}
|
|
710
1117
|
if (taskMode === "firstlast") {
|
|
@@ -734,6 +1141,7 @@ export function validateCreateVideoArgs(args) {
|
|
|
734
1141
|
usedRouteAlias: norm.usedRouteAlias,
|
|
735
1142
|
taskMode,
|
|
736
1143
|
resolution,
|
|
1144
|
+
outputFormat,
|
|
737
1145
|
dryRun,
|
|
738
1146
|
statusHint: getMcpVideoStatusHint(dryRun),
|
|
739
1147
|
};
|
|
@@ -746,7 +1154,8 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
|
|
|
746
1154
|
const isProfessionalRoute = normalized.route === "S5" ||
|
|
747
1155
|
normalized.route === "S6" ||
|
|
748
1156
|
normalized.route === "S7" ||
|
|
749
|
-
normalized.route === "S8"
|
|
1157
|
+
normalized.route === "S8" ||
|
|
1158
|
+
normalized.route === "S9";
|
|
750
1159
|
const requestBody = {
|
|
751
1160
|
token,
|
|
752
1161
|
prompt: args.prompt,
|
|
@@ -754,7 +1163,12 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
|
|
|
754
1163
|
? (await processFileInput(args.sourceImage, "image")).data
|
|
755
1164
|
: undefined,
|
|
756
1165
|
size: args.size || "1280x720",
|
|
757
|
-
seconds: args.seconds
|
|
1166
|
+
seconds: args.seconds ??
|
|
1167
|
+
(normalized.route === "S9"
|
|
1168
|
+
? normalized.taskMode === "edit"
|
|
1169
|
+
? -1
|
|
1170
|
+
: 5
|
|
1171
|
+
: 10),
|
|
758
1172
|
language: args.language || "zh-CN",
|
|
759
1173
|
count: args.count || 1,
|
|
760
1174
|
model: normalized.model,
|
|
@@ -768,7 +1182,9 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
|
|
|
768
1182
|
requestBody.taskMode = normalized.taskMode;
|
|
769
1183
|
requestBody.isFirstLastMode = normalized.taskMode === "firstlast";
|
|
770
1184
|
requestBody.resolution = normalized.resolution;
|
|
771
|
-
requestBody.
|
|
1185
|
+
requestBody.outputFormat = normalized.outputFormat;
|
|
1186
|
+
requestBody.ratio =
|
|
1187
|
+
args.ratio || (normalized.route === "S9" ? "adaptive" : undefined);
|
|
772
1188
|
requestBody.generateAudio = args.generateAudio;
|
|
773
1189
|
requestBody.webSearchEnabled = args.webSearchEnabled;
|
|
774
1190
|
if (normalized.dryRun) {
|
|
@@ -793,7 +1209,9 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
|
|
|
793
1209
|
`- **当前积分**: ${response.remainingCredits}`,
|
|
794
1210
|
];
|
|
795
1211
|
if (typeof estimatedCredits === "number") {
|
|
796
|
-
result.splice(6, 0,
|
|
1212
|
+
result.splice(6, 0, response.creditEstimateKind === "reservation"
|
|
1213
|
+
? `- **积分预留上限**: ${response.reservationCeilingCredits ?? estimatedCredits}(完成后按输入视频及实际时长结算)`
|
|
1214
|
+
: `- **预估积分**: ${estimatedCredits}(视频完成后按成功扣费)`);
|
|
797
1215
|
}
|
|
798
1216
|
if (!dryRun && response.taskId !== undefined) {
|
|
799
1217
|
result.splice(4, 0, `- **任务 ID**: ${response.taskId}`);
|
|
@@ -820,6 +1238,13 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
|
|
|
820
1238
|
videoCount: response.videoCount,
|
|
821
1239
|
dryRun,
|
|
822
1240
|
estimatedCredits,
|
|
1241
|
+
...(response.creditEstimateKind === "reservation"
|
|
1242
|
+
? {
|
|
1243
|
+
creditEstimateKind: response.creditEstimateKind,
|
|
1244
|
+
reservationCeilingCredits: response.reservationCeilingCredits ?? estimatedCredits,
|
|
1245
|
+
settlementDescription: response.settlementDescription,
|
|
1246
|
+
}
|
|
1247
|
+
: {}),
|
|
823
1248
|
estimateSource: response.estimateSource,
|
|
824
1249
|
remainingCredits: response.remainingCredits,
|
|
825
1250
|
statusHint,
|
|
@@ -833,9 +1258,13 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
|
|
|
833
1258
|
*/
|
|
834
1259
|
async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
835
1260
|
const response = await callRestApi(apiBaseUrl, `/mcp/video/status/${args.taskId}`, { token }, "GET");
|
|
836
|
-
const statusText = response.status === "completed"
|
|
837
|
-
|
|
838
|
-
|
|
1261
|
+
const statusText = response.status === "completed"
|
|
1262
|
+
? "✅ 已完成"
|
|
1263
|
+
: response.status === "processing"
|
|
1264
|
+
? "🔄 处理中"
|
|
1265
|
+
: response.status === "failed"
|
|
1266
|
+
? "❌ 失败"
|
|
1267
|
+
: `⏳ ${response.status}`;
|
|
839
1268
|
let result = [
|
|
840
1269
|
`# 视频生成任务状态`,
|
|
841
1270
|
``,
|
|
@@ -844,7 +1273,9 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
|
844
1273
|
`- **进度**: ${response.progress}% (${response.completedVideos}/${response.totalVideos} 个视频)`,
|
|
845
1274
|
response.engineName ? `- **生成引擎**: ${response.engineName}` : undefined,
|
|
846
1275
|
response.taskMode ? `- **模式**: ${response.taskMode}` : undefined,
|
|
847
|
-
typeof response.estimatedCredits === "number"
|
|
1276
|
+
typeof response.estimatedCredits === "number"
|
|
1277
|
+
? `- **预估积分**: ${response.estimatedCredits}`
|
|
1278
|
+
: undefined,
|
|
848
1279
|
``,
|
|
849
1280
|
].filter((line) => line !== undefined);
|
|
850
1281
|
if (response.errorMessage) {
|
|
@@ -857,9 +1288,13 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
|
857
1288
|
if (response.videos && response.videos.length > 0) {
|
|
858
1289
|
result.push(`## 视频列表:`);
|
|
859
1290
|
response.videos.forEach((video, index) => {
|
|
860
|
-
const videoStatus = video.status === "completed"
|
|
861
|
-
|
|
862
|
-
|
|
1291
|
+
const videoStatus = video.status === "completed"
|
|
1292
|
+
? "✅"
|
|
1293
|
+
: video.status === "processing"
|
|
1294
|
+
? "🔄"
|
|
1295
|
+
: video.status === "failed"
|
|
1296
|
+
? "❌"
|
|
1297
|
+
: "⏳";
|
|
863
1298
|
result.push(``);
|
|
864
1299
|
result.push(`${index + 1}. ${videoStatus} 视频 ID: ${video.id}`);
|
|
865
1300
|
if (video.videoUrl) {
|
|
@@ -949,10 +1384,15 @@ async function handleCreateVideoAnalysisTask(apiBaseUrl, token, args) {
|
|
|
949
1384
|
*/
|
|
950
1385
|
async function handleGetVideoAnalysisStatus(apiBaseUrl, token, args) {
|
|
951
1386
|
const response = await callRestApi(apiBaseUrl, `/mcp/video-analysis/status/${args.analysisId}`, { token }, "GET");
|
|
952
|
-
const statusText = response.status === "completed"
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1387
|
+
const statusText = response.status === "completed"
|
|
1388
|
+
? "✅ 已完成"
|
|
1389
|
+
: response.status === "processing"
|
|
1390
|
+
? "🔄 处理中"
|
|
1391
|
+
: response.status === "pending"
|
|
1392
|
+
? "⏳ 等待中"
|
|
1393
|
+
: response.status === "failed"
|
|
1394
|
+
? "❌ 失败"
|
|
1395
|
+
: `⏳ ${response.status}`;
|
|
956
1396
|
let result = [
|
|
957
1397
|
`# 视频拆解任务状态`,
|
|
958
1398
|
``,
|
|
@@ -1032,7 +1472,9 @@ function creativeToolResult(output, lines) {
|
|
|
1032
1472
|
};
|
|
1033
1473
|
}
|
|
1034
1474
|
async function handleCreateMotionVideoRemakeTask(apiBaseUrl, token, args) {
|
|
1035
|
-
if (!args ||
|
|
1475
|
+
if (!args ||
|
|
1476
|
+
typeof args.sourceImage !== "string" ||
|
|
1477
|
+
typeof args.referenceVideo !== "string") {
|
|
1036
1478
|
throw new McpToolError("sourceImage 和 referenceVideo 不能为空。", "MISSING_PARAMETERS");
|
|
1037
1479
|
}
|
|
1038
1480
|
const sourceImage = await processFileInput(args.sourceImage, "image", 5 * 1024 * 1024);
|
|
@@ -1053,7 +1495,8 @@ async function handleCreateMotionVideoRemakeTask(apiBaseUrl, token, args) {
|
|
|
1053
1495
|
response.estimateSource === "duration_hint"
|
|
1054
1496
|
? `- 时长提示: ${response.durationHintSeconds} 秒(正式创建将使用 OSS 真实检测值)`
|
|
1055
1497
|
: "- 时长提示: 未提供,正式创建后由 OSS 检测",
|
|
1056
|
-
response.estimatedCredits === null ||
|
|
1498
|
+
response.estimatedCredits === null ||
|
|
1499
|
+
response.estimatedCredits === undefined
|
|
1057
1500
|
? "- 预计积分: 暂不可用"
|
|
1058
1501
|
: `- 预计积分: ${response.estimatedCredits}`,
|
|
1059
1502
|
`- 当前余额: ${response.remainingCredits}`,
|
|
@@ -1084,11 +1527,15 @@ async function handleGetMotionVideoRemakeStatus(apiBaseUrl, token, args) {
|
|
|
1084
1527
|
`- 状态: ${response.status}`,
|
|
1085
1528
|
`- 进度: ${response.progress}%`,
|
|
1086
1529
|
`- 已完成: ${completed}/${response.count}`,
|
|
1087
|
-
...(response.status === "completed"
|
|
1530
|
+
...(response.status === "completed"
|
|
1531
|
+
? ["- 视频链接已返回在 results 中"]
|
|
1532
|
+
: []),
|
|
1088
1533
|
]);
|
|
1089
1534
|
}
|
|
1090
1535
|
async function handleCreateEcommerceStoryboardTask(apiBaseUrl, token, args) {
|
|
1091
|
-
if (!args ||
|
|
1536
|
+
if (!args ||
|
|
1537
|
+
typeof args.productName !== "string" ||
|
|
1538
|
+
typeof args.productDescription !== "string") {
|
|
1092
1539
|
throw new McpToolError("productName 和 productDescription 不能为空。", "MISSING_PARAMETERS");
|
|
1093
1540
|
}
|
|
1094
1541
|
const productImage = args.productImage
|
|
@@ -1148,7 +1595,9 @@ async function handleGetEcommerceStoryboardStatus(apiBaseUrl, token, args) {
|
|
|
1148
1595
|
*/
|
|
1149
1596
|
async function callRestApi(apiBaseUrl, path, params, method = "GET") {
|
|
1150
1597
|
// Build URL
|
|
1151
|
-
const baseUrl = apiBaseUrl.endsWith("/")
|
|
1598
|
+
const baseUrl = apiBaseUrl.endsWith("/")
|
|
1599
|
+
? apiBaseUrl.slice(0, -1)
|
|
1600
|
+
: apiBaseUrl;
|
|
1152
1601
|
const fullPath = path.startsWith("/") ? path : "/" + path;
|
|
1153
1602
|
let fullUrl = `${baseUrl}${fullPath}`;
|
|
1154
1603
|
// For GET requests, add params as query string
|