@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/README.md +46 -3
- package/dist/auth/credentialWriter.js +84 -0
- package/dist/auth/exchange.js +61 -0
- package/dist/auth/installationId.js +36 -0
- package/dist/auth/login.js +77 -0
- package/dist/auth/logout.js +34 -0
- package/dist/auth/loopback.js +153 -0
- package/dist/auth/paths.js +30 -0
- package/dist/auth/pkce.js +11 -0
- package/dist/auth/profile.js +15 -0
- package/dist/auth/redact.js +63 -0
- package/dist/auth/status.js +31 -0
- package/dist/creativeTools.js +251 -0
- package/dist/index.js +139 -39
- package/dist/server.js +420 -172
- package/dist/videoRules.js +83 -7
- package/package.json +34 -7
- package/dist/index.d.ts +0 -2
- package/dist/server.d.ts +0 -11
- package/dist/videoRules.d.ts +0 -17
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
|
|
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: "
|
|
70
|
-
"
|
|
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
|
-
|
|
76
|
+
title: "视频生成提示词",
|
|
77
|
+
description: "必填。描述希望生成的视频内容、主体、动作、场景、镜头和风格。",
|
|
77
78
|
},
|
|
78
79
|
sourceImage: {
|
|
79
80
|
type: "string",
|
|
80
|
-
|
|
81
|
+
title: "单张参考图片",
|
|
82
|
+
description: "可选。本地图片路径或公网 URL;SA 专业版图生视频或 HA 专业版标准模式单图生成时使用。",
|
|
81
83
|
},
|
|
82
84
|
sourceImages: {
|
|
83
85
|
type: "array",
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
+
title: "生成音频",
|
|
193
|
+
description: "可选。SD / SD2.5 专业版是否同时生成音频,默认 true。",
|
|
151
194
|
},
|
|
152
195
|
webSearchEnabled: {
|
|
153
196
|
type: "boolean",
|
|
154
|
-
|
|
197
|
+
title: "启用内容增强",
|
|
198
|
+
description: "可选。SD 专业版是否启用联网内容增强,默认 false;SD2.5 专业版不支持。",
|
|
155
199
|
},
|
|
156
200
|
dryRun: {
|
|
157
201
|
type: "boolean",
|
|
158
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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(
|
|
474
|
-
|
|
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("
|
|
558
|
+
throw new McpToolError("model 必须是 SA、SD、SD25、HA 或 H3;route 只接受 S2、S5、S6、S7、S8(已废弃),请改用 model。", "INVALID_ENGINE");
|
|
477
559
|
}
|
|
478
|
-
|
|
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 仅适用于极睿
|
|
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
|
|
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("极睿
|
|
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(`这些字段不适用于极睿
|
|
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(
|
|
634
|
+
throw new McpToolError(`sourceImage 不适用于${sdEngine};请使用 sourceImages 传参考图或首尾帧。`, "UNSUPPORTED_FIELD", route);
|
|
531
635
|
}
|
|
532
636
|
if (args.ratio !== undefined) {
|
|
533
|
-
throw new McpToolError(
|
|
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(
|
|
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(
|
|
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(
|
|
649
|
+
throw new McpToolError(`${sdEngine}首尾帧模式最多支持 2 张图片。`, "MEDIA_COUNT_EXCEEDED", route);
|
|
543
650
|
}
|
|
544
|
-
|
|
545
|
-
|
|
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) >
|
|
548
|
-
throw new McpToolError(
|
|
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) >
|
|
551
|
-
throw new McpToolError(
|
|
660
|
+
if (taskMode === "allinone" && itemCount(args.sourceAudios) > maxAudios) {
|
|
661
|
+
throw new McpToolError(`${sdEngine}全能参考模式最多支持 ${maxAudios} 个参考音频。`, "MEDIA_COUNT_EXCEEDED", route);
|
|
552
662
|
}
|
|
553
|
-
|
|
554
|
-
|
|
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(`这些字段仅适用于极睿
|
|
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("极睿
|
|
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 仅适用于极睿
|
|
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("极睿
|
|
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("极睿
|
|
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("极睿
|
|
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("极睿
|
|
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("极睿
|
|
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" ||
|
|
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
|
*
|