@infimind/video-mcp-cli 1.0.5 → 1.0.7
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 +209 -2
- package/dist/server.js +442 -25
- package/dist/videoRules.d.ts +17 -0
- package/dist/videoRules.js +44 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ Add the following to your Claude Desktop configuration file:
|
|
|
50
50
|
"command": "sora-mcp",
|
|
51
51
|
"args": ["--token=YOUR_TOKEN_HERE"],
|
|
52
52
|
"env": {
|
|
53
|
-
"SORA_API_URL": "https://your-api-domain.com/api
|
|
53
|
+
"SORA_API_URL": "https://your-api-domain.com/api"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -59,7 +59,7 @@ Add the following to your Claude Desktop configuration file:
|
|
|
59
59
|
|
|
60
60
|
#### Environment Variables
|
|
61
61
|
|
|
62
|
-
- `SORA_API_URL` (optional): Your API base URL. Defaults to `http://localhost:3000/api
|
|
62
|
+
- `SORA_API_URL` (optional): Your API base URL. Defaults to `http://localhost:3000/api`
|
|
63
63
|
|
|
64
64
|
### 3. Restart Claude Desktop
|
|
65
65
|
|
|
@@ -76,6 +76,158 @@ Get information about the authenticated user.
|
|
|
76
76
|
Get my user information for the video generator
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### `create_video_task`
|
|
80
|
+
|
|
81
|
+
Create a video generation task.
|
|
82
|
+
|
|
83
|
+
Supported engines:
|
|
84
|
+
|
|
85
|
+
- `S2`: 极睿Sa专业版
|
|
86
|
+
- `S5`: 极睿Sd专业版 (VIP only)
|
|
87
|
+
- `S6`: 极睿Ha专业版 (VIP only)
|
|
88
|
+
|
|
89
|
+
Parameter applicability:
|
|
90
|
+
|
|
91
|
+
| Parameter | S2 极睿Sa专业版 | S5 极睿Sd专业版 | S6 极睿Ha专业版 |
|
|
92
|
+
| --- | --- | --- | --- |
|
|
93
|
+
| `sourceImage` | Optional single image | Not supported | Standard mode single image |
|
|
94
|
+
| `sourceImages` | Not supported | allinone / firstlast images | reference / edit reference images |
|
|
95
|
+
| `sourceVideos` | Not supported | allinone reference videos | edit input videos |
|
|
96
|
+
| `sourceAudios` | Not supported | allinone reference audio | Not supported |
|
|
97
|
+
| `taskMode` | Not needed | `allinone` / `firstlast` | `standard` / `reference` / `edit` |
|
|
98
|
+
| `resolution` | Not supported | `720p` / `1080p` | `720p` / `1080p` |
|
|
99
|
+
| `ratio` | Not supported | Not supported | reference or text-to-video standard mode |
|
|
100
|
+
| `generateAudio` | Not supported | Optional | Not supported |
|
|
101
|
+
| `webSearchEnabled` | Not supported | Optional | Not supported |
|
|
102
|
+
| `dryRun` | Optional preflight | Optional preflight | Optional preflight |
|
|
103
|
+
|
|
104
|
+
Local file paths and public URLs are both supported. Local files are converted to base64 by the MCP server before being sent to the API.
|
|
105
|
+
|
|
106
|
+
Video inputs support MP4/MOV. Audio inputs support MP3/WAV. The server validates media duration, size, count, resolution, and URL safety before estimating credits or creating tasks.
|
|
107
|
+
|
|
108
|
+
Use `dryRun: true` to validate parameters, VIP access, estimated credits, and media rules without creating a task or deducting credits. The result includes `success`, `dryRun`, `route`, `engineName`, `taskMode`, `estimatedCredits`, `remainingCredits`, and `validationWarnings`. Specific credit tables are server-side only; use dry run to get the current estimated consumption.
|
|
109
|
+
|
|
110
|
+
#### Dry run before creating a task
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"prompt": "@图片1 作为角色参考,生成自然的走秀镜头",
|
|
115
|
+
"route": "S6",
|
|
116
|
+
"taskMode": "reference",
|
|
117
|
+
"sourceImages": ["/path/to/character.jpg"],
|
|
118
|
+
"seconds": 10,
|
|
119
|
+
"ratio": "9:16",
|
|
120
|
+
"resolution": "720p",
|
|
121
|
+
"dryRun": true
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### 极睿Sa专业版 - 标准生成
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"prompt": "一个高质感的产品展示视频,柔和布光,镜头缓慢推进",
|
|
130
|
+
"route": "S2",
|
|
131
|
+
"sourceImage": "/path/to/product.jpg",
|
|
132
|
+
"seconds": 10,
|
|
133
|
+
"size": "720x1280",
|
|
134
|
+
"language": "zh-CN",
|
|
135
|
+
"count": 1
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### 极睿Sd专业版 - 全能参考
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"prompt": "根据参考素材生成一个高质感产品展示视频",
|
|
144
|
+
"route": "S5",
|
|
145
|
+
"taskMode": "allinone",
|
|
146
|
+
"sourceImages": ["/path/to/reference.jpg"],
|
|
147
|
+
"sourceVideos": ["/path/to/reference.mp4"],
|
|
148
|
+
"sourceAudios": ["/path/to/music.mp3"],
|
|
149
|
+
"seconds": 10,
|
|
150
|
+
"size": "720x1280",
|
|
151
|
+
"resolution": "720p",
|
|
152
|
+
"generateAudio": true,
|
|
153
|
+
"webSearchEnabled": false
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### 极睿Sd专业版 - 首尾帧
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"prompt": "从首帧自然过渡到尾帧,保持产品主体一致",
|
|
162
|
+
"route": "S5",
|
|
163
|
+
"taskMode": "firstlast",
|
|
164
|
+
"sourceImages": ["/path/to/first-frame.jpg", "/path/to/end-frame.jpg"],
|
|
165
|
+
"seconds": 5,
|
|
166
|
+
"size": "1280x720",
|
|
167
|
+
"resolution": "720p"
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### 极睿Ha专业版 - 标准模式
|
|
172
|
+
|
|
173
|
+
Text-to-video:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"prompt": "一段电影感城市夜景镜头,雨后街道反光,霓虹灯闪烁",
|
|
178
|
+
"route": "S6",
|
|
179
|
+
"taskMode": "standard",
|
|
180
|
+
"seconds": 5,
|
|
181
|
+
"size": "1280x720",
|
|
182
|
+
"ratio": "16:9",
|
|
183
|
+
"resolution": "720p"
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Image-to-video:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"prompt": "让参考图中的商品在干净摄影棚中缓慢旋转展示",
|
|
192
|
+
"route": "S6",
|
|
193
|
+
"taskMode": "standard",
|
|
194
|
+
"sourceImage": "/path/to/product.jpg",
|
|
195
|
+
"seconds": 5,
|
|
196
|
+
"size": "720x1280",
|
|
197
|
+
"resolution": "720p"
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### 极睿Ha专业版 - 图片参考
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"prompt": "@图片1 和 @图片2 作为角色参考,生成自然的走秀镜头",
|
|
206
|
+
"route": "S6",
|
|
207
|
+
"taskMode": "reference",
|
|
208
|
+
"sourceImages": ["/path/to/character-1.jpg", "/path/to/character-2.jpg"],
|
|
209
|
+
"seconds": 10,
|
|
210
|
+
"size": "720x1280",
|
|
211
|
+
"ratio": "9:16",
|
|
212
|
+
"resolution": "720p"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### 极睿Ha专业版 - 视频编辑
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"prompt": "增强视频的电影感光影,保持人物和商品一致",
|
|
221
|
+
"route": "S6",
|
|
222
|
+
"taskMode": "edit",
|
|
223
|
+
"sourceVideos": ["/path/to/input.mp4"],
|
|
224
|
+
"sourceImages": ["/path/to/reference.jpg"],
|
|
225
|
+
"resolution": "720p"
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
After creating a task, use `get_video_status` with the returned `taskId` to query progress and video links.
|
|
230
|
+
|
|
79
231
|
## Development
|
|
80
232
|
|
|
81
233
|
```bash
|
|
@@ -89,10 +241,65 @@ npm install
|
|
|
89
241
|
# Build
|
|
90
242
|
npm run build
|
|
91
243
|
|
|
244
|
+
# Run MCP smoke tests
|
|
245
|
+
npm run test:smoke
|
|
246
|
+
|
|
92
247
|
# Run in development mode
|
|
93
248
|
node dist/index.js --token=YOUR_TOKEN
|
|
94
249
|
```
|
|
95
250
|
|
|
251
|
+
### Local MCP testing
|
|
252
|
+
|
|
253
|
+
#### MCP Inspector
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
cd mcp-server
|
|
257
|
+
npm run build
|
|
258
|
+
npx @modelcontextprotocol/inspector node dist/index.js --token=YOUR_TOKEN
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Set `SORA_API_URL=http://localhost:3000/api` when testing against a local app server.
|
|
262
|
+
|
|
263
|
+
#### Claude Desktop with local build
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"mcpServers": {
|
|
268
|
+
"sora-video-generator-local": {
|
|
269
|
+
"command": "node",
|
|
270
|
+
"args": ["/absolute/path/to/sora_video_generator/mcp-server/dist/index.js", "--token=YOUR_TOKEN"],
|
|
271
|
+
"env": {
|
|
272
|
+
"SORA_API_URL": "http://localhost:3000/api"
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### npx against the published package
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"mcpServers": {
|
|
284
|
+
"sora-video-generator": {
|
|
285
|
+
"command": "npx",
|
|
286
|
+
"args": ["-y", "@infimind/video-mcp-cli", "--token=YOUR_TOKEN"],
|
|
287
|
+
"env": {
|
|
288
|
+
"SORA_API_URL": "https://your-api-domain.com/api"
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Troubleshooting
|
|
296
|
+
|
|
297
|
+
- If a client reports schema mismatch, run `npm run build` and `npm run test:smoke` in `mcp-server`.
|
|
298
|
+
- If `get_video_status` cannot connect, verify `SORA_API_URL` includes `/api`, for example `http://localhost:3000/api`.
|
|
299
|
+
- If S5/S6 returns VIP errors, confirm the MCP token belongs to a VIP user.
|
|
300
|
+
- If S6 reference/edit prompts contain `@图片N`, keep the original markers; those modes skip prompt translation to preserve image references.
|
|
301
|
+
- If local file inputs fail, use absolute paths and supported media types: jpg/png/webp/gif, mp4/mov/avi/mkv, mp3/wav/m4a/aac/ogg.
|
|
302
|
+
|
|
96
303
|
## Publishing
|
|
97
304
|
|
|
98
305
|
```bash
|
package/dist/server.js
CHANGED
|
@@ -6,15 +6,28 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { readFileSync, existsSync } from "fs";
|
|
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";
|
|
12
|
+
class McpToolError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
route;
|
|
15
|
+
engineName;
|
|
16
|
+
constructor(message, code = "BAD_REQUEST", route) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "McpToolError";
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.route = route;
|
|
21
|
+
this.engineName = route ? getMcpEngineName(route) : undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
11
24
|
/**
|
|
12
25
|
* Create and configure the MCP Server
|
|
13
26
|
*/
|
|
14
27
|
export function createServer(token, apiBaseUrl) {
|
|
15
28
|
const server = new Server({
|
|
16
29
|
name: "sora-video-generator-mcp",
|
|
17
|
-
version:
|
|
30
|
+
version: MCP_SERVER_VERSION,
|
|
18
31
|
}, {
|
|
19
32
|
capabilities: {
|
|
20
33
|
tools: {},
|
|
@@ -27,15 +40,34 @@ export function createServer(token, apiBaseUrl) {
|
|
|
27
40
|
const tools = [
|
|
28
41
|
{
|
|
29
42
|
name: "get_user_info",
|
|
43
|
+
title: "获取用户信息",
|
|
30
44
|
description: "获取当前用户的信息,包括用户名、姓名、邮箱、角色、积分和 VIP 状态",
|
|
31
45
|
inputSchema: {
|
|
32
46
|
type: "object",
|
|
33
47
|
properties: {},
|
|
34
48
|
},
|
|
49
|
+
outputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
success: { type: "boolean" },
|
|
53
|
+
id: { type: "number" },
|
|
54
|
+
username: { type: "string" },
|
|
55
|
+
name: { type: ["string", "null"] },
|
|
56
|
+
email: { type: ["string", "null"] },
|
|
57
|
+
role: { type: "string" },
|
|
58
|
+
credits: { type: "number" },
|
|
59
|
+
isVip: { type: "boolean" },
|
|
60
|
+
code: { type: "string" },
|
|
61
|
+
message: { type: "string" },
|
|
62
|
+
},
|
|
63
|
+
required: ["success"],
|
|
64
|
+
},
|
|
35
65
|
},
|
|
36
66
|
{
|
|
37
67
|
name: "create_video_task",
|
|
68
|
+
title: "创建视频生成任务",
|
|
38
69
|
description: "创建视频生成任务。支持本地文件路径或公网 URL 作为参考图片。" +
|
|
70
|
+
"支持极睿Sd专业版和极睿Ha专业版的多模态素材输入。" +
|
|
39
71
|
"提示词会根据目标语言自动翻译和优化。",
|
|
40
72
|
inputSchema: {
|
|
41
73
|
type: "object",
|
|
@@ -46,16 +78,31 @@ export function createServer(token, apiBaseUrl) {
|
|
|
46
78
|
},
|
|
47
79
|
sourceImage: {
|
|
48
80
|
type: "string",
|
|
49
|
-
description: "
|
|
81
|
+
description: "可选的单张参考图片。S2 使用此字段;S6 标准模式可用作单图图生视频。可以是本地文件路径或公网 URL,本地文件会被自动转换为 base64",
|
|
82
|
+
},
|
|
83
|
+
sourceImages: {
|
|
84
|
+
type: "array",
|
|
85
|
+
description: "S5/S6 多张参考图片。本地文件路径或公网 URL 数组;本地文件会被自动转换为 base64。S5 首尾帧模式下第1张为首帧、第2张为尾帧;S6 reference/edit 模式作为参考图",
|
|
86
|
+
items: { type: "string" },
|
|
87
|
+
},
|
|
88
|
+
sourceVideos: {
|
|
89
|
+
type: "array",
|
|
90
|
+
description: "S5 全能参考或 S6 视频编辑模式的视频素材。本地文件路径或公网 URL 数组;本地文件会被自动转换为 base64",
|
|
91
|
+
items: { type: "string" },
|
|
92
|
+
},
|
|
93
|
+
sourceAudios: {
|
|
94
|
+
type: "array",
|
|
95
|
+
description: "S5 全能参考模式的音频素材。本地文件路径或公网 URL 数组;本地文件会被自动转换为 base64",
|
|
96
|
+
items: { type: "string" },
|
|
50
97
|
},
|
|
51
98
|
size: {
|
|
52
99
|
type: "string",
|
|
53
|
-
description: "
|
|
54
|
-
enum: ["1280x720", "720x1280"],
|
|
100
|
+
description: "视频方向/比例。S2 支持横竖屏;S5/S6 还支持正方形、4:3、3:4(默认:1280x720)",
|
|
101
|
+
enum: ["1280x720", "720x1280", "1024x1024", "1024x768", "768x1024"],
|
|
55
102
|
},
|
|
56
103
|
seconds: {
|
|
57
104
|
type: "number",
|
|
58
|
-
description: "视频时长(秒)。S2
|
|
105
|
+
description: "视频时长(秒)。S2=10或15秒;S5极睿Sd专业版=5、10或15秒;S6极睿Ha专业版 standard/reference=5、10或15秒,edit 模式由输入视频决定",
|
|
59
106
|
enum: [5, 10, 15],
|
|
60
107
|
},
|
|
61
108
|
language: {
|
|
@@ -71,15 +118,66 @@ export function createServer(token, apiBaseUrl) {
|
|
|
71
118
|
},
|
|
72
119
|
route: {
|
|
73
120
|
type: "string",
|
|
74
|
-
description: "
|
|
75
|
-
enum: ["S2", "S5"],
|
|
121
|
+
description: "使用的生成引擎:S2=极睿Sa专业版(默认)、S5=极睿Sd专业版、S6=极睿Ha专业版。注意:S5、S6 仅限 VIP 用户使用",
|
|
122
|
+
enum: ["S2", "S5", "S6"],
|
|
123
|
+
},
|
|
124
|
+
taskMode: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "引擎模式。S5: allinone=全能参考、firstlast=首尾帧;S6: standard=标准模式、reference=图片参考、edit=视频编辑",
|
|
127
|
+
enum: ["allinone", "firstlast", "standard", "reference", "edit"],
|
|
128
|
+
},
|
|
129
|
+
resolution: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "S5/S6 输出清晰度(默认:720p)",
|
|
132
|
+
enum: ["720p", "1080p"],
|
|
133
|
+
},
|
|
134
|
+
ratio: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "S6 图片参考或标准文生视频模式的宽高比,例如 9:16、16:9、1:1、4:3、3:4",
|
|
137
|
+
enum: ["16:9", "9:16", "1:1", "4:3", "3:4"],
|
|
138
|
+
},
|
|
139
|
+
generateAudio: {
|
|
140
|
+
type: "boolean",
|
|
141
|
+
description: "S5 极睿Sd专业版是否生成音频(默认:true)",
|
|
142
|
+
},
|
|
143
|
+
webSearchEnabled: {
|
|
144
|
+
type: "boolean",
|
|
145
|
+
description: "S5 极睿Sd专业版是否启用 Agent 增强(默认:false)",
|
|
146
|
+
},
|
|
147
|
+
dryRun: {
|
|
148
|
+
type: "boolean",
|
|
149
|
+
description: "仅执行参数、素材、权限和积分预检,不创建任务、不扣费。适合在正式创建前确认 S5/S6 参数是否可用",
|
|
76
150
|
},
|
|
77
151
|
},
|
|
78
152
|
required: ["prompt"],
|
|
153
|
+
additionalProperties: false,
|
|
154
|
+
},
|
|
155
|
+
outputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
success: { type: "boolean" },
|
|
159
|
+
taskId: { type: "number" },
|
|
160
|
+
route: { type: "string" },
|
|
161
|
+
engineName: { type: "string" },
|
|
162
|
+
taskMode: { type: "string" },
|
|
163
|
+
videoCount: { type: "number" },
|
|
164
|
+
dryRun: { type: "boolean" },
|
|
165
|
+
estimatedCredits: { type: "number" },
|
|
166
|
+
remainingCredits: { type: "number" },
|
|
167
|
+
statusHint: { type: "string" },
|
|
168
|
+
validationWarnings: {
|
|
169
|
+
type: "array",
|
|
170
|
+
items: { type: "string" },
|
|
171
|
+
},
|
|
172
|
+
code: { type: "string" },
|
|
173
|
+
message: { type: "string" },
|
|
174
|
+
},
|
|
175
|
+
required: ["success"],
|
|
79
176
|
},
|
|
80
177
|
},
|
|
81
178
|
{
|
|
82
179
|
name: "get_video_status",
|
|
180
|
+
title: "查询视频生成状态",
|
|
83
181
|
description: "获取视频生成任务的状态,包括进度和视频 URL",
|
|
84
182
|
inputSchema: {
|
|
85
183
|
type: "object",
|
|
@@ -91,9 +189,40 @@ export function createServer(token, apiBaseUrl) {
|
|
|
91
189
|
},
|
|
92
190
|
required: ["taskId"],
|
|
93
191
|
},
|
|
192
|
+
outputSchema: {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: {
|
|
195
|
+
success: { type: "boolean" },
|
|
196
|
+
taskId: { type: "number" },
|
|
197
|
+
status: { type: "string" },
|
|
198
|
+
progress: { type: "number" },
|
|
199
|
+
totalVideos: { type: "number" },
|
|
200
|
+
completedVideos: { type: "number" },
|
|
201
|
+
code: { type: "string" },
|
|
202
|
+
message: { type: "string" },
|
|
203
|
+
route: { type: "string" },
|
|
204
|
+
engineName: { type: "string" },
|
|
205
|
+
videos: {
|
|
206
|
+
type: "array",
|
|
207
|
+
items: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
id: { type: "number" },
|
|
211
|
+
status: { type: "string" },
|
|
212
|
+
videoUrl: { type: ["string", "null"] },
|
|
213
|
+
thumbnailUrl: { type: ["string", "null"] },
|
|
214
|
+
errorMessage: { type: ["string", "null"] },
|
|
215
|
+
},
|
|
216
|
+
required: ["id", "status", "videoUrl", "thumbnailUrl", "errorMessage"],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
required: ["success"],
|
|
221
|
+
},
|
|
94
222
|
},
|
|
95
223
|
{
|
|
96
224
|
name: "create_video_analysis_task",
|
|
225
|
+
title: "创建视频拆解任务",
|
|
97
226
|
description: "创建视频拆解任务,将视频拆解为脚本、风格、卖点话术、爆款公式,并生成用于视频创作的提示词。" +
|
|
98
227
|
"支持本地文件路径或公网 URL 作为视频文件。本地文件会被自动转换为 base64。",
|
|
99
228
|
inputSchema: {
|
|
@@ -106,9 +235,23 @@ export function createServer(token, apiBaseUrl) {
|
|
|
106
235
|
},
|
|
107
236
|
required: ["videoFile"],
|
|
108
237
|
},
|
|
238
|
+
outputSchema: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
success: { type: "boolean" },
|
|
242
|
+
analysisId: { type: "number" },
|
|
243
|
+
creditsUsed: { type: "number" },
|
|
244
|
+
remainingCredits: { type: "number" },
|
|
245
|
+
statusHint: { type: "string" },
|
|
246
|
+
code: { type: "string" },
|
|
247
|
+
message: { type: "string" },
|
|
248
|
+
},
|
|
249
|
+
required: ["success"],
|
|
250
|
+
},
|
|
109
251
|
},
|
|
110
252
|
{
|
|
111
253
|
name: "get_video_analysis_status",
|
|
254
|
+
title: "查询视频拆解状态",
|
|
112
255
|
description: "获取视频拆解任务的状态和结果",
|
|
113
256
|
inputSchema: {
|
|
114
257
|
type: "object",
|
|
@@ -120,6 +263,26 @@ export function createServer(token, apiBaseUrl) {
|
|
|
120
263
|
},
|
|
121
264
|
required: ["analysisId"],
|
|
122
265
|
},
|
|
266
|
+
outputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
success: { type: "boolean" },
|
|
270
|
+
analysisId: { type: "number" },
|
|
271
|
+
status: { type: "string" },
|
|
272
|
+
conversionProgress: { type: ["number", "null"] },
|
|
273
|
+
script: { type: ["string", "null"] },
|
|
274
|
+
style: { type: ["string", "null"] },
|
|
275
|
+
sellingPoints: { type: ["string", "null"] },
|
|
276
|
+
viralFormula: { type: ["string", "null"] },
|
|
277
|
+
generatedPrompt: { type: ["string", "null"] },
|
|
278
|
+
errorMessage: { type: ["string", "null"] },
|
|
279
|
+
originalVideoUrl: { type: ["string", "null"] },
|
|
280
|
+
videoFileName: { type: ["string", "null"] },
|
|
281
|
+
code: { type: "string" },
|
|
282
|
+
message: { type: "string" },
|
|
283
|
+
},
|
|
284
|
+
required: ["success"],
|
|
285
|
+
},
|
|
123
286
|
},
|
|
124
287
|
];
|
|
125
288
|
return { tools };
|
|
@@ -147,6 +310,7 @@ export function createServer(token, apiBaseUrl) {
|
|
|
147
310
|
}
|
|
148
311
|
catch (error) {
|
|
149
312
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
313
|
+
const toolError = error instanceof McpToolError ? error : undefined;
|
|
150
314
|
return {
|
|
151
315
|
content: [
|
|
152
316
|
{
|
|
@@ -154,6 +318,13 @@ export function createServer(token, apiBaseUrl) {
|
|
|
154
318
|
text: `错误:${errorMessage}`,
|
|
155
319
|
},
|
|
156
320
|
],
|
|
321
|
+
structuredContent: {
|
|
322
|
+
success: false,
|
|
323
|
+
code: toolError?.code || "INTERNAL_ERROR",
|
|
324
|
+
message: errorMessage,
|
|
325
|
+
route: toolError?.route,
|
|
326
|
+
engineName: toolError?.engineName,
|
|
327
|
+
},
|
|
157
328
|
isError: true,
|
|
158
329
|
};
|
|
159
330
|
}
|
|
@@ -182,13 +353,23 @@ async function handleGetUserInfo(apiBaseUrl, token) {
|
|
|
182
353
|
text: userInfo,
|
|
183
354
|
},
|
|
184
355
|
],
|
|
356
|
+
structuredContent: {
|
|
357
|
+
success: true,
|
|
358
|
+
id: response.id,
|
|
359
|
+
username: response.username,
|
|
360
|
+
name: response.name,
|
|
361
|
+
email: response.email,
|
|
362
|
+
role: response.role,
|
|
363
|
+
credits: response.credits,
|
|
364
|
+
isVip: Boolean(response.isVip),
|
|
365
|
+
},
|
|
185
366
|
};
|
|
186
367
|
}
|
|
187
368
|
/**
|
|
188
369
|
* 处理文件输入:支持本地文件路径或公网 URL
|
|
189
370
|
* 对于本地文件,读取并转换为 base64
|
|
190
371
|
*/
|
|
191
|
-
async function processFileInput(fileInput) {
|
|
372
|
+
async function processFileInput(fileInput, expectedType) {
|
|
192
373
|
// 检查是否是公网 URL
|
|
193
374
|
if (fileInput.startsWith("http://") || fileInput.startsWith("https://")) {
|
|
194
375
|
return {
|
|
@@ -202,7 +383,7 @@ async function processFileInput(fileInput) {
|
|
|
202
383
|
const filePath = fileInput;
|
|
203
384
|
// 检查文件是否存在
|
|
204
385
|
if (!existsSync(filePath)) {
|
|
205
|
-
throw new
|
|
386
|
+
throw new McpToolError(`文件不存在: ${filePath}`, "FILE_NOT_FOUND");
|
|
206
387
|
}
|
|
207
388
|
// 读取文件
|
|
208
389
|
const buffer = readFileSync(filePath);
|
|
@@ -217,11 +398,22 @@ async function processFileInput(fileInput) {
|
|
|
217
398
|
".webp": "image/webp",
|
|
218
399
|
".mp4": "video/mp4",
|
|
219
400
|
".mov": "video/quicktime",
|
|
220
|
-
".
|
|
221
|
-
".
|
|
401
|
+
".mp3": "audio/mpeg",
|
|
402
|
+
".wav": "audio/wav",
|
|
222
403
|
};
|
|
223
|
-
if (ext in mimeTypes) {
|
|
224
|
-
|
|
404
|
+
if (!(ext in mimeTypes)) {
|
|
405
|
+
throw new McpToolError(`不支持的文件类型:${ext || "无扩展名"}。请使用 jpg/png/webp/gif 图片、mp4/mov 视频或 mp3/wav 音频。`, "UNSUPPORTED_FILE_TYPE");
|
|
406
|
+
}
|
|
407
|
+
mimeType = mimeTypes[ext];
|
|
408
|
+
if (expectedType && !mimeType.startsWith(`${expectedType}/`)) {
|
|
409
|
+
throw new McpToolError(`文件 ${basename(filePath)} 类型不匹配:当前是 ${mimeType},但该字段需要 ${expectedType} 文件。`, "INVALID_FILE_TYPE");
|
|
410
|
+
}
|
|
411
|
+
const size = statSync(filePath).size;
|
|
412
|
+
if (expectedType === "video" && size > 50 * 1024 * 1024) {
|
|
413
|
+
throw new McpToolError(`视频文件 ${basename(filePath)} 超过 50MB 限制。`, "MEDIA_FILE_TOO_LARGE");
|
|
414
|
+
}
|
|
415
|
+
if (expectedType === "audio" && size > 15 * 1024 * 1024) {
|
|
416
|
+
throw new McpToolError(`音频文件 ${basename(filePath)} 超过 15MB 限制。`, "MEDIA_FILE_TOO_LARGE");
|
|
225
417
|
}
|
|
226
418
|
// 转换为 base64
|
|
227
419
|
const base64 = `data:${mimeType};base64,${buffer.toString("base64")}`;
|
|
@@ -232,38 +424,216 @@ async function processFileInput(fileInput) {
|
|
|
232
424
|
isUrl: false,
|
|
233
425
|
};
|
|
234
426
|
}
|
|
427
|
+
async function processFileInputs(fileInputs, expectedType) {
|
|
428
|
+
if (!Array.isArray(fileInputs) || fileInputs.length === 0) {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
const processed = await Promise.all(fileInputs
|
|
432
|
+
.filter((fileInput) => typeof fileInput === "string" && fileInput.length > 0)
|
|
433
|
+
.map(async (fileInput) => (await processFileInput(fileInput, expectedType)).data));
|
|
434
|
+
return processed.length > 0 ? processed : undefined;
|
|
435
|
+
}
|
|
436
|
+
function hasItems(value) {
|
|
437
|
+
return Array.isArray(value) && value.some((item) => typeof item === "string" && item.length > 0);
|
|
438
|
+
}
|
|
439
|
+
function itemCount(value) {
|
|
440
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0).length : 0;
|
|
441
|
+
}
|
|
442
|
+
function normalizeRoute(value) {
|
|
443
|
+
const route = typeof value === "string" ? value : "S2";
|
|
444
|
+
if (!isMcpRoute(route)) {
|
|
445
|
+
throw new McpToolError("route 只能是 S2、S5 或 S6。S2=极睿Sa专业版,S5=极睿Sd专业版,S6=极睿Ha专业版。", "INVALID_ROUTE");
|
|
446
|
+
}
|
|
447
|
+
return route;
|
|
448
|
+
}
|
|
449
|
+
function normalizeTaskMode(route, value) {
|
|
450
|
+
const mode = typeof value === "string" ? value : getMcpDefaultTaskMode(route);
|
|
451
|
+
if (route === "S2") {
|
|
452
|
+
if (value !== undefined) {
|
|
453
|
+
throw new McpToolError("taskMode 仅适用于极睿Sd专业版或极睿Ha专业版;S2 极睿Sa专业版无需传 taskMode。", "UNSUPPORTED_FIELD", route);
|
|
454
|
+
}
|
|
455
|
+
return getMcpDefaultTaskMode(route);
|
|
456
|
+
}
|
|
457
|
+
if (!isValidMcpTaskMode(route, mode)) {
|
|
458
|
+
const allowedModes = route === "S5" ? "allinone 或 firstlast" : "standard、reference 或 edit";
|
|
459
|
+
throw new McpToolError(`${getMcpEngineName(route)} 的 taskMode 只能是 ${allowedModes}。`, "INVALID_TASK_MODE", route);
|
|
460
|
+
}
|
|
461
|
+
return mode;
|
|
462
|
+
}
|
|
463
|
+
function validateCreateVideoArgs(args) {
|
|
464
|
+
if (!args || typeof args !== "object") {
|
|
465
|
+
throw new McpToolError("请提供视频生成参数。");
|
|
466
|
+
}
|
|
467
|
+
if (!args.prompt || typeof args.prompt !== "string" || !args.prompt.trim()) {
|
|
468
|
+
throw new McpToolError("prompt 不能为空。");
|
|
469
|
+
}
|
|
470
|
+
if (args.count !== undefined && (!Number.isInteger(args.count) || args.count < 1 || args.count > 12)) {
|
|
471
|
+
throw new McpToolError("count 必须是 1-12 的整数。", "INVALID_COUNT");
|
|
472
|
+
}
|
|
473
|
+
const route = normalizeRoute(args.route);
|
|
474
|
+
const taskMode = normalizeTaskMode(route, args.taskMode);
|
|
475
|
+
const resolution = typeof args.resolution === "string" ? args.resolution : "720p";
|
|
476
|
+
const dryRun = args.dryRun === true;
|
|
477
|
+
if (args.inputDurationTier !== undefined) {
|
|
478
|
+
throw new McpToolError("inputDurationTier 不再支持由 MCP 传入;输入视频时长档位由服务端根据素材自动计算。", "UNSUPPORTED_FIELD", route);
|
|
479
|
+
}
|
|
480
|
+
if ((route === "S5" || route === "S6") && resolution !== "720p" && resolution !== "1080p") {
|
|
481
|
+
throw new McpToolError(`${getMcpEngineName(route)} 仅支持 720p 或 1080p。`, "INVALID_RESOLUTION", route);
|
|
482
|
+
}
|
|
483
|
+
if (route === "S2" && args.seconds === 5) {
|
|
484
|
+
throw new McpToolError("极睿Sa专业版不支持 5 秒视频,请使用 10 或 15 秒。", "INVALID_SECONDS", route);
|
|
485
|
+
}
|
|
486
|
+
if (route === "S2") {
|
|
487
|
+
const unsupported = [
|
|
488
|
+
hasItems(args.sourceImages) ? "sourceImages" : undefined,
|
|
489
|
+
hasItems(args.sourceVideos) ? "sourceVideos" : undefined,
|
|
490
|
+
hasItems(args.sourceAudios) ? "sourceAudios" : undefined,
|
|
491
|
+
args.resolution !== undefined ? "resolution" : undefined,
|
|
492
|
+
args.ratio !== undefined ? "ratio" : undefined,
|
|
493
|
+
args.generateAudio !== undefined ? "generateAudio" : undefined,
|
|
494
|
+
args.webSearchEnabled !== undefined ? "webSearchEnabled" : undefined,
|
|
495
|
+
].filter(Boolean);
|
|
496
|
+
if (unsupported.length > 0) {
|
|
497
|
+
throw new McpToolError(`这些字段不适用于极睿Sa专业版:${unsupported.join("、")}。S2 单图请使用 sourceImage。`, "UNSUPPORTED_FIELD", route);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (route === "S5") {
|
|
501
|
+
if (args.sourceImage !== undefined) {
|
|
502
|
+
throw new McpToolError("sourceImage 不适用于极睿Sd专业版;请使用 sourceImages 传参考图或首尾帧。", "UNSUPPORTED_FIELD", route);
|
|
503
|
+
}
|
|
504
|
+
if (args.ratio !== undefined) {
|
|
505
|
+
throw new McpToolError("ratio 仅适用于极睿Ha专业版;极睿Sd专业版请使用 size 控制画面比例。", "UNSUPPORTED_FIELD", route);
|
|
506
|
+
}
|
|
507
|
+
if (taskMode === "firstlast" && !hasItems(args.sourceImages)) {
|
|
508
|
+
throw new McpToolError("极睿Sd专业版首尾帧模式需要在 sourceImages 中提供至少 1 张首帧图片。", "MISSING_SOURCE_IMAGE", route);
|
|
509
|
+
}
|
|
510
|
+
if (taskMode === "firstlast" && (hasItems(args.sourceVideos) || hasItems(args.sourceAudios))) {
|
|
511
|
+
throw new McpToolError("sourceVideos/sourceAudios 仅适用于极睿Sd专业版全能参考模式;首尾帧模式只使用 sourceImages。", "UNSUPPORTED_FIELD", route);
|
|
512
|
+
}
|
|
513
|
+
if (taskMode === "firstlast" && itemCount(args.sourceImages) > 2) {
|
|
514
|
+
throw new McpToolError("极睿Sd专业版首尾帧模式最多支持 2 张图片。", "MEDIA_COUNT_EXCEEDED", route);
|
|
515
|
+
}
|
|
516
|
+
if (taskMode === "allinone" && itemCount(args.sourceImages) > 9) {
|
|
517
|
+
throw new McpToolError("极睿Sd专业版全能参考模式最多支持 9 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
|
|
518
|
+
}
|
|
519
|
+
if (taskMode === "allinone" && itemCount(args.sourceVideos) > 3) {
|
|
520
|
+
throw new McpToolError("极睿Sd专业版全能参考模式最多支持 3 个参考视频。", "MEDIA_COUNT_EXCEEDED", route);
|
|
521
|
+
}
|
|
522
|
+
if (taskMode === "allinone" && itemCount(args.sourceAudios) > 3) {
|
|
523
|
+
throw new McpToolError("极睿Sd专业版全能参考模式最多支持 3 个参考音频。", "MEDIA_COUNT_EXCEEDED", route);
|
|
524
|
+
}
|
|
525
|
+
if (taskMode === "allinone" && hasItems(args.sourceAudios) && !hasItems(args.sourceImages) && !hasItems(args.sourceVideos)) {
|
|
526
|
+
throw new McpToolError("极睿Sd专业版全能参考模式中,音频素材必须搭配至少一张参考图片或一个参考视频。", "MISSING_VISUAL_REFERENCE", route);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (route === "S6") {
|
|
530
|
+
const unsupported = [
|
|
531
|
+
hasItems(args.sourceAudios) ? "sourceAudios" : undefined,
|
|
532
|
+
args.generateAudio !== undefined ? "generateAudio" : undefined,
|
|
533
|
+
args.webSearchEnabled !== undefined ? "webSearchEnabled" : undefined,
|
|
534
|
+
].filter(Boolean);
|
|
535
|
+
if (unsupported.length > 0) {
|
|
536
|
+
throw new McpToolError(`这些字段仅适用于极睿Sd专业版,不能用于极睿Ha专业版:${unsupported.join("、")}。`, "UNSUPPORTED_FIELD", route);
|
|
537
|
+
}
|
|
538
|
+
if (taskMode === "standard" && (hasItems(args.sourceImages) || hasItems(args.sourceVideos))) {
|
|
539
|
+
throw new McpToolError("极睿Ha专业版标准模式只支持 sourceImage 作为单图输入;多图请使用 reference 模式,视频请使用 edit 模式。", "UNSUPPORTED_FIELD", route);
|
|
540
|
+
}
|
|
541
|
+
if ((taskMode === "reference" || taskMode === "edit") && args.sourceImage !== undefined) {
|
|
542
|
+
throw new McpToolError("sourceImage 仅适用于极睿Ha专业版标准模式;reference/edit 模式请使用 sourceImages。", "UNSUPPORTED_FIELD", route);
|
|
543
|
+
}
|
|
544
|
+
if (taskMode === "reference" && !hasItems(args.sourceImages)) {
|
|
545
|
+
throw new McpToolError("极睿Ha专业版图片参考模式需要在 sourceImages 中提供至少 1 张参考图片。", "MISSING_SOURCE_IMAGE", route);
|
|
546
|
+
}
|
|
547
|
+
if (taskMode === "edit" && !hasItems(args.sourceVideos)) {
|
|
548
|
+
throw new McpToolError("极睿Ha专业版视频编辑模式需要在 sourceVideos 中提供至少 1 个参考视频。", "MISSING_SOURCE_VIDEO", route);
|
|
549
|
+
}
|
|
550
|
+
if (taskMode === "reference" && itemCount(args.sourceImages) > 9) {
|
|
551
|
+
throw new McpToolError("极睿Ha专业版图片参考模式最多支持 9 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
|
|
552
|
+
}
|
|
553
|
+
if (taskMode === "edit" && itemCount(args.sourceVideos) > 1) {
|
|
554
|
+
throw new McpToolError("极睿Ha专业版视频编辑模式最多支持 1 个参考视频。", "MEDIA_COUNT_EXCEEDED", route);
|
|
555
|
+
}
|
|
556
|
+
if (taskMode === "edit" && itemCount(args.sourceImages) > 5) {
|
|
557
|
+
throw new McpToolError("极睿Ha专业版视频编辑模式最多支持 5 张参考图片。", "MEDIA_COUNT_EXCEEDED", route);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
route,
|
|
562
|
+
taskMode,
|
|
563
|
+
resolution,
|
|
564
|
+
dryRun,
|
|
565
|
+
statusHint: getMcpVideoStatusHint(dryRun),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
235
568
|
/**
|
|
236
569
|
* Handle create_video_task tool call
|
|
237
570
|
*/
|
|
238
571
|
async function handleCreateVideoTask(apiBaseUrl, token, args) {
|
|
572
|
+
const normalized = validateCreateVideoArgs(args);
|
|
239
573
|
const response = await callRestApi(apiBaseUrl, "/mcp/video/create", {
|
|
240
574
|
token,
|
|
241
575
|
prompt: args.prompt,
|
|
242
|
-
sourceImage: args.sourceImage ? (await processFileInput(args.sourceImage)).data : undefined,
|
|
576
|
+
sourceImage: args.sourceImage ? (await processFileInput(args.sourceImage, "image")).data : undefined,
|
|
577
|
+
sourceImages: await processFileInputs(args.sourceImages, "image"),
|
|
578
|
+
sourceVideos: await processFileInputs(args.sourceVideos, "video"),
|
|
579
|
+
sourceAudios: await processFileInputs(args.sourceAudios, "audio"),
|
|
243
580
|
size: args.size || "1280x720",
|
|
244
581
|
seconds: args.seconds || 10,
|
|
245
582
|
language: args.language || "zh-CN",
|
|
246
583
|
count: args.count || 1,
|
|
247
|
-
route:
|
|
584
|
+
route: normalized.route,
|
|
585
|
+
taskMode: normalized.taskMode,
|
|
586
|
+
isFirstLastMode: normalized.taskMode === "firstlast",
|
|
587
|
+
resolution: normalized.resolution,
|
|
588
|
+
ratio: args.ratio || undefined,
|
|
589
|
+
generateAudio: args.generateAudio,
|
|
590
|
+
webSearchEnabled: args.webSearchEnabled,
|
|
591
|
+
dryRun: normalized.dryRun,
|
|
248
592
|
}, "POST");
|
|
593
|
+
const route = response.route || normalized.route;
|
|
594
|
+
const engineName = response.engineName || getMcpEngineName(route);
|
|
595
|
+
const taskMode = response.taskMode || normalized.taskMode;
|
|
596
|
+
const estimatedCredits = response.estimatedCredits ?? response.creditsUsed;
|
|
597
|
+
const dryRun = response.dryRun === true || normalized.dryRun;
|
|
598
|
+
const statusHint = response.statusHint || normalized.statusHint;
|
|
249
599
|
const result = [
|
|
250
|
-
`# 视频生成任务已创建`,
|
|
600
|
+
dryRun ? `# 视频生成参数预检通过` : `# 视频生成任务已创建`,
|
|
251
601
|
``,
|
|
252
|
-
`-
|
|
602
|
+
`- **生成引擎**: ${engineName}`,
|
|
603
|
+
`- **模式**: ${taskMode}`,
|
|
253
604
|
`- **消息**: ${response.message}`,
|
|
254
605
|
`- **生成视频数量**: ${response.videoCount}`,
|
|
255
|
-
`-
|
|
256
|
-
`-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
606
|
+
`- **预估积分**: ${estimatedCredits ?? "N/A"}(视频完成后按成功扣费)`,
|
|
607
|
+
`- **当前积分**: ${response.remainingCredits}`,
|
|
608
|
+
];
|
|
609
|
+
if (!dryRun && response.taskId !== undefined) {
|
|
610
|
+
result.splice(4, 0, `- **任务 ID**: ${response.taskId}`);
|
|
611
|
+
}
|
|
612
|
+
if (response.validationWarnings?.length) {
|
|
613
|
+
result.push(`- **预检提示**: ${response.validationWarnings.join(";")}`);
|
|
614
|
+
}
|
|
615
|
+
result.push(``, statusHint);
|
|
260
616
|
return {
|
|
261
617
|
content: [
|
|
262
618
|
{
|
|
263
619
|
type: "text",
|
|
264
|
-
text: result,
|
|
620
|
+
text: result.join("\n"),
|
|
265
621
|
},
|
|
266
622
|
],
|
|
623
|
+
structuredContent: {
|
|
624
|
+
success: response.success,
|
|
625
|
+
taskId: response.taskId,
|
|
626
|
+
route,
|
|
627
|
+
engineName,
|
|
628
|
+
taskMode,
|
|
629
|
+
videoCount: response.videoCount,
|
|
630
|
+
dryRun,
|
|
631
|
+
estimatedCredits,
|
|
632
|
+
remainingCredits: response.remainingCredits,
|
|
633
|
+
statusHint,
|
|
634
|
+
validationWarnings: response.validationWarnings || [],
|
|
635
|
+
message: response.message,
|
|
636
|
+
},
|
|
267
637
|
};
|
|
268
638
|
}
|
|
269
639
|
/**
|
|
@@ -308,6 +678,15 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
|
308
678
|
text: result.join("\n"),
|
|
309
679
|
},
|
|
310
680
|
],
|
|
681
|
+
structuredContent: {
|
|
682
|
+
success: true,
|
|
683
|
+
taskId: response.taskId,
|
|
684
|
+
status: response.status,
|
|
685
|
+
progress: response.progress,
|
|
686
|
+
totalVideos: response.totalVideos,
|
|
687
|
+
completedVideos: response.completedVideos,
|
|
688
|
+
videos: response.videos || [],
|
|
689
|
+
},
|
|
311
690
|
};
|
|
312
691
|
}
|
|
313
692
|
/**
|
|
@@ -316,7 +695,7 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
|
316
695
|
async function handleCreateVideoAnalysisTask(apiBaseUrl, token, args) {
|
|
317
696
|
const response = await callRestApi(apiBaseUrl, "/mcp/video-analysis/create", {
|
|
318
697
|
token,
|
|
319
|
-
videoFile: (await processFileInput(args.videoFile)).data,
|
|
698
|
+
videoFile: (await processFileInput(args.videoFile, "video")).data,
|
|
320
699
|
}, "POST");
|
|
321
700
|
const result = [
|
|
322
701
|
`# 视频拆解任务已创建`,
|
|
@@ -335,6 +714,14 @@ async function handleCreateVideoAnalysisTask(apiBaseUrl, token, args) {
|
|
|
335
714
|
text: result,
|
|
336
715
|
},
|
|
337
716
|
],
|
|
717
|
+
structuredContent: {
|
|
718
|
+
success: response.success,
|
|
719
|
+
analysisId: response.analysisId,
|
|
720
|
+
message: response.message,
|
|
721
|
+
creditsUsed: response.creditsUsed,
|
|
722
|
+
remainingCredits: response.remainingCredits,
|
|
723
|
+
statusHint: `使用分析 ID ${response.analysisId} 调用 get_video_analysis_status 查看进度。`,
|
|
724
|
+
},
|
|
338
725
|
};
|
|
339
726
|
}
|
|
340
727
|
/**
|
|
@@ -402,6 +789,20 @@ async function handleGetVideoAnalysisStatus(apiBaseUrl, token, args) {
|
|
|
402
789
|
text: result.join("\n"),
|
|
403
790
|
},
|
|
404
791
|
],
|
|
792
|
+
structuredContent: {
|
|
793
|
+
success: true,
|
|
794
|
+
analysisId: response.analysisId,
|
|
795
|
+
status: response.status,
|
|
796
|
+
conversionProgress: response.conversionProgress,
|
|
797
|
+
script: response.script,
|
|
798
|
+
style: response.style,
|
|
799
|
+
sellingPoints: response.sellingPoints,
|
|
800
|
+
viralFormula: response.viralFormula,
|
|
801
|
+
generatedPrompt: response.generatedPrompt,
|
|
802
|
+
errorMessage: response.errorMessage,
|
|
803
|
+
originalVideoUrl: response.originalVideoUrl,
|
|
804
|
+
videoFileName: response.videoFileName,
|
|
805
|
+
},
|
|
405
806
|
};
|
|
406
807
|
}
|
|
407
808
|
/**
|
|
@@ -432,6 +833,22 @@ async function callRestApi(apiBaseUrl, path, params, method = "GET") {
|
|
|
432
833
|
});
|
|
433
834
|
if (!response.ok) {
|
|
434
835
|
const errorText = await response.text();
|
|
836
|
+
try {
|
|
837
|
+
const parsed = JSON.parse(errorText);
|
|
838
|
+
const apiError = parsed?.error;
|
|
839
|
+
if (apiError?.message) {
|
|
840
|
+
const toolError = new McpToolError(apiError.message, apiError.code || `HTTP_${response.status}`, apiError.route);
|
|
841
|
+
if (apiError.engineName) {
|
|
842
|
+
toolError.engineName = apiError.engineName;
|
|
843
|
+
}
|
|
844
|
+
throw toolError;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
catch (error) {
|
|
848
|
+
if (error instanceof McpToolError) {
|
|
849
|
+
throw error;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
435
852
|
throw new Error(`API 请求失败,状态码 ${response.status}: ${errorText}`);
|
|
436
853
|
}
|
|
437
854
|
const data = await response.json();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type McpRoute = "S2" | "S5" | "S6";
|
|
2
|
+
export type McpTaskMode = "allinone" | "firstlast" | "standard" | "reference" | "edit";
|
|
3
|
+
export declare const MCP_SERVER_VERSION = "1.0.5";
|
|
4
|
+
export declare const MCP_VIDEO_STATUS_HINT = "\u4EFB\u52A1\u5DF2\u521B\u5EFA\u540E\uFF0C\u4F7F\u7528 get_video_status \u5E76\u4F20\u5165 taskId \u67E5\u8BE2\u751F\u6210\u8FDB\u5EA6\u548C\u89C6\u9891\u94FE\u63A5\u3002";
|
|
5
|
+
export declare const MCP_VIDEO_DRY_RUN_HINT = "dryRun \u4EC5\u5B8C\u6210\u53C2\u6570\u3001\u6743\u9650\u3001\u79EF\u5206\u548C\u7D20\u6750\u89C4\u5219\u9884\u68C0\uFF0C\u4E0D\u4F1A\u521B\u5EFA\u4EFB\u52A1\u6216\u6263\u8D39\u3002";
|
|
6
|
+
export declare const MCP_VIDEO_ENGINES: Record<McpRoute, {
|
|
7
|
+
engineName: string;
|
|
8
|
+
model: string;
|
|
9
|
+
defaultTaskMode: McpTaskMode;
|
|
10
|
+
taskModes: McpTaskMode[];
|
|
11
|
+
}>;
|
|
12
|
+
export declare function isMcpRoute(value: string): value is McpRoute;
|
|
13
|
+
export declare function getMcpEngineName(route: string): string;
|
|
14
|
+
export declare function getMcpTaskModel(route: McpRoute): string;
|
|
15
|
+
export declare function getMcpDefaultTaskMode(route: McpRoute, isFirstLastMode?: boolean): McpTaskMode;
|
|
16
|
+
export declare function isValidMcpTaskMode(route: McpRoute, taskMode: string): taskMode is McpTaskMode;
|
|
17
|
+
export declare function getMcpVideoStatusHint(dryRun?: boolean): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const MCP_SERVER_VERSION = "1.0.5";
|
|
2
|
+
export const MCP_VIDEO_STATUS_HINT = "任务已创建后,使用 get_video_status 并传入 taskId 查询生成进度和视频链接。";
|
|
3
|
+
export const MCP_VIDEO_DRY_RUN_HINT = "dryRun 仅完成参数、权限、积分和素材规则预检,不会创建任务或扣费。";
|
|
4
|
+
export const MCP_VIDEO_ENGINES = {
|
|
5
|
+
S2: {
|
|
6
|
+
engineName: "极睿Sa专业版",
|
|
7
|
+
model: "sora-2",
|
|
8
|
+
defaultTaskMode: "standard",
|
|
9
|
+
taskModes: ["standard"],
|
|
10
|
+
},
|
|
11
|
+
S5: {
|
|
12
|
+
engineName: "极睿Sd专业版",
|
|
13
|
+
model: "doubao-seedance-2-0-260128",
|
|
14
|
+
defaultTaskMode: "allinone",
|
|
15
|
+
taskModes: ["allinone", "firstlast"],
|
|
16
|
+
},
|
|
17
|
+
S6: {
|
|
18
|
+
engineName: "极睿Ha专业版",
|
|
19
|
+
model: "happyhorse-1.0",
|
|
20
|
+
defaultTaskMode: "standard",
|
|
21
|
+
taskModes: ["standard", "reference", "edit"],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export function isMcpRoute(value) {
|
|
25
|
+
return value === "S2" || value === "S5" || value === "S6";
|
|
26
|
+
}
|
|
27
|
+
export function getMcpEngineName(route) {
|
|
28
|
+
return isMcpRoute(route) ? MCP_VIDEO_ENGINES[route].engineName : MCP_VIDEO_ENGINES.S2.engineName;
|
|
29
|
+
}
|
|
30
|
+
export function getMcpTaskModel(route) {
|
|
31
|
+
return MCP_VIDEO_ENGINES[route].model;
|
|
32
|
+
}
|
|
33
|
+
export function getMcpDefaultTaskMode(route, isFirstLastMode) {
|
|
34
|
+
if (route === "S5" && isFirstLastMode === true) {
|
|
35
|
+
return "firstlast";
|
|
36
|
+
}
|
|
37
|
+
return MCP_VIDEO_ENGINES[route].defaultTaskMode;
|
|
38
|
+
}
|
|
39
|
+
export function isValidMcpTaskMode(route, taskMode) {
|
|
40
|
+
return MCP_VIDEO_ENGINES[route].taskModes.includes(taskMode);
|
|
41
|
+
}
|
|
42
|
+
export function getMcpVideoStatusHint(dryRun) {
|
|
43
|
+
return dryRun ? MCP_VIDEO_DRY_RUN_HINT : MCP_VIDEO_STATUS_HINT;
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infimind/video-mcp-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "MCP Server for Sora AI Video Generator - Access video generation features through Claude Desktop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"dev": "tsc --watch",
|
|
17
|
+
"test:smoke": "node scripts/smoke-test.mjs",
|
|
17
18
|
"prepublishOnly": "npm run build"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|