@infimind/video-mcp-cli 1.0.11 → 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, isMcpModel, MCP_VIDEO_MODELS, } 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,103 +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
  },
128
159
  model: {
129
160
  type: "string",
130
- description: "生成引擎短码(推荐):SA=极睿Sa专业版(默认,Sora 2,失败自动回退 S4)、SD=极睿Sd专业版(Seedance 2.0)、HA=极睿Ha专业版(HappyHorse 1.1)。注意:SD、HA 仅限 VIP 用户使用",
131
- enum: ["SA", "SD", "HA"],
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"],
132
164
  },
133
165
  route: {
134
166
  type: "string",
135
- description: "内部线路代码(已废弃,请改用 model)。兼容老客户端:S2=极睿Sa专业版、S5=极睿Sd专业版、S6=极睿Ha专业版",
136
- enum: ["S2", "S5", "S6"],
167
+ title: "兼容模型参数",
168
+ description: "已废弃的兼容参数,仅供旧客户端使用;新调用请使用 model 选择 SA、SD、SD25、HA 或 H3 专业版。",
169
+ enum: ["S2", "S5", "S6", "S7", "S8"],
137
170
  deprecated: true,
138
171
  },
139
172
  taskMode: {
140
173
  type: "string",
141
- description: "引擎模式。S5: allinone=全能参考、firstlast=首尾帧;S6: standard=标准模式、reference=图片参考、edit=视频编辑",
174
+ title: "生成模式",
175
+ description: "可选。allinone=全能参考,firstlast=首尾帧,standard=标准模式,reference=图片参考,edit=视频编辑;可用模式取决于所选模型。",
142
176
  enum: ["allinone", "firstlast", "standard", "reference", "edit"],
143
177
  },
144
178
  resolution: {
145
179
  type: "string",
146
- description: "S5/S6 输出清晰度(默认:720p)",
180
+ title: "输出清晰度",
181
+ description: "可选。SD 或 HA 专业版可选 720p 或 1080p;SD2.5 专业版仅支持 480p 或 720p;默认 720p。",
147
182
  enum: ["720p", "1080p"],
148
183
  },
149
184
  ratio: {
150
185
  type: "string",
151
- 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。",
152
188
  enum: ["16:9", "9:16", "1:1", "4:3", "3:4"],
153
189
  },
154
190
  generateAudio: {
155
191
  type: "boolean",
156
- description: "S5 极睿Sd专业版是否生成音频(默认:true)",
192
+ title: "生成音频",
193
+ description: "可选。SD / SD2.5 专业版是否同时生成音频,默认 true。",
157
194
  },
158
195
  webSearchEnabled: {
159
196
  type: "boolean",
160
- description: "S5 极睿Sd专业版是否启用 Agent 增强(默认:false)",
197
+ title: "启用内容增强",
198
+ description: "可选。SD 专业版是否启用联网内容增强,默认 false;SD2.5 专业版不支持。",
161
199
  },
162
200
  dryRun: {
163
201
  type: "boolean",
164
- description: "仅执行参数、素材、权限和积分预检,不创建任务、不扣费。适合在正式创建前确认 S5/S6 参数是否可用",
202
+ title: "仅预检",
203
+ description: "可选。设为 true 时仅校验参数、素材、权限并估算积分,不创建任务且不扣费;默认 false。",
165
204
  },
166
205
  },
167
206
  required: ["prompt"],
@@ -169,82 +208,95 @@ export function createServer(token, apiBaseUrl) {
169
208
  },
