@infimind/video-mcp-cli 1.0.7 → 1.0.8
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 +55 -16
- package/dist/index.js +11 -6
- package/dist/server.js +70 -2
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -21,7 +21,10 @@ npm install -g @infimind/video-mcp-cli
|
|
|
21
21
|
"mcpServers": {
|
|
22
22
|
"sora-video-generator": {
|
|
23
23
|
"command": "npx",
|
|
24
|
-
"args": ["-y", "@infimind/video-mcp-cli",
|
|
24
|
+
"args": ["-y", "@infimind/video-mcp-cli"],
|
|
25
|
+
"env": {
|
|
26
|
+
"SORA_MCP_TOKEN": "YOUR_TOKEN"
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -48,8 +51,8 @@ Add the following to your Claude Desktop configuration file:
|
|
|
48
51
|
"mcpServers": {
|
|
49
52
|
"sora-video-generator": {
|
|
50
53
|
"command": "sora-mcp",
|
|
51
|
-
"args": ["--token=YOUR_TOKEN_HERE"],
|
|
52
54
|
"env": {
|
|
55
|
+
"SORA_MCP_TOKEN": "YOUR_TOKEN_HERE",
|
|
53
56
|
"SORA_API_URL": "https://your-api-domain.com/api"
|
|
54
57
|
}
|
|
55
58
|
}
|
|
@@ -59,8 +62,11 @@ Add the following to your Claude Desktop configuration file:
|
|
|
59
62
|
|
|
60
63
|
#### Environment Variables
|
|
61
64
|
|
|
65
|
+
- `SORA_MCP_TOKEN` (recommended): Your MCP API token. This avoids exposing the token in process arguments.
|
|
62
66
|
- `SORA_API_URL` (optional): Your API base URL. Defaults to `http://localhost:3000/api`
|
|
63
67
|
|
|
68
|
+
`--token=YOUR_TOKEN` remains supported for backward compatibility. `--token-env=CUSTOM_ENV_NAME` can be used when the token is stored in a different environment variable.
|
|
69
|
+
|
|
64
70
|
### 3. Restart Claude Desktop
|
|
65
71
|
|
|
66
72
|
After saving the configuration, restart Claude Desktop for the changes to take effect.
|
|
@@ -105,6 +111,23 @@ Local file paths and public URLs are both supported. Local files are converted t
|
|
|
105
111
|
|
|
106
112
|
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
113
|
|
|
114
|
+
Media limits:
|
|
115
|
+
|
|
116
|
+
- Local video files: MP4/MOV, up to 50MB each, 2-15 seconds.
|
|
117
|
+
- Local audio files: MP3/WAV, up to 15MB each, 2-15 seconds.
|
|
118
|
+
- S5 allinone: up to 9 images, 3 videos, and 3 audio files.
|
|
119
|
+
- S5 firstlast: up to 2 images; the first frame is required.
|
|
120
|
+
- S6 reference: up to 9 images.
|
|
121
|
+
- S6 edit: up to 1 video and 5 images.
|
|
122
|
+
- `inputDurationTier` is not accepted from clients; the server calculates it from the media.
|
|
123
|
+
|
|
124
|
+
URL safety rules:
|
|
125
|
+
|
|
126
|
+
- Only `http` and `https` URLs are accepted.
|
|
127
|
+
- `localhost`, `127.0.0.1`, private network addresses, reserved IP ranges, and `file:` URLs are rejected.
|
|
128
|
+
- Redirect targets are revalidated.
|
|
129
|
+
- Remote files that are too large, not media, or cannot be safely probed are rejected.
|
|
130
|
+
|
|
108
131
|
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
132
|
|
|
110
133
|
#### Dry run before creating a task
|
|
@@ -132,7 +155,8 @@ Use `dryRun: true` to validate parameters, VIP access, estimated credits, and me
|
|
|
132
155
|
"seconds": 10,
|
|
133
156
|
"size": "720x1280",
|
|
134
157
|
"language": "zh-CN",
|
|
135
|
-
"count": 1
|
|
158
|
+
"count": 1,
|
|
159
|
+
"dryRun": true
|
|
136
160
|
}
|
|
137
161
|
```
|
|
138
162
|
|
|
@@ -150,7 +174,8 @@ Use `dryRun: true` to validate parameters, VIP access, estimated credits, and me
|
|
|
150
174
|
"size": "720x1280",
|
|
151
175
|
"resolution": "720p",
|
|
152
176
|
"generateAudio": true,
|
|
153
|
-
"webSearchEnabled": false
|
|
177
|
+
"webSearchEnabled": false,
|
|
178
|
+
"dryRun": true
|
|
154
179
|
}
|
|
155
180
|
```
|
|
156
181
|
|
|
@@ -164,7 +189,8 @@ Use `dryRun: true` to validate parameters, VIP access, estimated credits, and me
|
|
|
164
189
|
"sourceImages": ["/path/to/first-frame.jpg", "/path/to/end-frame.jpg"],
|
|
165
190
|
"seconds": 5,
|
|
166
191
|
"size": "1280x720",
|
|
167
|
-
"resolution": "720p"
|
|
192
|
+
"resolution": "720p",
|
|
193
|
+
"dryRun": true
|
|
168
194
|
}
|
|
169
195
|
```
|
|
170
196
|
|
|
@@ -180,7 +206,8 @@ Text-to-video:
|
|
|
180
206
|
"seconds": 5,
|
|
181
207
|
"size": "1280x720",
|
|
182
208
|
"ratio": "16:9",
|
|
183
|
-
"resolution": "720p"
|
|
209
|
+
"resolution": "720p",
|
|
210
|
+
"dryRun": true
|
|
184
211
|
}
|
|
185
212
|
```
|
|
186
213
|
|
|
@@ -194,7 +221,8 @@ Image-to-video:
|
|
|
194
221
|
"sourceImage": "/path/to/product.jpg",
|
|
195
222
|
"seconds": 5,
|
|
196
223
|
"size": "720x1280",
|
|
197
|
-
"resolution": "720p"
|
|
224
|
+
"resolution": "720p",
|
|
225
|
+
"dryRun": true
|
|
198
226
|
}
|
|
199
227
|
```
|
|
200
228
|
|
|
@@ -209,7 +237,8 @@ Image-to-video:
|
|
|
209
237
|
"seconds": 10,
|
|
210
238
|
"size": "720x1280",
|
|
211
239
|
"ratio": "9:16",
|
|
212
|
-
"resolution": "720p"
|
|
240
|
+
"resolution": "720p",
|
|
241
|
+
"dryRun": true
|
|
213
242
|
}
|
|
214
243
|
```
|
|
215
244
|
|
|
@@ -222,11 +251,12 @@ Image-to-video:
|
|
|
222
251
|
"taskMode": "edit",
|
|
223
252
|
"sourceVideos": ["/path/to/input.mp4"],
|
|
224
253
|
"sourceImages": ["/path/to/reference.jpg"],
|
|
225
|
-
"resolution": "720p"
|
|
254
|
+
"resolution": "720p",
|
|
255
|
+
"dryRun": true
|
|
226
256
|
}
|
|
227
257
|
```
|
|
228
258
|
|
|
229
|
-
After creating a task, use `get_video_status` with the returned `taskId` to query progress and video links.
|
|
259
|
+
After dry run succeeds and `estimatedCredits` looks correct, remove `dryRun` or set it to `false` to create the real task. After creating a task, use `get_video_status` with the returned `taskId` to query progress and video links.
|
|
230
260
|
|
|
231
261
|
## Development
|
|
232
262
|
|
|
@@ -245,7 +275,7 @@ npm run build
|
|
|
245
275
|
npm run test:smoke
|
|
246
276
|
|
|
247
277
|
# Run in development mode
|
|
248
|
-
node dist/index.js
|
|
278
|
+
SORA_MCP_TOKEN=YOUR_TOKEN node dist/index.js
|
|
249
279
|
```
|
|
250
280
|
|
|
251
281
|
### Local MCP testing
|
|
@@ -255,7 +285,7 @@ node dist/index.js --token=YOUR_TOKEN
|
|
|
255
285
|
```bash
|
|
256
286
|
cd mcp-server
|
|
257
287
|
npm run build
|
|
258
|
-
npx @modelcontextprotocol/inspector node dist/index.js
|
|
288
|
+
SORA_MCP_TOKEN=YOUR_TOKEN npx @modelcontextprotocol/inspector node dist/index.js
|
|
259
289
|
```
|
|
260
290
|
|
|
261
291
|
Set `SORA_API_URL=http://localhost:3000/api` when testing against a local app server.
|
|
@@ -267,8 +297,9 @@ Set `SORA_API_URL=http://localhost:3000/api` when testing against a local app se
|
|
|
267
297
|
"mcpServers": {
|
|
268
298
|
"sora-video-generator-local": {
|
|
269
299
|
"command": "node",
|
|
270
|
-
"args": ["/absolute/path/to/sora_video_generator/mcp-server/dist/index.js"
|
|
300
|
+
"args": ["/absolute/path/to/sora_video_generator/mcp-server/dist/index.js"],
|
|
271
301
|
"env": {
|
|
302
|
+
"SORA_MCP_TOKEN": "YOUR_TOKEN",
|
|
272
303
|
"SORA_API_URL": "http://localhost:3000/api"
|
|
273
304
|
}
|
|
274
305
|
}
|
|
@@ -283,8 +314,9 @@ Set `SORA_API_URL=http://localhost:3000/api` when testing against a local app se
|
|
|
283
314
|
"mcpServers": {
|
|
284
315
|
"sora-video-generator": {
|
|
285
316
|
"command": "npx",
|
|
286
|
-
"args": ["-y", "@infimind/video-mcp-cli"
|
|
317
|
+
"args": ["-y", "@infimind/video-mcp-cli"],
|
|
287
318
|
"env": {
|
|
319
|
+
"SORA_MCP_TOKEN": "YOUR_TOKEN",
|
|
288
320
|
"SORA_API_URL": "https://your-api-domain.com/api"
|
|
289
321
|
}
|
|
290
322
|
}
|
|
@@ -298,16 +330,23 @@ Set `SORA_API_URL=http://localhost:3000/api` when testing against a local app se
|
|
|
298
330
|
- If `get_video_status` cannot connect, verify `SORA_API_URL` includes `/api`, for example `http://localhost:3000/api`.
|
|
299
331
|
- If S5/S6 returns VIP errors, confirm the MCP token belongs to a VIP user.
|
|
300
332
|
- 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
|
|
333
|
+
- If local file inputs fail, use absolute paths and supported media types: jpg/png/webp/gif, mp4/mov, mp3/wav.
|
|
302
334
|
|
|
303
335
|
## Publishing
|
|
304
336
|
|
|
305
337
|
```bash
|
|
306
|
-
# Build
|
|
338
|
+
# Build and verify local dist
|
|
307
339
|
npm run build
|
|
340
|
+
npm run test:smoke
|
|
341
|
+
|
|
342
|
+
# Verify the package contents that npm will publish
|
|
343
|
+
npm run test:smoke:package
|
|
308
344
|
|
|
309
345
|
# Publish to npm
|
|
310
346
|
npm publish --access public
|
|
347
|
+
|
|
348
|
+
# Optional after publishing: verify latest through npx
|
|
349
|
+
npm run test:smoke:published
|
|
311
350
|
```
|
|
312
351
|
|
|
313
352
|
## Security
|
package/dist/index.js
CHANGED
|
@@ -4,23 +4,28 @@ import { createServer } from "./server.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* CLI Entry Point for Sora MCP Server
|
|
6
6
|
*
|
|
7
|
-
* Usage: sora-mcp
|
|
7
|
+
* Usage: sora-mcp
|
|
8
8
|
*
|
|
9
9
|
* Environment Variables:
|
|
10
|
+
* SORA_MCP_TOKEN - MCP API token
|
|
10
11
|
* SORA_API_URL - API base URL (default: http://localhost:3000/api)
|
|
11
12
|
*/
|
|
12
13
|
async function main() {
|
|
13
14
|
// Parse command line arguments
|
|
14
15
|
const args = process.argv.slice(2);
|
|
15
16
|
const tokenArg = args.find(arg => arg.startsWith("--token="));
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const tokenEnvArg = args.find(arg => arg.startsWith("--token-env="));
|
|
18
|
+
const explicitTokenEnvName = tokenEnvArg?.split("=")[1];
|
|
19
|
+
const tokenEnvName = explicitTokenEnvName || "SORA_MCP_TOKEN";
|
|
20
|
+
if (tokenEnvArg && !explicitTokenEnvName) {
|
|
21
|
+
console.error("Error: --token-env value is empty");
|
|
22
|
+
console.error("Usage: sora-mcp --token-env=SORA_MCP_TOKEN");
|
|
19
23
|
process.exit(1);
|
|
20
24
|
}
|
|
21
|
-
const
|
|
25
|
+
const tokenFromEnv = process.env[tokenEnvName];
|
|
26
|
+
const token = tokenFromEnv || tokenArg?.split("=")[1];
|
|
22
27
|
if (!token) {
|
|
23
|
-
console.error(
|
|
28
|
+
console.error(`Error: token is required. Set ${tokenEnvName} or pass --token=YOUR_TOKEN.`);
|
|
24
29
|
process.exit(1);
|
|
25
30
|
}
|
|
26
31
|
// Get API URL from environment variable or use default
|
package/dist/server.js
CHANGED
|
@@ -198,6 +198,19 @@ export function createServer(token, apiBaseUrl) {
|
|
|
198
198
|
progress: { type: "number" },
|
|
199
199
|
totalVideos: { type: "number" },
|
|
200
200
|
completedVideos: { type: "number" },
|
|
201
|
+
estimatedCredits: { type: "number" },
|
|
202
|
+
chargedVideos: { type: "number" },
|
|
203
|
+
actualRoute: { type: "string" },
|
|
204
|
+
taskMode: { type: ["string", "null"] },
|
|
205
|
+
retryCount: { type: "number" },
|
|
206
|
+
statusHint: { type: "string" },
|
|
207
|
+
createdAt: { type: ["string", "null"] },
|
|
208
|
+
updatedAt: { type: ["string", "null"] },
|
|
209
|
+
completedAt: { type: ["string", "null"] },
|
|
210
|
+
errorMessage: { type: "string" },
|
|
211
|
+
errorCode: { type: "string" },
|
|
212
|
+
errorCategory: { type: "string" },
|
|
213
|
+
retryable: { type: "boolean" },
|
|
201
214
|
code: { type: "string" },
|
|
202
215
|
message: { type: "string" },
|
|
203
216
|
route: { type: "string" },
|
|
@@ -212,6 +225,14 @@ export function createServer(token, apiBaseUrl) {
|
|
|
212
225
|
videoUrl: { type: ["string", "null"] },
|
|
213
226
|
thumbnailUrl: { type: ["string", "null"] },
|
|
214
227
|
errorMessage: { type: ["string", "null"] },
|
|
228
|
+
errorCode: { type: ["string", "null"] },
|
|
229
|
+
errorCategory: { type: ["string", "null"] },
|
|
230
|
+
retryable: { type: ["boolean", "null"] },
|
|
231
|
+
actualRoute: { type: ["string", "null"] },
|
|
232
|
+
retryCount: { type: "number" },
|
|
233
|
+
creditsCharged: { type: "boolean" },
|
|
234
|
+
createdAt: { type: ["string", "null"] },
|
|
235
|
+
completedAt: { type: ["string", "null"] },
|
|
215
236
|
},
|
|
216
237
|
required: ["id", "status", "videoUrl", "thumbnailUrl", "errorMessage"],
|
|
217
238
|
},
|
|
@@ -650,8 +671,21 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
|
650
671
|
`- **任务 ID**: ${response.taskId}`,
|
|
651
672
|
`- **状态**: ${statusText}`,
|
|
652
673
|
`- **进度**: ${response.progress}% (${response.completedVideos}/${response.totalVideos} 个视频)`,
|
|
674
|
+
response.engineName ? `- **生成引擎**: ${response.engineName}` : undefined,
|
|
675
|
+
response.taskMode ? `- **模式**: ${response.taskMode}` : undefined,
|
|
676
|
+
response.actualRoute ? `- **实际线路**: ${response.actualRoute}` : undefined,
|
|
677
|
+
typeof response.estimatedCredits === "number" ? `- **预估积分**: ${response.estimatedCredits}` : undefined,
|
|
678
|
+
typeof response.chargedVideos === "number" ? `- **已扣费视频**: ${response.chargedVideos}/${response.totalVideos}` : undefined,
|
|
679
|
+
typeof response.retryCount === "number" ? `- **重试次数**: ${response.retryCount}` : undefined,
|
|
653
680
|
``,
|
|
654
|
-
];
|
|
681
|
+
].filter((line) => line !== undefined);
|
|
682
|
+
if (response.errorMessage) {
|
|
683
|
+
result.push(`- **错误**: ${response.errorMessage}`);
|
|
684
|
+
if (response.errorCode) {
|
|
685
|
+
result.push(`- **错误代码**: ${response.errorCode}`);
|
|
686
|
+
}
|
|
687
|
+
result.push(``);
|
|
688
|
+
}
|
|
655
689
|
if (response.videos && response.videos.length > 0) {
|
|
656
690
|
result.push(`## 视频列表:`);
|
|
657
691
|
response.videos.forEach((video, index) => {
|
|
@@ -669,8 +703,23 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
|
669
703
|
if (video.errorMessage) {
|
|
670
704
|
result.push(` ❌ 错误: ${video.errorMessage}`);
|
|
671
705
|
}
|
|
706
|
+
if (video.errorCode) {
|
|
707
|
+
result.push(` 错误代码: ${video.errorCode}`);
|
|
708
|
+
}
|
|
709
|
+
if (video.actualRoute) {
|
|
710
|
+
result.push(` 实际线路: ${video.actualRoute}`);
|
|
711
|
+
}
|
|
712
|
+
if (typeof video.retryCount === "number" && video.retryCount > 0) {
|
|
713
|
+
result.push(` 重试次数: ${video.retryCount}`);
|
|
714
|
+
}
|
|
715
|
+
if (typeof video.creditsCharged === "boolean") {
|
|
716
|
+
result.push(` 已扣费: ${video.creditsCharged ? "是" : "否"}`);
|
|
717
|
+
}
|
|
672
718
|
});
|
|
673
719
|
}
|
|
720
|
+
if (response.statusHint) {
|
|
721
|
+
result.push(``, response.statusHint);
|
|
722
|
+
}
|
|
674
723
|
return {
|
|
675
724
|
content: [
|
|
676
725
|
{
|
|
@@ -685,6 +734,21 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
|
|
|
685
734
|
progress: response.progress,
|
|
686
735
|
totalVideos: response.totalVideos,
|
|
687
736
|
completedVideos: response.completedVideos,
|
|
737
|
+
route: response.route,
|
|
738
|
+
engineName: response.engineName,
|
|
739
|
+
taskMode: response.taskMode,
|
|
740
|
+
actualRoute: response.actualRoute,
|
|
741
|
+
retryCount: response.retryCount,
|
|
742
|
+
chargedVideos: response.chargedVideos,
|
|
743
|
+
estimatedCredits: response.estimatedCredits,
|
|
744
|
+
statusHint: response.statusHint,
|
|
745
|
+
createdAt: response.createdAt,
|
|
746
|
+
updatedAt: response.updatedAt,
|
|
747
|
+
completedAt: response.completedAt,
|
|
748
|
+
errorMessage: response.errorMessage,
|
|
749
|
+
errorCode: response.errorCode,
|
|
750
|
+
errorCategory: response.errorCategory,
|
|
751
|
+
retryable: response.retryable,
|
|
688
752
|
videos: response.videos || [],
|
|
689
753
|
},
|
|
690
754
|
};
|
|
@@ -854,7 +918,11 @@ async function callRestApi(apiBaseUrl, path, params, method = "GET") {
|
|
|
854
918
|
const data = await response.json();
|
|
855
919
|
// Check for error in response
|
|
856
920
|
if (data.error) {
|
|
857
|
-
|
|
921
|
+
const toolError = new McpToolError(data.error.message || "API 返回错误", data.error.code || "API_ERROR", data.error.route);
|
|
922
|
+
if (data.error.engineName) {
|
|
923
|
+
toolError.engineName = data.error.engineName;
|
|
924
|
+
}
|
|
925
|
+
throw toolError;
|
|
858
926
|
}
|
|
859
927
|
return data;
|
|
860
928
|
}
|
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.8",
|
|
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",
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"dev": "tsc --watch",
|
|
17
17
|
"test:smoke": "node scripts/smoke-test.mjs",
|
|
18
|
-
"
|
|
18
|
+
"test:smoke:package": "MCP_SMOKE_TARGET=package node scripts/smoke-test.mjs",
|
|
19
|
+
"test:smoke:published": "MCP_SMOKE_TARGET=published node scripts/smoke-test.mjs",
|
|
20
|
+
"prepublishOnly": "npm run build && npm run test:smoke:package"
|
|
19
21
|
},
|
|
20
22
|
"keywords": [
|
|
21
23
|
"mcp",
|