@infimind/video-mcp-cli 1.0.10 → 1.0.12

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/server.js CHANGED
@@ -8,7 +8,8 @@ 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, } from "./videoRules.js";
11
+ import { MCP_SERVER_VERSION, getMcpDefaultTaskMode, getMcpEngineName, getMcpVideoStatusHint, isMcpRoute, isValidMcpTaskMode, getMcpModelForRoute, isMcpModel, MCP_VIDEO_MODELS, } from "./videoRules.js";
12
+ import { MCP_CREATIVE_TOOLS } from "./creativeTools.js";
12
13
  class McpToolError extends Error {
13
14
  code;
14
15
  route;
@@ -21,10 +22,7 @@ class McpToolError extends Error {
21
22
  this.engineName = route ? getMcpEngineName(route) : undefined;
22
23
  }
23
24
  }
24
- /**
25
- * Create and configure the MCP Server
26
- */
27
- export function createServer(token, apiBaseUrl) {
25
+ export function createServer(token, apiBaseUrl, options = {}) {
28
26
  const server = new Server({
29
27
  name: "sora-video-generator-mcp",
30
28
  version: MCP_SERVER_VERSION,
@@ -37,7 +35,7 @@ export function createServer(token, apiBaseUrl) {
37
35
  * List available tools
38
36
  */
39
37
  server.setRequestHandler(ListToolsRequestSchema, async () => {
40
- const tools = [
38
+ const allTools = [
41
39
  {
42
40
  name: "get_user_info",
43
41
  title: "获取用户信息",
@@ -65,97 +63,144 @@ export function createServer(token, apiBaseUrl) {
65
63
  },
66
64
  {
67
65
  name: "create_video_task",
68
- title: "创建视频生成任务",
69
- description: "创建视频生成任务。支持本地文件路径或公网 URL 作为参考图片。" +
70
- "支持极睿Sd专业版和极睿Ha专业版的多模态素材输入。",
66
+ title: "生成 AI 视频",
67
+ description: "根据提示词和可选的图片、视频或音频素材创建 AI 视频生成任务,可选择 SA、SD 或 HA 专业版;素材可使用本地文件路径或公网 URL。" +
68
+ "本工具返回 taskId,不直接返回最终视频;创建后请调用 get_video_status 并原样传入 taskId,轮询生成进度和结果。",
71
69
  inputSchema: {
72
70
  type: "object",
71
+ title: "AI 视频生成参数",
72
+ description: "创建 AI 视频生成任务所需的提示词、参考素材和生成配置。",
73
73
  properties: {
74
74
  prompt: {
75
75
  type: "string",
76
- description: "视频生成提示词,描述你想要创建的内容",
76
+ title: "视频生成提示词",
77
+ description: "必填。描述希望生成的视频内容、主体、动作、场景、镜头和风格。",
77
78
  },
78
79
  sourceImage: {
79
80
  type: "string",
80
- description: "可选的单张参考图片。S2 使用此字段;S6 标准模式可用作单图图生视频。可以是本地文件路径或公网 URL",
81
+ title: "单张参考图片",
82
+ description: "可选。本地图片路径或公网 URL;SA 专业版图生视频或 HA 专业版标准模式单图生成时使用。",
81
83
  },
82
84
  sourceImages: {
83
85
  type: "array",
84
- description: "S5/S6 多张参考图片。本地文件路径或公网 URL 数组。S5 首尾帧模式下第1张为首帧、第2张为尾帧;S6 reference/edit 模式作为参考图",
85
- items: { type: "string" },
86
+ title: "参考图片列表",
87
+ description: "可选。本地图片路径或公网 URL 列表;SD 专业版首尾帧模式按首帧、尾帧顺序传入,HA 专业版图片参考或视频编辑模式按引用顺序传入。",
88
+ items: {
89
+ type: "string",
90
+ title: "参考图片",
91
+ description: "一张参考图片的本地路径或公网 URL。",
92
+ },
86
93
  },
87
94
  sourceVideos: {
88
95
  type: "array",
89
- description: "S5 全能参考或 S6 视频编辑模式的视频素材。本地文件路径或公网 URL 数组",
90
- items: { type: "string" },
96
+ title: "参考视频列表",
97
+ description: "可选。本地视频路径或公网 URL 列表;用于 SD 专业版全能参考模式或 HA 专业版视频编辑模式。",
98
+ items: {
99
+ type: "string",
100
+ title: "参考视频",
101
+ description: "一段参考视频的本地路径或公网 URL。",
102
+ },
91
103
  },
92
104
  sourceAudios: {
93
105
  type: "array",
94
- description: "S5 全能参考模式的音频素材。本地文件路径或公网 URL 数组",
95
- items: { type: "string" },
106
+ title: "参考音频列表",
107
+ description: "可选。本地音频路径或公网 URL 列表;用于 SD 专业版全能参考模式。",
108
+ items: {
109
+ type: "string",
110
+ title: "参考音频",
111
+ description: "一段参考音频的本地路径或公网 URL。",
112
+ },
96
113
  },
97
114
  sourceVideoDurationSeconds: {
98
115
  type: "array",
99
- description: "可选。dryRun 使用的视频素材时长秒数数组,顺序与 sourceVideos 一致;用于获得预估积分",
100
- items: { type: "number" },
116
+ title: "参考视频时长列表",
117
+ description: "可选。仅在 dryRun 预检时用于估算积分,单位为秒,顺序须与 sourceVideos 一致。",
118
+ items: {
119
+ type: "number",
120
+ title: "参考视频时长",
121
+ description: "对应参考视频的时长,单位为秒。",
122
+ },
101
123
  },
102
124
  sourceAudioDurationSeconds: {
103
125
  type: "array",
104
- description: "可选。dryRun 使用的音频素材时长秒数数组,顺序与 sourceAudios 一致;用于获得预估积分",
105
- items: { type: "number" },
126
+ title: "参考音频时长列表",
127
+ description: "可选。仅在 dryRun 预检时用于估算积分,单位为秒,顺序须与 sourceAudios 一致。",
128
+ items: {
129
+ type: "number",
130
+ title: "参考音频时长",
131
+ description: "对应参考音频的时长,单位为秒。",
132
+ },
106
133
  },
107
134
  size: {
108
135
  type: "string",
109
- description: "视频方向/比例。S2 支持横竖屏;S5/S6 还支持正方形、4:3、3:4(默认:1280x720)",
136
+ title: "输出尺寸",
137
+ description: "可选。视频的输出尺寸,默认 1280x720;可选择横屏、竖屏、正方形、4:3 或 3:4 尺寸。",
110
138
  enum: ["1280x720", "720x1280", "1024x1024", "1024x768", "768x1024"],
111
139
  },
112
140
  seconds: {
113
141
  type: "number",
114
- description: "视频时长(秒)。S2=10或15秒;S5极睿Sd专业版=5、10或15秒;S6极睿Ha专业版 standard/reference=5、10或15秒,edit 模式由输入视频决定",
115
- enum: [5, 10, 15],
142
+ title: "视频时长",
143
+ description: "可选。输出视频时长,单位为秒;SA 专业版支持 10 或 15 秒,SD 专业版和 HA 专业版标准/图片参考模式支持 5、10 或 15 秒,SD2.5 专业版支持 5、10、15、20、25 或 30 秒,H3 专业版支持 5、10 或 15 秒,HA 专业版视频编辑模式由输入视频决定。",
144
+ enum: [5, 10, 15, 20, 25, 30],
116
145
  },
117
146
  language: {
118
147
  type: "string",
119
- description: "视频的目标语言(默认:zh-CN)",
148
+ title: "提示词优化语言",
149
+ description: "可选。用于理解和优化视频提示词的语言区域代码,默认 zh-CN。",
120
150
  enum: ["zh-CN", "en-US", "ja-JP", "ko-KR", "es-ES", "fr-FR", "th-TH", "id-ID", "de-DE", "pt-PT", "bn-BD", "ru-RU", "ar-SA", "zh-HK", "hi-IN"],
121
151
  },
122
152
  count: {
123
153
  type: "number",
124
- description: "生成的视频数量(1-12,默认:1)",
154
+ title: "生成数量",
155
+ description: "可选。需要生成的视频数量,范围 1–12,默认 1。",
125
156
  minimum: 1,
126
157
  maximum: 12,
127
158
  },
159
+ model: {
160
+ type: "string",
161
+ title: "生成模型",
162
+ description: "可选。SA=极睿 Sa 专业版(默认),SD=极睿 Sd 专业版,SD25=极睿 SD2.5 专业版,HA=极睿 Ha 专业版,H3=极睿 H3 专业版;SD、SD25、HA、H3 仅限 VIP 用户使用。",
163
+ enum: ["SA", "SD", "SD25", "HA", "H3"],
164
+ },
128
165
  route: {
129
166
  type: "string",
130
- description: "使用的生成引擎:S2=极睿Sa专业版(默认)、S5=极睿Sd专业版、S6=极睿Ha专业版。注意:S5、S6 仅限 VIP 用户使用",
131
- enum: ["S2", "S5", "S6"],
167
+ title: "兼容模型参数",
168
+ description: "已废弃的兼容参数,仅供旧客户端使用;新调用请使用 model 选择 SA、SD、SD25、HA 或 H3 专业版。",
169
+ enum: ["S2", "S5", "S6", "S7", "S8"],
170
+ deprecated: true,
132
171
  },
133
172
  taskMode: {
134
173
  type: "string",
135
- description: "引擎模式。S5: allinone=全能参考、firstlast=首尾帧;S6: standard=标准模式、reference=图片参考、edit=视频编辑",
174
+ title: "生成模式",
175
+ description: "可选。allinone=全能参考,firstlast=首尾帧,standard=标准模式,reference=图片参考,edit=视频编辑;可用模式取决于所选模型。",
136
176
  enum: ["allinone", "firstlast", "standard", "reference", "edit"],
137
177
  },
138
178
  resolution: {
139
179
  type: "string",
140
- description: "S5/S6 输出清晰度(默认:720p)",
180
+ title: "输出清晰度",
181
+ description: "可选。SD 或 HA 专业版可选 720p 或 1080p;SD2.5 专业版仅支持 480p 或 720p;默认 720p。",
141
182
  enum: ["720p", "1080p"],
142
183
  },
143
184
  ratio: {
144
185
  type: "string",
145
- description: "S6 图片参考或标准文生视频模式的宽高比,例如 9:16、16:9、1:1、4:3、3:4",
186
+ title: "画面比例",
187
+ description: "可选。HA 专业版图片参考或标准文生视频模式的画面比例,可选 16:9、9:16、1:1、4:3 或 3:4。",
146
188
  enum: ["16:9", "9:16", "1:1", "4:3", "3:4"],
147
189
  },
148
190
  generateAudio: {
149
191
  type: "boolean",
150
- description: "S5 极睿Sd专业版是否生成音频(默认:true)",
192
+ title: "生成音频",
193
+ description: "可选。SD / SD2.5 专业版是否同时生成音频,默认 true。",
151
194
  },
152
195
  webSearchEnabled: {
153
196
  type: "boolean",
154
- description: "S5 极睿Sd专业版是否启用 Agent 增强(默认:false)",
197
+ title: "启用内容增强",
198
+ description: "可选。SD 专业版是否启用联网内容增强,默认 false;SD2.5 专业版不支持。",
155
199
  },
156
200
  dryRun: {
157
201
  type: "boolean",
158
- description: "仅执行参数、素材、权限和积分预检,不创建任务、不扣费。适合在正式创建前确认 S5/S6 参数是否可用",
202
+ title: "仅预检",
203
+ description: "可选。设为 true 时仅校验参数、素材、权限并估算积分,不创建任务且不扣费;默认 false。",
159
204
  },
160
205
  },
161
206
  required: ["prompt"],
@@ -163,86 +208,95 @@ export function createServer(token, apiBaseUrl) {
163
208
  },
164
209
  outputSchema: {
165
210
  type: "object",
211
+ title: "AI 视频任务创建结果",
212
+ description: "返回任务标识、所用配置、积分信息和后续查询提示;预检或失败时部分字段可能不返回。",
166
213
  properties: {
167
- success: { type: "boolean" },
168
- taskId: { type: "number" },
169
- route: { type: "string" },
170
- engineName: { type: "string" },
171
- taskMode: { type: "string" },
172
- videoCount: { type: "number" },
173
- dryRun: { type: "boolean" },
174
- estimatedCredits: { type: "number" },
175
- estimateSource: { type: "string" },
176
- remainingCredits: { type: "number" },
177
- statusHint: { type: "string" },
214
+ success: { type: "boolean", title: "是否成功", description: "表示任务创建或预检请求是否成功。" },
215
+ taskId: { type: "number", title: "任务 ID", description: "创建成功后返回的视频任务 ID;dryRun 或失败时可能不返回,请用于调用 get_video_status。" },
216
+ model: { type: "string", title: "生成模型", description: "实际使用的模型短码:SA、SD、SD25、HA 或 H3。" },
217
+ route: { type: "string", title: "兼容模型标识", description: "为旧客户端返回的兼容模型标识。" },
218
+ engineName: { type: "string", title: "模型名称", description: "实际使用的极睿专业版模型名称。" },
219
+ usedRouteAlias: { type: "boolean", title: "是否使用兼容参数", description: "表示本次调用是否通过已废弃的 route 参数选择模型。" },
220
+ taskMode: { type: "string", title: "生成模式", description: "本次任务实际使用的生成模式。" },
221
+ videoCount: { type: "number", title: "视频数量", description: "本次任务计划生成的视频数量。" },
222
+ dryRun: { type: "boolean", title: "是否仅预检", description: "表示本次请求是否只进行了预检而未创建任务。" },
223
+ estimatedCredits: { type: "number", title: "预估积分", description: "完成本次任务预计需要的积分;无法估算时可能不返回。" },
224
+ estimateSource: { type: "string", title: "估算依据", description: "预估积分所依据的素材检测结果或时长提示。" },
225
+ remainingCredits: { type: "number", title: "剩余积分", description: "请求处理后的账户剩余积分。" },
226
+ statusHint: { type: "string", title: "后续操作提示", description: "提示是否应使用 taskId 调用 get_video_status 查询任务进度。" },
178
227
  validationWarnings: {
179
228
  type: "array",
180
- items: { type: "string" },
229
+ title: "预检警告",
230
+ description: "参数或素材预检产生的非阻断性提示列表。",
231
+ items: { type: "string", title: "预检警告项", description: "一条需要关注但不阻断请求的预检提示。" },
181
232
  },
182
- code: { type: "string" },
183
- message: { type: "string" },
233
+ code: { type: "string", title: "错误代码", description: "请求失败时返回的机器可读错误代码,成功时可能不返回。" },
234
+ message: { type: "string", title: "结果消息", description: "请求结果或失败原因的可读说明。" },
184
235
  },
185
236
  required: ["success"],
186
237
  },
187
238
  },
188
239
  {
189
240
  name: "get_video_status",
190
- title: "查询视频生成状态",
191
- description: "获取视频生成任务的状态,包括进度和视频 URL",
241
+ title: "查询 AI 视频生成结果",
242
+ description: "根据 create_video_task 返回的 taskId 查询 AI 视频生成进度和结果。任务未完成时请间隔一段时间后使用同一 taskId 再次调用;完成后返回视频链接,失败时返回原因。",
192
243
  inputSchema: {
193
244
  type: "object",
245
+ title: "AI 视频任务查询参数",
246
+ description: "指定需要查询的视频生成任务。",
194
247
  properties: {
195
248
  taskId: {
196
249
  type: "number",
197
- description: "视频生成任务 ID",
250
+ title: "任务 ID",
251
+ description: "必填。由 create_video_task 返回的视频任务 ID,请原样传入并用于后续轮询。",
198
252
  },
199
253
  },
200
254
  required: ["taskId"],
201
255
  },
202
256
  outputSchema: {
203
257
  type: "object",
258
+ title: "AI 视频生成状态与结果",
259
+ description: "返回任务进度、生成结果和错误信息;任务未完成时结果链接和完成时间可能为空。",
204
260
  properties: {
205
- success: { type: "boolean" },
206
- taskId: { type: "number" },
207
- status: { type: "string" },
208
- progress: { type: "number" },
209
- totalVideos: { type: "number" },
210
- completedVideos: { type: "number" },
211
- estimatedCredits: { type: "number" },
212
- chargedVideos: { type: "number" },
213
- actualRoute: { type: "string" },
214
- taskMode: { type: ["string", "null"] },
215
- retryCount: { type: "number" },
216
- statusHint: { type: "string" },
217
- createdAt: { type: ["string", "null"] },
218
- updatedAt: { type: ["string", "null"] },
219
- completedAt: { type: ["string", "null"] },
220
- errorMessage: { type: "string" },
221
- errorCode: { type: "string" },
222
- errorCategory: { type: "string" },
223
- retryable: { type: "boolean" },
224
- code: { type: "string" },
225
- message: { type: "string" },
226
- route: { type: "string" },
227
- engineName: { type: "string" },
261
+ success: { type: "boolean", title: "是否成功", description: "表示查询请求是否成功。" },
262
+ taskId: { type: "number", title: "任务 ID", description: "当前查询的视频任务 ID。" },
263
+ status: { type: "string", title: "任务状态", description: "视频任务当前的处理状态;未完成时应根据 statusHint 稍后重试。" },
264
+ progress: { type: "number", title: "总体进度", description: "任务总体处理进度,通常为 0–100 的数值。" },
265
+ totalVideos: { type: "number", title: "视频总数", description: "任务计划生成的视频总数。" },
266
+ completedVideos: { type: "number", title: "已完成数量", description: "当前已经完成生成的视频数量。" },
267
+ estimatedCredits: { type: "number", title: "预估积分", description: "任务预计消耗的积分;无法估算时可能不返回。" },
268
+ taskMode: { type: ["string", "null"], title: "生成模式", description: "任务使用的生成模式;旧任务或尚未确定时可能为空。" },
269
+ statusHint: { type: "string", title: "状态提示", description: "面向用户的当前状态和下一步轮询提示。" },
270
+ createdAt: { type: ["string", "null"], title: "创建时间", description: "任务创建时间;不可用时为空。" },
271
+ updatedAt: { type: ["string", "null"], title: "更新时间", description: "任务最近更新时间;不可用时为空。" },
272
+ completedAt: { type: ["string", "null"], title: "完成时间", description: "任务完成时间;未完成时为空。" },
273
+ errorMessage: { type: "string", title: "错误信息", description: "任务失败时的可读原因,未失败时可能不返回。" },
274
+ errorCode: { type: "string", title: "错误代码", description: "任务失败时的机器可读错误代码,未失败时可能不返回。" },
275
+ errorCategory: { type: "string", title: "错误分类", description: "任务失败时的错误类别,未失败时可能不返回。" },
276
+ retryable: { type: "boolean", title: "是否可重试", description: "表示失败后是否建议重新发起任务,未失败时可能不返回。" },
277
+ code: { type: "string", title: "查询错误代码", description: "查询请求失败时的机器可读错误代码,成功时可能不返回。" },
278
+ message: { type: "string", title: "查询消息", description: "查询结果或失败原因的可读说明。" },
279
+ route: { type: "string", title: "兼容模型标识", description: "任务对应的兼容模型标识。" },
280
+ engineName: { type: "string", title: "模型名称", description: "任务实际使用的极睿专业版模型名称。" },
228
281
  videos: {
229
282
  type: "array",
283
+ title: "视频结果列表",
284
+ description: "每个待生成视频的状态和结果;单项未完成时链接可能为空。",
230
285
  items: {
231
286
  type: "object",
287
+ title: "视频结果",
288
+ description: "一个视频生成结果及其状态、链接、时间和错误信息。",
232
289
  properties: {
233
- id: { type: "number" },
234
- status: { type: "string" },
235
- videoUrl: { type: ["string", "null"] },
236
- thumbnailUrl: { type: ["string", "null"] },
237
- errorMessage: { type: ["string", "null"] },
238
- errorCode: { type: ["string", "null"] },
239
- errorCategory: { type: ["string", "null"] },
240
- retryable: { type: ["boolean", "null"] },
241
- actualRoute: { type: ["string", "null"] },
242
- retryCount: { type: "number" },
243
- creditsCharged: { type: "boolean" },
244
- createdAt: { type: ["string", "null"] },
245
- completedAt: { type: ["string", "null"] },
290
+ id: { type: "number", title: "视频结果 ID", description: "该视频结果在任务中的唯一标识。" },
291
+ status: { type: "string", title: "视频状态", description: "该视频当前的生成状态。" },
292
+ videoUrl: { type: ["string", "null"], title: "视频 URL", description: "生成完成后的视频访问 URL;未完成或失败时为空。" },
293
+ thumbnailUrl: { type: ["string", "null"], title: "缩略图 URL", description: "视频缩略图访问 URL;尚未生成时为空。" },
294
+ errorMessage: { type: ["string", "null"], title: "错误信息", description: "该视频生成失败时的可读原因;未失败时为空。" },
295
+ errorCode: { type: ["string", "null"], title: "错误代码", description: "该视频生成失败时的机器可读错误代码;未失败时为空。" },
296
+ errorCategory: { type: ["string", "null"], title: "错误分类", description: "该视频生成失败时的错误类别;未失败时为空。" },
297
+ retryable: { type: ["boolean", "null"], title: "是否可重试", description: "表示该视频失败后是否建议重新创建任务;未失败时为空。" },
298
+ createdAt: { type: ["string", "null"], title: "创建时间", description: "该视频结果的创建时间;不可用时为空。" },
299
+ completedAt: { type: ["string", "null"], title: "完成时间", description: "该视频的完成时间;未完成时为空。" },
246
300
  },
247
301
  required: ["id", "status", "videoUrl", "thumbnailUrl", "errorMessage"],
248
302
  },
@@ -253,70 +307,82 @@ export function createServer(token, apiBaseUrl) {
253
307
  },
254
308
  {
255
309
  name: "create_video_analysis_task",
256
- title: "创建视频拆解任务",
257
- description: "创建视频拆解任务,将视频拆解为脚本、风格、卖点话术、爆款公式,并生成用于视频创作的提示词。" +
258
- "支持本地文件路径或公网 URL。",
310
+ title: "分析参考视频内容",
311
+ description: "分析本地视频或公网 URL,提取脚本、风格、卖点、创作公式并生成视频创作提示词;本工具不会生成最终视频。" +
312
+ "本工具返回 analysisId,创建后请调用 get_video_analysis_status 并原样传入 analysisId,轮询分析进度和结果。",
259
313
  inputSchema: {
260
314
  type: "object",
315
+ title: "参考视频分析参数",
316
+ description: "指定需要分析的参考视频。",
261
317
  properties: {
262
318
  videoFile: {
263
319
  type: "string",
264
- description: "要分析的视频文件,可以是本地文件路径或公网 URL",
320
+ title: "参考视频",
321
+ description: "必填。需要分析的视频本地文件路径或公网 URL。",
265
322
  },
266
323
  },
267
324
  required: ["videoFile"],
268
325
  },
269
326
  outputSchema: {
270
327
  type: "object",
328
+ title: "视频分析任务创建结果",
329
+ description: "返回分析任务标识、积分信息和后续查询提示;失败时部分字段可能不返回。",
271
330
  properties: {
272
- success: { type: "boolean" },
273
- analysisId: { type: "number" },
274
- creditsUsed: { type: "number" },
275
- remainingCredits: { type: "number" },
276
- statusHint: { type: "string" },
277
- code: { type: "string" },
278
- message: { type: "string" },
331
+ success: { type: "boolean", title: "是否成功", description: "表示视频分析任务是否创建成功。" },
332
+ analysisId: { type: "number", title: "分析任务 ID", description: "创建成功后返回的分析任务 ID,请用于调用 get_video_analysis_status。" },
333
+ creditsUsed: { type: "number", title: "消耗积分", description: "创建本次视频分析任务实际扣除的积分。" },
334
+ remainingCredits: { type: "number", title: "剩余积分", description: "任务创建后的账户剩余积分。" },
335
+ statusHint: { type: "string", title: "后续操作提示", description: "提示使用 analysisId 调用 get_video_analysis_status 查询分析进度。" },
336
+ code: { type: "string", title: "错误代码", description: "创建失败时返回的机器可读错误代码,成功时可能不返回。" },
337
+ message: { type: "string", title: "结果消息", description: "创建结果或失败原因的可读说明。" },
279
338
  },
280
339
  required: ["success"],
281
340
  },
282
341
  },
283
342
  {
284
343
  name: "get_video_analysis_status",
285
- title: "查询视频拆解状态",
286
- description: "获取视频拆解任务的状态和结果",
344
+ title: "查询视频分析结果",
345
+ description: "根据 create_video_analysis_task 返回的 analysisId 查询分析进度和结构化结果。任务未完成时请间隔一段时间后使用同一 analysisId 再次调用。",
287
346
  inputSchema: {
288
347
  type: "object",
348
+ title: "视频分析任务查询参数",
349
+ description: "指定需要查询的视频分析任务。",
289
350
  properties: {
290
351
  analysisId: {
291
352
  type: "number",
292
- description: "视频拆解任务 ID",
353
+ title: "分析任务 ID",
354
+ description: "必填。由 create_video_analysis_task 返回的分析任务 ID,请原样传入并用于后续轮询。",
293
355
  },
294
356
  },
295
357
  required: ["analysisId"],
296
358
  },
297
359
  outputSchema: {
298
360
  type: "object",
361
+ title: "视频分析状态与结果",
362
+ description: "返回分析进度和结构化内容;任务未完成或失败时分析内容可能为空。",
299
363
  properties: {
300
- success: { type: "boolean" },
301
- analysisId: { type: "number" },
302
- status: { type: "string" },
303
- conversionProgress: { type: ["number", "null"] },
304
- script: { type: ["string", "null"] },
305
- style: { type: ["string", "null"] },
306
- sellingPoints: { type: ["string", "null"] },
307
- viralFormula: { type: ["string", "null"] },
308
- generatedPrompt: { type: ["string", "null"] },
309
- errorMessage: { type: ["string", "null"] },
310
- originalVideoUrl: { type: ["string", "null"] },
311
- videoFileName: { type: ["string", "null"] },
312
- code: { type: "string" },
313
- message: { type: "string" },
364
+ success: { type: "boolean", title: "是否成功", description: "表示查询请求是否成功。" },
365
+ analysisId: { type: "number", title: "分析任务 ID", description: "当前查询的视频分析任务 ID。" },
366
+ status: { type: "string", title: "分析状态", description: "视频分析任务当前的处理状态;未完成时应稍后继续查询。" },
367
+ conversionProgress: { type: ["number", "null"], title: "处理进度", description: "视频转换和分析进度,通常为 0–100;尚未开始时可能为空。" },
368
+ script: { type: ["string", "null"], title: "视频脚本", description: "从参考视频中提取的脚本内容;分析未完成时为空。" },
369
+ style: { type: ["string", "null"], title: "视频风格", description: "识别出的视觉、叙事和表达风格;分析未完成时为空。" },
370
+ sellingPoints: { type: ["string", "null"], title: "卖点分析", description: "从视频中识别出的商品或内容卖点;分析未完成时为空。" },
371
+ viralFormula: { type: ["string", "null"], title: "创作公式", description: "总结的视频结构和内容创作公式;分析未完成时为空。" },
372
+ generatedPrompt: { type: ["string", "null"], title: "生成提示词", description: "基于分析结果生成的后续视频创作提示词;分析未完成时为空。" },
373
+ errorMessage: { type: ["string", "null"], title: "错误信息", description: "分析失败时的可读原因;未失败时为空。" },
374
+ originalVideoUrl: { type: ["string", "null"], title: "原视频 URL", description: "本次分析所使用的视频访问 URL;不可用时为空。" },
375
+ videoFileName: { type: ["string", "null"], title: "视频文件名", description: "参考视频的文件名;不可用时为空。" },
376
+ code: { type: "string", title: "查询错误代码", description: "查询请求失败时返回的机器可读错误代码,成功时可能不返回。" },
377
+ message: { type: "string", title: "查询消息", description: "查询结果或失败原因的可读说明。" },
314
378
  },
315
379
  required: ["success"],
316
380
  },
317
381
  },
382
+ ...MCP_CREATIVE_TOOLS,
318
383
  ];
319
- return { tools };
384
+ const filtered = options.filterTools ? options.filterTools(allTools) : allTools;
385
+ return { tools: filtered };
320
386
  });
321
387
  /**
322
388
  * Handle tool calls
@@ -335,6 +401,14 @@ export function createServer(token, apiBaseUrl) {
335
401
  return await handleCreateVideoAnalysisTask(apiBaseUrl, token, args);
336
402
  case "get_video_analysis_status":
337
403
  return await handleGetVideoAnalysisStatus(apiBaseUrl, token, args);
404
+ case "create_motion_video_remake_task":
405
+ return await handleCreateMotionVideoRemakeTask(apiBaseUrl, token, args);
406
+ case "get_motion_video_remake_status":
407
+ return await handleGetMotionVideoRemakeStatus(apiBaseUrl, token, args);
408
+ case "create_ecommerce_storyboard_task":
409
+ return await handleCreateEcommerceStoryboardTask(apiBaseUrl, token, args);
410
+ case "get_ecommerce_storyboard_status":
411
+ return await handleGetEcommerceStoryboardStatus(apiBaseUrl, token, args);
338
412
  default:
339
413
  throw new Error(`Unknown tool: ${name}`);
340
414
  }
@@ -400,7 +474,7 @@ async function handleGetUserInfo(apiBaseUrl, token) {
400
474
  * 处理文件输入:支持本地文件路径或公网 URL
401
475
  * 对于本地文件,读取并转换为 base64
402
476
  */
403
- async function processFileInput(fileInput, expectedType) {
477
+ async function processFileInput(fileInput, expectedType, maxBytes) {
404
478
  // 检查是否是公网 URL
405
479
  if (fileInput.startsWith("http://") || fileInput.startsWith("https://")) {
406
480
  return {
@@ -416,8 +490,6 @@ async function processFileInput(fileInput, expectedType) {
416
490
  if (!existsSync(filePath)) {
417
491
  throw new McpToolError(`文件不存在: ${filePath}`, "FILE_NOT_FOUND");
418
492
  }
419
- // 读取文件
420
- const buffer = readFileSync(filePath);
421
493
  // 获取文件扩展名并确定 MIME type
422
494
  const ext = extname(filePath).toLowerCase();
423
495
  let mimeType = "application/octet-stream";
@@ -440,12 +512,12 @@ async function processFileInput(fileInput, expectedType) {
440
512
  throw new McpToolError(`文件 ${basename(filePath)} 类型不匹配:当前是 ${mimeType},但该字段需要 ${expectedType} 文件。`, "INVALID_FILE_TYPE");
441
513
  }
442
514
  const size = statSync(filePath).size;
443
- if (expectedType === "video" && size > 50 * 1024 * 1024) {
444
- throw new McpToolError(`视频文件 ${basename(filePath)} 超过 50MB 限制。`, "MEDIA_FILE_TOO_LARGE");
445
- }
446
- if (expectedType === "audio" && size > 15 * 1024 * 1024) {
447
- throw new McpToolError(`音频文件 ${basename(filePath)} 超过 15MB 限制。`, "MEDIA_FILE_TOO_LARGE");
515
+ const effectiveMaxBytes = maxBytes ?? (expectedType === "video" ? 50 * 1024 * 1024 : expectedType === "audio" ? 15 * 1024 * 1024 : undefined);
516
+ if (effectiveMaxBytes !== undefined && size > effectiveMaxBytes) {
517
+ throw new McpToolError(`文件 ${basename(filePath)} 超过 ${Math.floor(effectiveMaxBytes / 1024 / 1024)}MB 限制。`, "MEDIA_FILE_TOO_LARGE");
448
518
  }
519
+ // 通过大小预检后再读取,避免将超限文件装入内存。
520
+ const buffer = readFileSync(filePath);
449
521
  // 转换为 base64
450
522
  const base64 = `data:${mimeType};base64,${buffer.toString("base64")}`;
451
523
  return {
@@ -470,28 +542,43 @@ function hasItems(value) {
470
542
  function itemCount(value) {
471
543
  return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0).length : 0;
472
544
  }
473
- function normalizeRoute(value) {
474
- const route = typeof value === "string" ? value : "S2";
545
+ function normalizeRoute(args) {
546
+ // 优先 model;若 model 未传或不是 SA/SD/HA,再看 route(route 现在是 deprecated alias)
547
+ if (args && typeof args === "object" && isMcpModel(args.model)) {
548
+ const m = args.model;
549
+ return {
550
+ route: MCP_VIDEO_MODELS[m].route,
551
+ model: m,
552
+ usedRouteAlias: false,
553
+ };
554
+ }
555
+ const raw = args && typeof args === "object" ? args.route : undefined;
556
+ const route = typeof raw === "string" ? raw : "S2";
475
557
  if (!isMcpRoute(route)) {
476
- throw new McpToolError("route 只能是 S2、S5 或 S6。S2=极睿Sa专业版,S5=极睿Sd专业版,S6=极睿Ha专业版。", "INVALID_ROUTE");
558
+ throw new McpToolError("model 必须是 SA、SD、SD25、HA 或 H3;route 只接受 S2、S5、S6、S7、S8(已废弃),请改用 model。", "INVALID_ENGINE");
477
559
  }
478
- return route;
560
+ const m = getMcpModelForRoute(route);
561
+ return {
562
+ route,
563
+ model: m,
564
+ usedRouteAlias: true,
565
+ };
479
566
  }
480
567
  function normalizeTaskMode(route, value) {
481
568
  const mode = typeof value === "string" ? value : getMcpDefaultTaskMode(route);
482
569
  if (route === "S2") {
483
570
  if (value !== undefined) {
484
- throw new McpToolError("taskMode 仅适用于极睿Sd专业版或极睿Ha专业版;S2 极睿Sa专业版无需传 taskMode。", "UNSUPPORTED_FIELD", route);
571
+ throw new McpToolError("taskMode 仅适用于极睿SD专业版或极睿HA专业版;S2 极睿SA专业版无需传 taskMode。", "UNSUPPORTED_FIELD", route);
485
572
  }
486
573
  return getMcpDefaultTaskMode(route);
487
574
  }
488
575
  if (!isValidMcpTaskMode(route, mode)) {
489
- const allowedModes = route === "S5" ? "allinone 或 firstlast" : "standard、reference 或 edit";
576
+ const allowedModes = route === "S5" || route === "S8" ? "allinone 或 firstlast" : route === "S7" ? "allinone、firstlast 或 reference" : "standard、reference 或 edit";
490
577
  throw new McpToolError(`${getMcpEngineName(route)} 的 taskMode 只能是 ${allowedModes}。`, "INVALID_TASK_MODE", route);
491
578
  }
492
579
  return mode;
493
580
  }
494
- function validateCreateVideoArgs(args) {
581
+ export function validateCreateVideoArgs(args) {
495
582
  if (!args || typeof args !== "object") {
496
583
  throw new McpToolError("请提供视频生成参数。");
497
584
  }
@@ -501,15 +588,28 @@ function validateCreateVideoArgs(args) {
501
588
  if (args.count !== undefined && (!Number.isInteger(args.count) || args.count < 1 || args.count > 12)) {
502
589
  throw new McpToolError("count 必须是 1-12 的整数。", "INVALID_COUNT");
503
590
  }
504
- const route = normalizeRoute(args.route);
591
+ const norm = normalizeRoute(args);
592
+ const route = norm.route;
505
593
  const taskMode = normalizeTaskMode(route, args.taskMode);
506
594
  const resolution = typeof args.resolution === "string" ? args.resolution : "720p";
507
595
  const dryRun = args.dryRun === true;
596
+ if (route === "S8" && resolution !== "480p" && resolution !== "720p") {
597
+ throw new McpToolError(`${getMcpEngineName(route)} 仅支持 480p 或 720p。`, "INVALID_RESOLUTION", route);
598
+ }
508
599
  if ((route === "S5" || route === "S6") && resolution !== "720p" && resolution !== "1080p") {
509
600
  throw new McpToolError(`${getMcpEngineName(route)} 仅支持 720p 或 1080p。`, "INVALID_RESOLUTION", route);
510
601
  }
511
602
  if (route === "S2" && args.seconds === 5) {
512
- throw new McpToolError("极睿Sa专业版不支持 5 秒视频,请使用 10 或 15 秒。", "INVALID_SECONDS", route);
603
+ throw new McpToolError("极睿SA专业版不支持 5 秒视频,请使用 10 或 15 秒。", "INVALID_SECONDS", route);
604
+ }
605
+ // S5/S6/S7/S8 的 seconds 白名单
606
+ if (route === "S5" || route === "S6" || route === "S7" || route === "S8") {
607
+ const allowedSeconds = route === "S8" ? [5, 10, 15, 20, 25, 30] : [5, 10, 15];
608
+ if (args.seconds !== undefined && !allowedSeconds.includes(args.seconds)) {
609
+ throw new McpToolError(route === "S8"
610
+ ? `${getMcpEngineName(route)} 视频时长仅支持 5、10、15、20、25、30 秒。`
611
+ : `${getMcpEngineName(route)} 视频时长仅支持 5、10、15 秒。`, "INVALID_SECONDS", route);
612
+ }
513
613
  }
514
614
  if (route === "S2") {
515
615
  const unsupported = [
@@ -522,36 +622,47 @@ function validateCreateVideoArgs(args) {
522
622
  args.webSearchEnabled !== undefined ? "webSearchEnabled" : undefined,
523
623
  ].filter(Boolean);
524
624
  if (unsupported.length > 0) {
525
- throw new McpToolError(`这些字段不适用于极睿Sa专业版:${unsupported.join("、")}。S2 单图请使用 sourceImage。`, "UNSUPPORTED_FIELD", route);
625
+ throw new McpToolError(`这些字段不适用于极睿SA专业版:${unsupported.join("、")}。S2 单图请使用 sourceImage。`, "UNSUPPORTED_FIELD", route);
526
626
  }
527
627
  }
528
- if (route === "S5") {
628
+ if (route === "S5" || route === "S8") {
629
+ // S8 = SD2.5 独立通道:素材上限放宽(30 图 / 10 视频 / 10 音频),支持纯音频参考,
630
+ // 不支持联网搜索;文案与上限按通道区分。
631
+ const isSd25 = route === "S8";
632
+ const sdEngine = getMcpEngineName(route);
529
633
  if (args.sourceImage !== undefined) {
530
- throw new McpToolError("sourceImage 不适用于极睿Sd专业版;请使用 sourceImages 传参考图或首尾帧。", "UNSUPPORTED_FIELD", route);
634
+ throw new McpToolError(`sourceImage 不适用于${sdEngine};请使用 sourceImages 传参考图或首尾帧。`, "UNSUPPORTED_FIELD", route);
531
635
  }
532
636
  if (args.ratio !== undefined) {
533
- throw new McpToolError("ratio 仅适用于极睿Ha专业版;极睿Sd专业版请使用 size 控制画面比例。", "UNSUPPORTED_FIELD", route);
637
+ throw new McpToolError(`ratio 仅适用于极睿HA专业版;${sdEngine}请使用 size 控制画面比例。`, "UNSUPPORTED_FIELD", route);
638
+ }
639
+ if (isSd25 && args.webSearchEnabled !== undefined) {
640
+ throw new McpToolError(`webSearchEnabled 不适用于${sdEngine}(SD2.5 不支持联网搜索)。`, "UNSUPPORTED_FIELD", route);
534
641
  }
535
642
  if (taskMode === "firstlast" && !hasItems(args.sourceImages)) {
536
- throw new McpToolError("极睿Sd专业版首尾帧模式需要在 sourceImages 中提供至少 1 张首帧图片。", "MISSING_SOURCE_IMAGE", route);
643
+ throw new McpToolError(`${sdEngine}首尾帧模式需要在 sourceImages 中提供至少 1 张首帧图片。`, "MISSING_SOURCE_IMAGE", route);
537
644
  }
538
645
  if (taskMode === "firstlast" && (hasItems(args.sourceVideos) || hasItems(args.sourceAudios))) {
539
- throw new McpToolError("sourceVideos/sourceAudios 仅适用于极睿Sd专业版全能参考模式;首尾帧模式只使用 sourceImages。", "UNSUPPORTED_FIELD", route);
646
+ throw new McpToolError(`sourceVideos/sourceAudios 仅适用于${sdEngine}全能参考模式;首尾帧模式只使用 sourceImages。`, "UNSUPPORTED_FIELD", route);
540
647
  }
541
648
  if (taskMode === "firstlast" && itemCount(args.sourceImages) > 2) {
542
- throw new McpToolError("极睿Sd专业版首尾帧模式最多支持 2 张图片。", "MEDIA_COUNT_EXCEEDED", route);
649
+ throw new McpToolError(`${sdEngine}首尾帧模式最多支持 2 张图片。`, "MEDIA_COUNT_EXCEEDED", route);
543
650
  }
544
- if (taskMode === "allinone" && itemCount(args.sourceImages) > 9) {
545
- throw new McpToolError("极睿Sd专业版全能参考模式最多支持 9 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
651
+ const maxImages = isSd25 ? 30 : 9;
652
+ const maxVideos = isSd25 ? 10 : 3;
653
+ const maxAudios = isSd25 ? 10 : 3;
654
+ if (taskMode === "allinone" && itemCount(args.sourceImages) > maxImages) {
655
+ throw new McpToolError(`${sdEngine}全能参考模式最多支持 ${maxImages} 张参考图片。`, "MEDIA_COUNT_EXCEEDED", route);
546
656
  }
547
- if (taskMode === "allinone" && itemCount(args.sourceVideos) > 3) {
548
- throw new McpToolError("极睿Sd专业版全能参考模式最多支持 3 个参考视频。", "MEDIA_COUNT_EXCEEDED", route);
657
+ if (taskMode === "allinone" && itemCount(args.sourceVideos) > maxVideos) {
658
+ throw new McpToolError(`${sdEngine}全能参考模式最多支持 ${maxVideos} 个参考视频。`, "MEDIA_COUNT_EXCEEDED", route);
549
659
  }
550
- if (taskMode === "allinone" && itemCount(args.sourceAudios) > 3) {
551
- throw new McpToolError("极睿Sd专业版全能参考模式最多支持 3 个参考音频。", "MEDIA_COUNT_EXCEEDED", route);
660
+ if (taskMode === "allinone" && itemCount(args.sourceAudios) > maxAudios) {
661
+ throw new McpToolError(`${sdEngine}全能参考模式最多支持 ${maxAudios} 个参考音频。`, "MEDIA_COUNT_EXCEEDED", route);
552
662
  }
553
- if (taskMode === "allinone" && hasItems(args.sourceAudios) && !hasItems(args.sourceImages) && !hasItems(args.sourceVideos)) {
554
- throw new McpToolError("极睿Sd专业版全能参考模式中,音频素材必须搭配至少一张参考图片或一个参考视频。", "MISSING_VISUAL_REFERENCE", route);
663
+ // 纯音频参考:S5 2.0 要求音频搭配图片/视频;S8(SD2.5)支持纯音频参考,豁免
664
+ if (!isSd25 && taskMode === "allinone" && hasItems(args.sourceAudios) && !hasItems(args.sourceImages) && !hasItems(args.sourceVideos)) {
665
+ throw new McpToolError(`${sdEngine}全能参考模式中,音频素材必须搭配至少一张参考图片或一个参考视频。`, "MISSING_VISUAL_REFERENCE", route);
555
666
  }
556
667
  }
557
668
  if (route === "S6") {
@@ -561,32 +672,66 @@ function validateCreateVideoArgs(args) {
561
672
  args.webSearchEnabled !== undefined ? "webSearchEnabled" : undefined,
562
673
  ].filter(Boolean);
563
674
  if (unsupported.length > 0) {
564
- throw new McpToolError(`这些字段仅适用于极睿Sd专业版,不能用于极睿Ha专业版:${unsupported.join("、")}。`, "UNSUPPORTED_FIELD", route);
675
+ throw new McpToolError(`这些字段仅适用于极睿SD专业版,不能用于极睿HA专业版:${unsupported.join("、")}。`, "UNSUPPORTED_FIELD", route);
565
676
  }
566
677
  if (taskMode === "standard" && (hasItems(args.sourceImages) || hasItems(args.sourceVideos))) {
567
- throw new McpToolError("极睿Ha专业版标准模式只支持 sourceImage 作为单图输入;多图请使用 reference 模式,视频请使用 edit 模式。", "UNSUPPORTED_FIELD", route);
678
+ throw new McpToolError("极睿HA专业版标准模式只支持 sourceImage 作为单图输入;多图请使用 reference 模式,视频请使用 edit 模式。", "UNSUPPORTED_FIELD", route);
568
679
  }
569
680
  if ((taskMode === "reference" || taskMode === "edit") && args.sourceImage !== undefined) {
570
- throw new McpToolError("sourceImage 仅适用于极睿Ha专业版标准模式;reference/edit 模式请使用 sourceImages。", "UNSUPPORTED_FIELD", route);
681
+ throw new McpToolError("sourceImage 仅适用于极睿HA专业版标准模式;reference/edit 模式请使用 sourceImages。", "UNSUPPORTED_FIELD", route);
571
682
  }
572
683
  if (taskMode === "reference" && !hasItems(args.sourceImages)) {
573
- throw new McpToolError("极睿Ha专业版图片参考模式需要在 sourceImages 中提供至少 1 张参考图片。", "MISSING_SOURCE_IMAGE", route);
684
+ throw new McpToolError("极睿HA专业版图片参考模式需要在 sourceImages 中提供至少 1 张参考图片。", "MISSING_SOURCE_IMAGE", route);
574
685
  }
575
686
  if (taskMode === "edit" && !hasItems(args.sourceVideos)) {
576
- throw new McpToolError("极睿Ha专业版视频编辑模式需要在 sourceVideos 中提供至少 1 个参考视频。", "MISSING_SOURCE_VIDEO", route);
687
+ throw new McpToolError("极睿HA专业版视频编辑模式需要在 sourceVideos 中提供至少 1 个参考视频。", "MISSING_SOURCE_VIDEO", route);
577
688
  }
578
689
  if (taskMode === "reference" && itemCount(args.sourceImages) > 9) {
579
- throw new McpToolError("极睿Ha专业版图片参考模式最多支持 9 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
690
+ throw new McpToolError("极睿HA专业版图片参考模式最多支持 9 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
580
691
  }
581
692
  if (taskMode === "edit" && itemCount(args.sourceVideos) > 1) {
582
- throw new McpToolError("极睿Ha专业版视频编辑模式最多支持 1 个参考视频。", "MEDIA_COUNT_EXCEEDED", route);
693
+ throw new McpToolError("极睿HA专业版视频编辑模式最多支持 1 个参考视频。", "MEDIA_COUNT_EXCEEDED", route);
583
694
  }
584
695
  if (taskMode === "edit" && itemCount(args.sourceImages) > 5) {
585
- throw new McpToolError("极睿Ha专业版视频编辑模式最多支持 5 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
696
+ throw new McpToolError("极睿HA专业版视频编辑模式最多支持 5 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
697
+ }
698
+ }
699
+ if (route === "S7") {
700
+ // S7 = H3(MiniMax V2):固定 2K 输出;i2va/r2va 互斥;seconds 白名单 [5,10,15](已在上方统一校验)。
701
+ if (args.resolution !== undefined) {
702
+ throw new McpToolError(`${getMcpEngineName(route)}固定 2K 输出,无需传 resolution。`, "UNSUPPORTED_FIELD", route);
703
+ }
704
+ if (args.sourceImage !== undefined) {
705
+ throw new McpToolError(`sourceImage 不适用于${getMcpEngineName(route)};请使用 sourceImages 传参考图或首尾帧。`, "UNSUPPORTED_FIELD", route);
706
+ }
707
+ if (args.generateAudio !== undefined || args.webSearchEnabled !== undefined) {
708
+ throw new McpToolError(`generateAudio/webSearchEnabled 不适用于${getMcpEngineName(route)}。`, "UNSUPPORTED_FIELD", route);
709
+ }
710
+ if (taskMode === "firstlast") {
711
+ if (!hasItems(args.sourceImages)) {
712
+ throw new McpToolError(`${getMcpEngineName(route)}首尾帧模式需要在 sourceImages 中提供至少 1 张首帧图片。`, "MISSING_SOURCE_IMAGE", route);
713
+ }
714
+ if (hasItems(args.sourceVideos) || hasItems(args.sourceAudios)) {
715
+ throw new McpToolError(`sourceVideos/sourceAudios 仅适用于${getMcpEngineName(route)}全能参考模式;首尾帧模式只使用 sourceImages。`, "UNSUPPORTED_FIELD", route);
716
+ }
717
+ if (itemCount(args.sourceImages) > 2) {
718
+ throw new McpToolError(`${getMcpEngineName(route)}首尾帧模式最多支持 2 张图片(首帧 + 尾帧)。`, "MEDIA_COUNT_EXCEEDED", route);
719
+ }
720
+ }
721
+ else if (hasItems(args.sourceAudios) &&
722
+ !hasItems(args.sourceImages) &&
723
+ !hasItems(args.sourceVideos)) {
724
+ // r2va 不允许仅音频(与网页端 / 服务端校验一致)
725
+ throw new McpToolError(`${getMcpEngineName(route)}全能参考模式中,音频素材必须搭配至少一张参考图片或一个参考视频。`, "MISSING_VISUAL_REFERENCE", route);
726
+ }
727
+ if (itemCount(args.sourceImages) > 9) {
728
+ throw new McpToolError(`${getMcpEngineName(route)}最多支持 9 张参考图片。`, "MEDIA_COUNT_EXCEEDED", route);
586
729
  }
587
730
  }
588
731
  return {
589
732
  route,
733
+ model: norm.model,
734
+ usedRouteAlias: norm.usedRouteAlias,
590
735
  taskMode,
591
736
  resolution,
592
737
  dryRun,
@@ -598,7 +743,10 @@ function validateCreateVideoArgs(args) {
598
743
  */
599
744
  async function handleCreateVideoTask(apiBaseUrl, token, args) {
600
745
  const normalized = validateCreateVideoArgs(args);
601
- const isProfessionalRoute = normalized.route === "S5" || normalized.route === "S6";
746
+ const isProfessionalRoute = normalized.route === "S5" ||
747
+ normalized.route === "S6" ||
748
+ normalized.route === "S7" ||
749
+ normalized.route === "S8";
602
750
  const requestBody = {
603
751
  token,
604
752
  prompt: args.prompt,
@@ -609,6 +757,7 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
609
757
  seconds: args.seconds || 10,
610
758
  language: args.language || "zh-CN",
611
759
  count: args.count || 1,
760
+ model: normalized.model,
612
761
  route: normalized.route,
613
762
  dryRun: normalized.dryRun,
614
763
  };
@@ -663,8 +812,10 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
663
812
  structuredContent: {
664
813
  success: response.success,
665
814
  taskId: response.taskId,
815
+ model: response.model ?? normalized.model,
666
816
  route,
667
817
  engineName,
818
+ usedRouteAlias: normalized.usedRouteAlias,
668
819
  taskMode,
669
820
  videoCount: response.videoCount,
670
821
  dryRun,
@@ -693,10 +844,7 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
693
844
  `- **进度**: ${response.progress}% (${response.completedVideos}/${response.totalVideos} 个视频)`,
694
845
  response.engineName ? `- **生成引擎**: ${response.engineName}` : undefined,
695
846
  response.taskMode ? `- **模式**: ${response.taskMode}` : undefined,
696
- response.actualRoute ? `- **实际线路**: ${response.actualRoute}` : undefined,
697
847
  typeof response.estimatedCredits === "number" ? `- **预估积分**: ${response.estimatedCredits}` : undefined,
698
- typeof response.chargedVideos === "number" ? `- **已扣费视频**: ${response.chargedVideos}/${response.totalVideos}` : undefined,
699
- typeof response.retryCount === "number" ? `- **重试次数**: ${response.retryCount}` : undefined,
700
848
  ``,
701
849
  ].filter((line) => line !== undefined);
702
850
  if (response.errorMessage) {
@@ -726,15 +874,6 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
726
874
  if (video.errorCode) {
727
875
  result.push(` 错误代码: ${video.errorCode}`);
728
876
  }
729
- if (video.actualRoute) {
730
- result.push(` 实际线路: ${video.actualRoute}`);
731
- }
732
- if (typeof video.retryCount === "number" && video.retryCount > 0) {
733
- result.push(` 重试次数: ${video.retryCount}`);
734
- }
735
- if (typeof video.creditsCharged === "boolean") {
736
- result.push(` 已扣费: ${video.creditsCharged ? "是" : "否"}`);
737
- }
738
877
  });
739
878
  }
740
879
  if (response.statusHint) {
@@ -757,9 +896,6 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
757
896
  route: response.route,
758
897
  engineName: response.engineName,
759
898
  taskMode: response.taskMode,
760
- actualRoute: response.actualRoute,
761
- retryCount: response.retryCount,
762
- chargedVideos: response.chargedVideos,
763
899
  estimatedCredits: response.estimatedCredits,
764
900
  statusHint: response.statusHint,
765
901
  createdAt: response.createdAt,
@@ -889,6 +1025,118 @@ async function handleGetVideoAnalysisStatus(apiBaseUrl, token, args) {
889
1025
  },
890
1026
  };
891
1027
  }
1028
+ function creativeToolResult(output, lines) {
1029
+ return {
1030
+ content: [{ type: "text", text: lines.join("\n") }],
1031
+ structuredContent: output,
1032
+ };
1033
+ }
1034
+ async function handleCreateMotionVideoRemakeTask(apiBaseUrl, token, args) {
1035
+ if (!args || typeof args.sourceImage !== "string" || typeof args.referenceVideo !== "string") {
1036
+ throw new McpToolError("sourceImage 和 referenceVideo 不能为空。", "MISSING_PARAMETERS");
1037
+ }
1038
+ const sourceImage = await processFileInput(args.sourceImage, "image", 5 * 1024 * 1024);
1039
+ const referenceVideo = await processFileInput(args.referenceVideo, "video", 100 * 1024 * 1024);
1040
+ const response = await callRestApi(apiBaseUrl, "/mcp/motion-remake/create", {
1041
+ token,
1042
+ sourceImage: sourceImage.data,
1043
+ referenceVideo: referenceVideo.data,
1044
+ mode: args.mode,
1045
+ quality: args.quality,
1046
+ count: args.count,
1047
+ referenceVideoDurationSeconds: args.referenceVideoDurationSeconds,
1048
+ dryRun: args.dryRun === true,
1049
+ }, "POST");
1050
+ const lines = response.dryRun
1051
+ ? [
1052
+ "# 爆款视频复刻预检通过",
1053
+ response.estimateSource === "duration_hint"
1054
+ ? `- 时长提示: ${response.durationHintSeconds} 秒(正式创建将使用 OSS 真实检测值)`
1055
+ : "- 时长提示: 未提供,正式创建后由 OSS 检测",
1056
+ response.estimatedCredits === null || response.estimatedCredits === undefined
1057
+ ? "- 预计积分: 暂不可用"
1058
+ : `- 预计积分: ${response.estimatedCredits}`,
1059
+ `- 当前余额: ${response.remainingCredits}`,
1060
+ "- 未上传 OSS、未创建任务、未扣费",
1061
+ ]
1062
+ : [
1063
+ "# 爆款视频复刻任务已创建",
1064
+ `- 任务 ID: ${response.taskId}`,
1065
+ `- 模式: ${response.mode}`,
1066
+ `- 质量: ${response.quality}`,
1067
+ `- 数量: ${response.count}`,
1068
+ `- 已扣积分: ${response.creditsDeducted}`,
1069
+ `- ${response.statusHint}`,
1070
+ ];
1071
+ return creativeToolResult(response, lines);
1072
+ }
1073
+ async function handleGetMotionVideoRemakeStatus(apiBaseUrl, token, args) {
1074
+ if (!Number.isInteger(args?.taskId)) {
1075
+ throw new McpToolError("taskId 必须是整数。", "BAD_REQUEST");
1076
+ }
1077
+ const response = await callRestApi(apiBaseUrl, `/mcp/motion-remake/status/${args.taskId}`, { token }, "GET");
1078
+ const completed = Array.isArray(response.results)
1079
+ ? response.results.filter((item) => item.status === "completed").length
1080
+ : 0;
1081
+ return creativeToolResult(response, [
1082
+ "# 爆款视频复刻任务状态",
1083
+ `- 任务 ID: ${response.taskId}`,
1084
+ `- 状态: ${response.status}`,
1085
+ `- 进度: ${response.progress}%`,
1086
+ `- 已完成: ${completed}/${response.count}`,
1087
+ ...(response.status === "completed" ? ["- 视频链接已返回在 results 中"] : []),
1088
+ ]);
1089
+ }
1090
+ async function handleCreateEcommerceStoryboardTask(apiBaseUrl, token, args) {
1091
+ if (!args || typeof args.productName !== "string" || typeof args.productDescription !== "string") {
1092
+ throw new McpToolError("productName 和 productDescription 不能为空。", "MISSING_PARAMETERS");
1093
+ }
1094
+ const productImage = args.productImage
1095
+ ? (await processFileInput(args.productImage, "image", 5 * 1024 * 1024)).data
1096
+ : undefined;
1097
+ const response = await callRestApi(apiBaseUrl, "/mcp/storyboard/create", {
1098
+ token,
1099
+ productName: args.productName,
1100
+ productDescription: args.productDescription,
1101
+ targetAudience: args.targetAudience,
1102
+ videoStyle: args.videoStyle,
1103
+ productImage,
1104
+ targetDuration: args.targetDuration,
1105
+ language: args.language,
1106
+ orientation: args.orientation,
1107
+ dryRun: args.dryRun === true,
1108
+ }, "POST");
1109
+ const lines = response.dryRun
1110
+ ? [
1111
+ "# 电商视频分镜预检通过",
1112
+ `- 预计积分: ${response.estimatedCredits}`,
1113
+ `- 当前余额: ${response.remainingCredits}`,
1114
+ "- 未上传商品图、未创建任务、未扣费",
1115
+ ]
1116
+ : [
1117
+ "# 电商视频分镜任务已创建",
1118
+ `- 任务 ID: ${response.taskId}`,
1119
+ `- 已扣积分: ${response.creditsDeducted}`,
1120
+ `- ${response.statusHint}`,
1121
+ ];
1122
+ return creativeToolResult(response, lines);
1123
+ }
1124
+ async function handleGetEcommerceStoryboardStatus(apiBaseUrl, token, args) {
1125
+ if (!Number.isInteger(args?.taskId)) {
1126
+ throw new McpToolError("taskId 必须是整数。", "BAD_REQUEST");
1127
+ }
1128
+ const response = await callRestApi(apiBaseUrl, `/mcp/storyboard/status/${args.taskId}`, { token }, "GET");
1129
+ return creativeToolResult(response, [
1130
+ "# 电商视频分镜任务状态",
1131
+ `- 任务 ID: ${response.taskId}`,
1132
+ `- 状态: ${response.status}`,
1133
+ `- 进度: ${response.progress}%`,
1134
+ `- 分镜数量: ${Array.isArray(response.scenes) ? response.scenes.length : 0}`,
1135
+ ...(response.status === "completed"
1136
+ ? ["- 完整剧情和逐镜 Prompt/插图已返回在 structuredContent 中"]
1137
+ : []),
1138
+ ]);
1139
+ }
892
1140
  /**
893
1141
  * Call REST API
894
1142
  *