170
209
  outputSchema: {
171
210
  type: "object",
211
+ title: "AI 视频任务创建结果",
212
+ description: "返回任务标识、所用配置、积分信息和后续查询提示;预检或失败时部分字段可能不返回。",
172
213
  properties: {
173
- success: { type: "boolean" },
174
- taskId: { type: "number" },
175
- model: { type: "string" },
176
- route: { type: "string" },
177
- engineName: { type: "string" },
178
- usedRouteAlias: { type: "boolean" },
179
- taskMode: { type: "string" },
180
- videoCount: { type: "number" },
181
- dryRun: { type: "boolean" },
182
- estimatedCredits: { type: "number" },
183
- estimateSource: { type: "string" },
184
- remainingCredits: { type: "number" },
185
- 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 查询任务进度。" },
186
227
  validationWarnings: {
187
228
  type: "array",
188
- items: { type: "string" },
229
+ title: "预检警告",
230
+ description: "参数或素材预检产生的非阻断性提示列表。",
231
+ items: { type: "string", title: "预检警告项", description: "一条需要关注但不阻断请求的预检提示。" },
189
232
  },
190
- code: { type: "string" },
191
- message: { type: "string" },
233
+ code: { type: "string", title: "错误代码", description: "请求失败时返回的机器可读错误代码,成功时可能不返回。" },
234
+ message: { type: "string", title: "结果消息", description: "请求结果或失败原因的可读说明。" },
192
235
  },
193
236
  required: ["success"],
194
237
  },
195
238
  },
196
239
  {
197
240
  name: "get_video_status",
198
- title: "查询视频生成状态",
199
- description: "获取视频生成任务的状态,包括进度和视频 URL",
241
+ title: "查询 AI 视频生成结果",
242
+ description: "根据 create_video_task 返回的 taskId 查询 AI 视频生成进度和结果。任务未完成时请间隔一段时间后使用同一 taskId 再次调用;完成后返回视频链接,失败时返回原因。",
200
243
  inputSchema: {
201
244
  type: "object",
245
+ title: "AI 视频任务查询参数",
246
+ description: "指定需要查询的视频生成任务。",
202
247
  properties: {
203
248
  taskId: {
204
249
  type: "number",
205
- description: "视频生成任务 ID",
250
+ title: "任务 ID",
251
+ description: "必填。由 create_video_task 返回的视频任务 ID,请原样传入并用于后续轮询。",
206
252
  },
207
253
  },
208
254
  required: ["taskId"],
209
255
  },
210
256
  outputSchema: {
211
257
  type: "object",
258
+ title: "AI 视频生成状态与结果",
259
+ description: "返回任务进度、生成结果和错误信息;任务未完成时结果链接和完成时间可能为空。",
212
260
  properties: {
213
- success: { type: "boolean" },
214
- taskId: { type: "number" },
215
- status: { type: "string" },
216
- progress: { type: "number" },
217
- totalVideos: { type: "number" },
218
- completedVideos: { type: "number" },
219
- estimatedCredits: { type: "number" },
220
- taskMode: { type: ["string", "null"] },
221
- statusHint: { type: "string" },
222
- createdAt: { type: ["string", "null"] },
223
- updatedAt: { type: ["string", "null"] },
224
- completedAt: { type: ["string", "null"] },
225
- errorMessage: { type: "string" },
226
- errorCode: { type: "string" },
227
- errorCategory: { type: "string" },
228
- retryable: { type: "boolean" },
229
- code: { type: "string" },
230
- message: { type: "string" },
231
- route: { type: "string" },
232
- 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: "任务实际使用的极睿专业版模型名称。" },
233
281
  videos: {
234
282
  type: "array",
283
+ title: "视频结果列表",
284
+ description: "每个待生成视频的状态和结果;单项未完成时链接可能为空。",
235
285
  items: {
236
286
  type: "object",
287
+ title: "视频结果",
288
+ description: "一个视频生成结果及其状态、链接、时间和错误信息。",
237
289
  properties: {
238
- id: { type: "number" },
239
- status: { type: "string" },
240
- videoUrl: { type: ["string", "null"] },
241
- thumbnailUrl: { type: ["string", "null"] },
242
- errorMessage: { type: ["string", "null"] },
243
- errorCode: { type: ["string", "null"] },
244
- errorCategory: { type: ["string", "null"] },
245
- retryable: { type: ["boolean", "null"] },
246
- createdAt: { type: ["string", "null"] },
247
- 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: "该视频的完成时间;未完成时为空。" },
248
300
  },
249
301
  required: ["id", "status", "videoUrl", "thumbnailUrl", "errorMessage"],
250
302
  },
@@ -255,70 +307,82 @@ export function createServer(token, apiBaseUrl) {
255
307
  },
256
308
  {
257
309
  name: "create_video_analysis_task",
258
- title: "创建视频拆解任务",
259
- description: "创建视频拆解任务,将视频拆解为脚本、风格、卖点话术、爆款公式,并生成用于视频创作的提示词。" +
260
- "支持本地文件路径或公网 URL。",
310
+ title: "分析参考视频内容",
311
+ description: "分析本地视频或公网 URL,提取脚本、风格、卖点、创作公式并生成视频创作提示词;本工具不会生成最终视频。" +
312
+ "本工具返回 analysisId,创建后请调用 get_video_analysis_status 并原样传入 analysisId,轮询分析进度和结果。",
261
313
  inputSchema: {
262
314
  type: "object",
315
+ title: "参考视频分析参数",
316
+ description: "指定需要分析的参考视频。",
263
317
  properties: {
264
318
  videoFile: {
265
319
  type: "string",
266
- description: "要分析的视频文件,可以是本地文件路径或公网 URL",
320
+ title: "参考视频",
321
+ description: "必填。需要分析的视频本地文件路径或公网 URL。",
267
322
  },
268
323
  },
269
324
  required: ["videoFile"],
270
325
  },
271
326
  outputSchema: {
272
327
  type: "object",
328
+ title: "视频分析任务创建结果",
329
+ description: "返回分析任务标识、积分信息和后续查询提示;失败时部分字段可能不返回。",
273
330
  properties: {
274
- success: { type: "boolean" },
275
- analysisId: { type: "number" },
276
- creditsUsed: { type: "number" },
277
- remainingCredits: { type: "number" },
278
- statusHint: { type: "string" },
279
- code: { type: "string" },
280
- 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: "创建结果或失败原因的可读说明。" },
281
338
  },
282
339
  required: ["success"],
283
340
  },
284
341
  },
285
342
  {
286
343
  name: "get_video_analysis_status",
287
- title: "查询视频拆解状态",
288
- description: "获取视频拆解任务的状态和结果",
344
+ title: "查询视频分析结果",
345
+ description: "根据 create_video_analysis_task 返回的 analysisId 查询分析进度和结构化结果。任务未完成时请间隔一段时间后使用同一 analysisId 再次调用。",
289
346
  inputSchema: {
290
347
  type: "object",
348
+ title: "视频分析任务查询参数",
349
+ description: "指定需要查询的视频分析任务。",
291
350
  properties: {
292
351
  analysisId: {
293
352
  type: "number",
294
- description: "视频拆解任务 ID",
353
+ title: "分析任务 ID",
354
+ description: "必填。由 create_video_analysis_task 返回的分析任务 ID,请原样传入并用于后续轮询。",
295
355
  },
296
356
  },
297
357
  required: ["analysisId"],
298
358
  },
299
359
  outputSchema: {
300
360
  type: "object",
361
+ title: "视频分析状态与结果",
362
+ description: "返回分析进度和结构化内容;任务未完成或失败时分析内容可能为空。",
301
363
  properties: {
302
- success: { type: "boolean" },
303
- analysisId: { type: "number" },
304
- status: { type: "string" },
305
- conversionProgress: { type: ["number", "null"] },
306
- script: { type: ["string", "null"] },
307
- style: { type: ["string", "null"] },
308
- sellingPoints: { type: ["string", "null"] },
309
- viralFormula: { type: ["string", "null"] },
310
- generatedPrompt: { type: ["string", "null"] },
311
- errorMessage: { type: ["string", "null"] },
312
- originalVideoUrl: { type: ["string", "null"] },
313
- videoFileName: { type: ["string", "null"] },
314
- code: { type: "string" },
315
- 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: "查询结果或失败原因的可读说明。" },
316
378
  },
317
379
  required: ["success"],
318
380
  },
319
381
  },
382
+ ...MCP_CREATIVE_TOOLS,
320
383
  ];
321
- return { tools };
384
+ const filtered = options.filterTools ? options.filterTools(allTools) : allTools;
385
+ return { tools: filtered };
322
386
  });
323
387
  /**
324
388
  * Handle tool calls
@@ -337,6 +401,14 @@ export function createServer(token, apiBaseUrl) {
337
401
  return await handleCreateVideoAnalysisTask(apiBaseUrl, token, args);
338
402
  case "get_video_analysis_status":
339
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);
340
412
  default:
341
413
  throw new Error(`Unknown tool: ${name}`);
342
414
  }
@@ -402,7 +474,7 @@ async function handleGetUserInfo(apiBaseUrl, token) {
402
474
  * 处理文件输入:支持本地文件路径或公网 URL
403
475
  * 对于本地文件,读取并转换为 base64
404
476
  */
405
- async function processFileInput(fileInput, expectedType) {
477
+ async function processFileInput(fileInput, expectedType, maxBytes) {
406
478
  // 检查是否是公网 URL
407
479
  if (fileInput.startsWith("http://") || fileInput.startsWith("https://")) {
408
480
  return {
@@ -418,8 +490,6 @@ async function processFileInput(fileInput, expectedType) {
418
490
  if (!existsSync(filePath)) {
419
491
  throw new McpToolError(`文件不存在: ${filePath}`, "FILE_NOT_FOUND");
420
492
  }
421
- // 读取文件
422
- const buffer = readFileSync(filePath);
423
493
  // 获取文件扩展名并确定 MIME type
424
494
  const ext = extname(filePath).toLowerCase();
425
495
  let mimeType = "application/octet-stream";
@@ -442,12 +512,12 @@ async function processFileInput(fileInput, expectedType) {
442
512
  throw new McpToolError(`文件 ${basename(filePath)} 类型不匹配:当前是 ${mimeType},但该字段需要 ${expectedType} 文件。`, "INVALID_FILE_TYPE");
443
513
  }
444
514
  const size = statSync(filePath).size;
445
- if (expectedType === "video" && size > 50 * 1024 * 1024) {
446
- throw new McpToolError(`视频文件 ${basename(filePath)} 超过 50MB 限制。`, "MEDIA_FILE_TOO_LARGE");
447
- }
448
- if (expectedType === "audio" && size > 15 * 1024 * 1024) {
449
- 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");
450
518
  }
519
+ // 通过大小预检后再读取,避免将超限文件装入内存。
520
+ const buffer = readFileSync(filePath);
451
521
  // 转换为 base64
452
522
  const base64 = `data:${mimeType};base64,${buffer.toString("base64")}`;
453
523
  return {
@@ -485,9 +555,9 @@ function normalizeRoute(args) {
485
555
  const raw = args && typeof args === "object" ? args.route : undefined;
486
556
  const route = typeof raw === "string" ? raw : "S2";
487
557
  if (!isMcpRoute(route)) {
488
- throw new McpToolError("model 必须是 SA、SD 或 HA;route 只接受 S2、S5、S6(已废弃),请改用 model。", "INVALID_ENGINE");
558
+ throw new McpToolError("model 必须是 SA、SD、SD25、HA 或 H3;route 只接受 S2、S5、S6、S7、S8(已废弃),请改用 model。", "INVALID_ENGINE");
489
559
  }
490
- const m = route === "S2" ? "SA" : route === "S5" ? "SD" : "HA";
560
+ const m = getMcpModelForRoute(route);
491
561
  return {
492
562
  route,
493
563
  model: m,
@@ -498,17 +568,17 @@ function normalizeTaskMode(route, value) {
498
568
  const mode = typeof value === "string" ? value : getMcpDefaultTaskMode(route);
499
569
  if (route === "S2") {
500
570
  if (value !== undefined) {
501
- 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);
502
572
  }
503
573
  return getMcpDefaultTaskMode(route);
504
574
  }
505
575
  if (!isValidMcpTaskMode(route, mode)) {
506
- 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";
507
577
  throw new McpToolError(`${getMcpEngineName(route)} 的 taskMode 只能是 ${allowedModes}。`, "INVALID_TASK_MODE", route);
508
578
  }
509
579
  return mode;
510
580
  }
511
- function validateCreateVideoArgs(args) {
581
+ export function validateCreateVideoArgs(args) {
512
582
  if (!args || typeof args !== "object") {
513
583
  throw new McpToolError("请提供视频生成参数。");
514
584
  }
@@ -523,11 +593,23 @@ function validateCreateVideoArgs(args) {
523
593
  const taskMode = normalizeTaskMode(route, args.taskMode);
524
594
  const resolution = typeof args.resolution === "string" ? args.resolution : "720p";
525
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
+ }
526
599
  if ((route === "S5" || route === "S6") && resolution !== "720p" && resolution !== "1080p") {
527
600
  throw new McpToolError(`${getMcpEngineName(route)} 仅支持 720p 或 1080p。`, "INVALID_RESOLUTION", route);
528
601
  }
529
602
  if (route === "S2" && args.seconds === 5) {
530
- 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
+ }
531
613
  }
532
614
  if (route === "S2") {
533
615
  const unsupported = [
@@ -540,36 +622,47 @@ function validateCreateVideoArgs(args) {
540
622
  args.webSearchEnabled !== undefined ? "webSearchEnabled" : undefined,
541
623
  ].filter(Boolean);
542
624
  if (unsupported.length > 0) {
543
- throw new McpToolError(`这些字段不适用于极睿Sa专业版:${unsupported.join("、")}。S2 单图请使用 sourceImage。`, "UNSUPPORTED_FIELD", route);
625
+ throw new McpToolError(`这些字段不适用于极睿SA专业版:${unsupported.join("、")}。S2 单图请使用 sourceImage。`, "UNSUPPORTED_FIELD", route);
544
626
  }
545
627
  }
546
- 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);
547
633
  if (args.sourceImage !== undefined) {
548
- throw new McpToolError("sourceImage 不适用于极睿Sd专业版;请使用 sourceImages 传参考图或首尾帧。", "UNSUPPORTED_FIELD", route);
634
+ throw new McpToolError(`sourceImage 不适用于${sdEngine};请使用 sourceImages 传参考图或首尾帧。`, "UNSUPPORTED_FIELD", route);
549
635
  }
550
636
  if (args.ratio !== undefined) {
551
- 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);
552
641
  }
553
642
  if (taskMode === "firstlast" && !hasItems(args.sourceImages)) {
554
- throw new McpToolError("极睿Sd专业版首尾帧模式需要在 sourceImages 中提供至少 1 张首帧图片。", "MISSING_SOURCE_IMAGE", route);
643
+ throw new McpToolError(`${sdEngine}首尾帧模式需要在 sourceImages 中提供至少 1 张首帧图片。`, "MISSING_SOURCE_IMAGE", route);
555
644
  }
556
645
  if (taskMode === "firstlast" && (hasItems(args.sourceVideos) || hasItems(args.sourceAudios))) {
557
- throw new McpToolError("sourceVideos/sourceAudios 仅适用于极睿Sd专业版全能参考模式;首尾帧模式只使用 sourceImages。", "UNSUPPORTED_FIELD", route);
646
+ throw new McpToolError(`sourceVideos/sourceAudios 仅适用于${sdEngine}全能参考模式;首尾帧模式只使用 sourceImages。`, "UNSUPPORTED_FIELD", route);
558
647
  }
559
648
  if (taskMode === "firstlast" && itemCount(args.sourceImages) > 2) {
560
- throw new McpToolError("极睿Sd专业版首尾帧模式最多支持 2 张图片。", "MEDIA_COUNT_EXCEEDED", route);
649
+ throw new McpToolError(`${sdEngine}首尾帧模式最多支持 2 张图片。`, "MEDIA_COUNT_EXCEEDED", route);
561
650
  }
562
- if (taskMode === "allinone" && itemCount(args.sourceImages) > 9) {
563
- 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);
564
656
  }
565
- if (taskMode === "allinone" && itemCount(args.sourceVideos) > 3) {
566
- 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);
567
659
  }
568
- if (taskMode === "allinone" && itemCount(args.sourceAudios) > 3) {
569
- 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);
570
662
  }
571
- if (taskMode === "allinone" && hasItems(args.sourceAudios) && !hasItems(args.sourceImages) && !hasItems(args.sourceVideos)) {
572
- 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);
573
666
  }
574
667
  }
575
668
  if (route === "S6") {
@@ -579,28 +672,60 @@ function validateCreateVideoArgs(args) {
579
672
  args.webSearchEnabled !== undefined ? "webSearchEnabled" : undefined,
580
673
  ].filter(Boolean);
581
674
  if (unsupported.length > 0) {
582
- throw new McpToolError(`这些字段仅适用于极睿Sd专业版,不能用于极睿Ha专业版:${unsupported.join("、")}。`, "UNSUPPORTED_FIELD", route);
675
+ throw new McpToolError(`这些字段仅适用于极睿SD专业版,不能用于极睿HA专业版:${unsupported.join("、")}。`, "UNSUPPORTED_FIELD", route);
583
676
  }
584
677
  if (taskMode === "standard" && (hasItems(args.sourceImages) || hasItems(args.sourceVideos))) {
585
- throw new McpToolError("极睿Ha专业版标准模式只支持 sourceImage 作为单图输入;多图请使用 reference 模式,视频请使用 edit 模式。", "UNSUPPORTED_FIELD", route);
678
+ throw new McpToolError("极睿HA专业版标准模式只支持 sourceImage 作为单图输入;多图请使用 reference 模式,视频请使用 edit 模式。", "UNSUPPORTED_FIELD", route);
586
679
  }
587
680
  if ((taskMode === "reference" || taskMode === "edit") && args.sourceImage !== undefined) {
588
- throw new McpToolError("sourceImage 仅适用于极睿Ha专业版标准模式;reference/edit 模式请使用 sourceImages。", "UNSUPPORTED_FIELD", route);
681
+ throw new McpToolError("sourceImage 仅适用于极睿HA专业版标准模式;reference/edit 模式请使用 sourceImages。", "UNSUPPORTED_FIELD", route);
589
682
  }
590
683
  if (taskMode === "reference" && !hasItems(args.sourceImages)) {
591
- throw new McpToolError("极睿Ha专业版图片参考模式需要在 sourceImages 中提供至少 1 张参考图片。", "MISSING_SOURCE_IMAGE", route);
684
+ throw new McpToolError("极睿HA专业版图片参考模式需要在 sourceImages 中提供至少 1 张参考图片。", "MISSING_SOURCE_IMAGE", route);
592
685
  }
593
686
  if (taskMode === "edit" && !hasItems(args.sourceVideos)) {
594
- throw new McpToolError("极睿Ha专业版视频编辑模式需要在 sourceVideos 中提供至少 1 个参考视频。", "MISSING_SOURCE_VIDEO", route);
687
+ throw new McpToolError("极睿HA专业版视频编辑模式需要在 sourceVideos 中提供至少 1 个参考视频。", "MISSING_SOURCE_VIDEO", route);
595
688
  }
596
689
  if (taskMode === "reference" && itemCount(args.sourceImages) > 9) {
597
- throw new McpToolError("极睿Ha专业版图片参考模式最多支持 9 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
690
+ throw new McpToolError("极睿HA专业版图片参考模式最多支持 9 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
598
691
  }
599
692
  if (taskMode === "edit" && itemCount(args.sourceVideos) > 1) {
600
- throw new McpToolError("极睿Ha专业版视频编辑模式最多支持 1 个参考视频。", "MEDIA_COUNT_EXCEEDED", route);
693
+ throw new McpToolError("极睿HA专业版视频编辑模式最多支持 1 个参考视频。", "MEDIA_COUNT_EXCEEDED", route);
601
694
  }
602
695
  if (taskMode === "edit" && itemCount(args.sourceImages) > 5) {
603
- 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);
604
729
  }
605
730
  }
606
731
  return {
@@ -618,7 +743,10 @@ function validateCreateVideoArgs(args) {
618
743
  */
619
744
  async function handleCreateVideoTask(apiBaseUrl, token, args) {
620
745
  const normalized = validateCreateVideoArgs(args);
621
- 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";
622
750
  const requestBody = {
623
751
  token,
624
752
  prompt: args.prompt,
@@ -897,6 +1025,118 @@ async function handleGetVideoAnalysisStatus(apiBaseUrl, token, args) {
897
1025
  },
898
1026
  };
899
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
+ }
900
1140
  /**
901
1141
  * Call REST API
902
1142
  *