@infimind/video-mcp-cli 1.0.0

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 ADDED
@@ -0,0 +1,119 @@
1
+ # @infimind/video-mcp-cli
2
+
3
+ MCP Server for Sora AI Video Generator - Access video generation features through Claude Desktop.
4
+
5
+ ## What is MCP?
6
+
7
+ MCP (Model Context Protocol) is an open protocol that enables Claude Desktop to securely connect to external data sources and tools. This MCP server allows you to interact with the Sora AI Video Generator API directly from Claude Desktop.
8
+
9
+ ## Installation
10
+
11
+ ### Via npm (Recommended)
12
+
13
+ ```bash
14
+ npm install -g @infimind/video-mcp-cli
15
+ ```
16
+
17
+ ### Via npx (No installation required)
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "sora-video-generator": {
23
+ "command": "npx",
24
+ "args": ["-y", "@infimind/video-mcp-cli", "--token=YOUR_TOKEN"]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ ### 1. Get Your API Token
33
+
34
+ 1. Log in to your Sora AI Video Generator account
35
+ 2. Navigate to "API Tokens" in the sidebar
36
+ 3. Click "New Token" and give it a name
37
+ 4. Copy the generated token (it will only be shown once!)
38
+
39
+ ### 2. Configure Claude Desktop
40
+
41
+ Add the following to your Claude Desktop configuration file:
42
+
43
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
44
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "sora-video-generator": {
50
+ "command": "sora-mcp",
51
+ "args": ["--token=YOUR_TOKEN_HERE"],
52
+ "env": {
53
+ "SORA_API_URL": "https://your-api-domain.com/api/trpc"
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ #### Environment Variables
61
+
62
+ - `SORA_API_URL` (optional): Your API base URL. Defaults to `http://localhost:3000/api/trpc`
63
+
64
+ ### 3. Restart Claude Desktop
65
+
66
+ After saving the configuration, restart Claude Desktop for the changes to take effect.
67
+
68
+ ## Available Tools
69
+
70
+ ### `get_user_info`
71
+
72
+ Get information about the authenticated user.
73
+
74
+ **Example usage in Claude:**
75
+ ```
76
+ Get my user information for the video generator
77
+ ```
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ # Clone the repository
83
+ git clone <repository-url>
84
+
85
+ # Install dependencies
86
+ cd mcp-server
87
+ npm install
88
+
89
+ # Build
90
+ npm run build
91
+
92
+ # Run in development mode
93
+ node dist/index.js --token=YOUR_TOKEN
94
+ ```
95
+
96
+ ## Publishing
97
+
98
+ ```bash
99
+ # Build the package
100
+ npm run build
101
+
102
+ # Publish to npm
103
+ npm publish --access public
104
+ ```
105
+
106
+ ## Security
107
+
108
+ - Never share your API token with others
109
+ - Store tokens securely and rotate them periodically
110
+ - Use environment variables for sensitive configuration
111
+ - The token is transmitted over HTTPS in production
112
+
113
+ ## License
114
+
115
+ MIT
116
+
117
+ ## Support
118
+
119
+ For issues and support, please visit: https://github.com/infimind/sora-video-generator
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer } from "./server.js";
4
+ /**
5
+ * CLI Entry Point for Sora MCP Server
6
+ *
7
+ * Usage: sora-mcp --token=YOUR_TOKEN
8
+ *
9
+ * Environment Variables:
10
+ * SORA_API_URL - API base URL (default: http://localhost:3000/api)
11
+ */
12
+ async function main() {
13
+ // Parse command line arguments
14
+ const args = process.argv.slice(2);
15
+ const tokenArg = args.find(arg => arg.startsWith("--token="));
16
+ if (!tokenArg) {
17
+ console.error("Error: --token argument is required");
18
+ console.error("Usage: sora-mcp --token=YOUR_TOKEN");
19
+ process.exit(1);
20
+ }
21
+ const token = tokenArg.split("=")[1];
22
+ if (!token) {
23
+ console.error("Error: Token value is empty");
24
+ process.exit(1);
25
+ }
26
+ // Get API URL from environment variable or use default
27
+ // Note: Default should be just the base path without /trpc
28
+ const apiUrl = process.env.SORA_API_URL || "http://localhost:3000/api";
29
+ // Create and start the MCP server
30
+ const server = createServer(token, apiUrl);
31
+ // Set up stdio transport
32
+ const transport = new StdioServerTransport();
33
+ // Connect the server to the transport
34
+ await server.connect(transport);
35
+ // Server is now running and listening for messages via stdio
36
+ // Graceful shutdown
37
+ process.on("SIGINT", async () => {
38
+ await server.close();
39
+ process.exit(0);
40
+ });
41
+ }
42
+ main().catch((error) => {
43
+ console.error("Fatal error:", error);
44
+ process.exit(1);
45
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP Server Implementation for Sora AI Video Generator
3
+ *
4
+ * This server implements the Model Context Protocol to allow Claude Desktop
5
+ * to interact with the Sora AI Video Generator API.
6
+ */
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ /**
9
+ * Create and configure the MCP Server
10
+ */
11
+ export declare function createServer(token: string, apiBaseUrl: string): Server;
package/dist/server.js ADDED
@@ -0,0 +1,399 @@
1
+ /**
2
+ * MCP Server Implementation for Sora AI Video Generator
3
+ *
4
+ * This server implements the Model Context Protocol to allow Claude Desktop
5
+ * to interact with the Sora AI Video Generator API.
6
+ */
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
9
+ /**
10
+ * Create and configure the MCP Server
11
+ */
12
+ export function createServer(token, apiBaseUrl) {
13
+ const server = new Server({
14
+ name: "sora-video-generator-mcp",
15
+ version: "1.0.0",
16
+ }, {
17
+ capabilities: {
18
+ tools: {},
19
+ },
20
+ });
21
+ /**
22
+ * List available tools
23
+ */
24
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
25
+ const tools = [
26
+ {
27
+ name: "get_user_info",
28
+ description: "Get information about the authenticated user, including username, name, email, role, credits, and VIP status",
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: {},
32
+ },
33
+ },
34
+ {
35
+ name: "create_video_task",
36
+ description: "Create a video generation task. Supports local file paths or public URLs for reference images. " +
37
+ "The prompt will be automatically translated and optimized based on the target language.",
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {
41
+ prompt: {
42
+ type: "string",
43
+ description: "The video generation prompt describing what you want to create",
44
+ },
45
+ sourceImage: {
46
+ type: "string",
47
+ description: "Optional reference image - can be a local file path (e.g., /path/to/image.jpg) or a public URL",
48
+ },
49
+ model: {
50
+ type: "string",
51
+ description: "Video model to use: sora_video2-15s (default) or sora-pro",
52
+ enum: ["sora_video2-15s", "sora-pro"],
53
+ },
54
+ size: {
55
+ type: "string",
56
+ description: "Video resolution (default: 1280x704)",
57
+ enum: ["1280x704", "1920x1080", "1080x1920", "704x1280"],
58
+ },
59
+ seconds: {
60
+ type: "number",
61
+ description: "Video duration in seconds: 10 (default) or 15",
62
+ enum: [10, 15],
63
+ },
64
+ language: {
65
+ type: "string",
66
+ description: "Target language for the video (default: zh-CN)",
67
+ enum: ["zh-CN", "en-US", "ja-JP", "ko-KR", "es-ES", "fr-FR"],
68
+ },
69
+ count: {
70
+ type: "number",
71
+ description: "Number of videos to generate (1-10, default: 1)",
72
+ minimum: 1,
73
+ maximum: 10,
74
+ },
75
+ route: {
76
+ type: "string",
77
+ description: "API route to use: S1 (default), S2, or S3",
78
+ enum: ["S1", "S2", "S3"],
79
+ },
80
+ },
81
+ required: ["prompt"],
82
+ },
83
+ },
84
+ {
85
+ name: "get_video_status",
86
+ description: "Get the status of a video generation task, including progress and video URLs",
87
+ inputSchema: {
88
+ type: "object",
89
+ properties: {
90
+ taskId: {
91
+ type: "number",
92
+ description: "The video generation task ID",
93
+ },
94
+ },
95
+ required: ["taskId"],
96
+ },
97
+ },
98
+ {
99
+ name: "create_video_analysis_task",
100
+ description: "Create a video analysis task that breaks down a video into script, style, selling points, viral formula, and generates a prompt for video creation. " +
101
+ "Supports local file paths or public URLs for the video file.",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: {
105
+ videoFile: {
106
+ type: "string",
107
+ description: "Video file to analyze - can be a local file path (e.g., /path/to/video.mp4) or a public URL",
108
+ },
109
+ },
110
+ required: ["videoFile"],
111
+ },
112
+ },
113
+ {
114
+ name: "get_video_analysis_status",
115
+ description: "Get the status and results of a video analysis task",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {
119
+ analysisId: {
120
+ type: "number",
121
+ description: "The video analysis task ID",
122
+ },
123
+ },
124
+ required: ["analysisId"],
125
+ },
126
+ },
127
+ ];
128
+ return { tools };
129
+ });
130
+ /**
131
+ * Handle tool calls
132
+ */
133
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
134
+ const { name, arguments: args } = request.params;
135
+ try {
136
+ switch (name) {
137
+ case "get_user_info":
138
+ return await handleGetUserInfo(apiBaseUrl, token);
139
+ case "create_video_task":
140
+ return await handleCreateVideoTask(apiBaseUrl, token, args);
141
+ case "get_video_status":
142
+ return await handleGetVideoStatus(apiBaseUrl, token, args);
143
+ case "create_video_analysis_task":
144
+ return await handleCreateVideoAnalysisTask(apiBaseUrl, token, args);
145
+ case "get_video_analysis_status":
146
+ return await handleGetVideoAnalysisStatus(apiBaseUrl, token, args);
147
+ default:
148
+ throw new Error(`Unknown tool: ${name}`);
149
+ }
150
+ }
151
+ catch (error) {
152
+ const errorMessage = error instanceof Error ? error.message : String(error);
153
+ return {
154
+ content: [
155
+ {
156
+ type: "text",
157
+ text: `Error: ${errorMessage}`,
158
+ },
159
+ ],
160
+ isError: true,
161
+ };
162
+ }
163
+ });
164
+ return server;
165
+ }
166
+ /**
167
+ * Handle get_user_info tool call
168
+ */
169
+ async function handleGetUserInfo(apiBaseUrl, token) {
170
+ const response = await callRestApi(apiBaseUrl, "/mcp/user-info", { token }, "GET");
171
+ const userInfo = [
172
+ `# User Information`,
173
+ ``,
174
+ `- **Username**: ${response.username}`,
175
+ `- **Name**: ${response.name || "N/A"}`,
176
+ `- **Email**: ${response.email || "N/A"}`,
177
+ `- **Role**: ${response.role === "admin" ? "Administrator" : "User"}`,
178
+ `- **Credits**: ${response.credits}`,
179
+ `- **VIP Status**: ${response.isVip ? "Yes" : "No"}`,
180
+ ].join("\n");
181
+ return {
182
+ content: [
183
+ {
184
+ type: "text",
185
+ text: userInfo,
186
+ },
187
+ ],
188
+ };
189
+ }
190
+ /**
191
+ * Handle create_video_task tool call
192
+ */
193
+ async function handleCreateVideoTask(apiBaseUrl, token, args) {
194
+ const response = await callRestApi(apiBaseUrl, "/mcp/video/create", {
195
+ token,
196
+ prompt: args.prompt,
197
+ sourceImage: args.sourceImage,
198
+ model: args.model || "sora_video2-15s",
199
+ size: args.size || "1280x704",
200
+ seconds: args.seconds || 10,
201
+ language: args.language || "zh-CN",
202
+ count: args.count || 1,
203
+ route: args.route || "S1",
204
+ }, "POST");
205
+ const result = [
206
+ `# Video Generation Task Created`,
207
+ ``,
208
+ `- **Task ID**: ${response.taskId}`,
209
+ `- **Message**: ${response.message}`,
210
+ `- **Videos to Generate**: ${response.videoCount}`,
211
+ `- **Credits Used**: ${response.creditsUsed}`,
212
+ `- **Remaining Credits**: ${response.remainingCredits}`,
213
+ ``,
214
+ `Use \`get_video_status\` with task ID ${response.taskId} to check the progress.`,
215
+ ].join("\n");
216
+ return {
217
+ content: [
218
+ {
219
+ type: "text",
220
+ text: result,
221
+ },
222
+ ],
223
+ };
224
+ }
225
+ /**
226
+ * Handle get_video_status tool call
227
+ */
228
+ async function handleGetVideoStatus(apiBaseUrl, token, args) {
229
+ const response = await callRestApi(apiBaseUrl, `/mcp/video/status/${args.taskId}`, { token }, "GET");
230
+ const statusText = response.status === "completed" ? "✅ Completed" :
231
+ response.status === "processing" ? "🔄 Processing" :
232
+ response.status === "failed" ? "❌ Failed" : `⏳ ${response.status}`;
233
+ let result = [
234
+ `# Video Generation Task Status`,
235
+ ``,
236
+ `- **Task ID**: ${response.taskId}`,
237
+ `- **Status**: ${statusText}`,
238
+ `- **Progress**: ${response.progress}% (${response.completedVideos}/${response.totalVideos} videos)`,
239
+ ``,
240
+ ];
241
+ if (response.videos && response.videos.length > 0) {
242
+ result.push(`## Videos:`);
243
+ response.videos.forEach((video, index) => {
244
+ const videoStatus = video.status === "completed" ? "✅" :
245
+ video.status === "processing" ? "🔄" :
246
+ video.status === "failed" ? "❌" : "⏳";
247
+ result.push(``);
248
+ result.push(`${index + 1}. ${videoStatus} Video ID: ${video.id}`);
249
+ if (video.videoUrl) {
250
+ result.push(` 📹 [Video](${video.videoUrl})`);
251
+ }
252
+ if (video.thumbnailUrl) {
253
+ result.push(` 🖼️ [Thumbnail](${video.thumbnailUrl})`);
254
+ }
255
+ if (video.errorMessage) {
256
+ result.push(` ❌ Error: ${video.errorMessage}`);
257
+ }
258
+ });
259
+ }
260
+ return {
261
+ content: [
262
+ {
263
+ type: "text",
264
+ text: result.join("\n"),
265
+ },
266
+ ],
267
+ };
268
+ }
269
+ /**
270
+ * Handle create_video_analysis_task tool call
271
+ */
272
+ async function handleCreateVideoAnalysisTask(apiBaseUrl, token, args) {
273
+ const response = await callRestApi(apiBaseUrl, "/mcp/video-analysis/create", {
274
+ token,
275
+ videoFile: args.videoFile,
276
+ }, "POST");
277
+ const result = [
278
+ `# Video Analysis Task Created`,
279
+ ``,
280
+ `- **Analysis ID**: ${response.analysisId}`,
281
+ `- **Message**: ${response.message}`,
282
+ `- **Credits Used**: ${response.creditsUsed}`,
283
+ `- **Remaining Credits**: ${response.remainingCredits}`,
284
+ ``,
285
+ `Use \`get_video_analysis_status\` with analysis ID ${response.analysisId} to check the progress.`,
286
+ ].join("\n");
287
+ return {
288
+ content: [
289
+ {
290
+ type: "text",
291
+ text: result,
292
+ },
293
+ ],
294
+ };
295
+ }
296
+ /**
297
+ * Handle get_video_analysis_status tool call
298
+ */
299
+ async function handleGetVideoAnalysisStatus(apiBaseUrl, token, args) {
300
+ const response = await callRestApi(apiBaseUrl, `/mcp/video-analysis/status/${args.analysisId}`, { token }, "GET");
301
+ const statusText = response.status === "completed" ? "✅ Completed" :
302
+ response.status === "processing" ? "🔄 Processing" :
303
+ response.status === "pending" ? "⏳ Pending" :
304
+ response.status === "failed" ? "❌ Failed" : `⏳ ${response.status}`;
305
+ let result = [
306
+ `# Video Analysis Task Status`,
307
+ ``,
308
+ `- **Analysis ID**: ${response.analysisId}`,
309
+ `- **Status**: ${statusText}`,
310
+ ];
311
+ if (response.conversionProgress !== null) {
312
+ result.push(`- **Conversion Progress**: ${response.conversionProgress}%`);
313
+ }
314
+ if (response.status === "completed") {
315
+ result.push(``);
316
+ result.push(`## Analysis Results:`);
317
+ result.push(``);
318
+ if (response.script) {
319
+ result.push(`### 📝 Video Script`);
320
+ result.push(response.script);
321
+ result.push(``);
322
+ }
323
+ if (response.style) {
324
+ result.push(`### 🎨 Video Style`);
325
+ result.push(response.style);
326
+ result.push(``);
327
+ }
328
+ if (response.sellingPoints) {
329
+ result.push(`### 🎯 Selling Points`);
330
+ result.push(response.sellingPoints);
331
+ result.push(``);
332
+ }
333
+ if (response.viralFormula) {
334
+ result.push(`### 💡 Viral Formula`);
335
+ result.push(response.viralFormula);
336
+ result.push(``);
337
+ }
338
+ if (response.generatedPrompt) {
339
+ result.push(`### 🎬 Generated Prompt (for Sora)`);
340
+ result.push(`\`\`\``);
341
+ result.push(response.generatedPrompt);
342
+ result.push(`\`\`\``);
343
+ }
344
+ }
345
+ else if (response.status === "failed" && response.errorMessage) {
346
+ result.push(``);
347
+ result.push(`### ❌ Error`);
348
+ result.push(response.errorMessage);
349
+ }
350
+ if (response.videoFileName) {
351
+ result.push(``);
352
+ result.push(`**Source File**: ${response.videoFileName}`);
353
+ }
354
+ return {
355
+ content: [
356
+ {
357
+ type: "text",
358
+ text: result.join("\n"),
359
+ },
360
+ ],
361
+ };
362
+ }
363
+ /**
364
+ * Call REST API
365
+ *
366
+ * @param apiBaseUrl - Base URL of the API (e.g., http://localhost:3001/api)
367
+ * @param path - API path (e.g., /mcp/user-info)
368
+ * @param params - Query parameters or request body
369
+ * @param method - HTTP method (GET or POST)
370
+ * @returns Parsed response data
371
+ */
372
+ async function callRestApi(apiBaseUrl, path, params, method = "GET") {
373
+ // Build URL
374
+ const baseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl.slice(0, -1) : apiBaseUrl;
375
+ const fullPath = path.startsWith("/") ? path : "/" + path;
376
+ let fullUrl = `${baseUrl}${fullPath}`;
377
+ // For GET requests, add params as query string
378
+ if (method === "GET" && Object.keys(params).length > 0) {
379
+ const queryParams = new URLSearchParams(params).toString();
380
+ fullUrl += `?${queryParams}`;
381
+ }
382
+ const response = await fetch(fullUrl, {
383
+ method,
384
+ headers: {
385
+ "Content-Type": "application/json",
386
+ },
387
+ body: method === "POST" ? JSON.stringify(params) : undefined,
388
+ });
389
+ if (!response.ok) {
390
+ const errorText = await response.text();
391
+ throw new Error(`API request failed with status ${response.status}: ${errorText}`);
392
+ }
393
+ const data = await response.json();
394
+ // Check for error in response
395
+ if (data.error) {
396
+ throw new Error(data.error.message || "API returned an error");
397
+ }
398
+ return data;
399
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@infimind/video-mcp-cli",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server for Sora AI Video Generator - Access video generation features through Claude Desktop",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "sora-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "claude",
23
+ "video-generation",
24
+ "sora"
25
+ ],
26
+ "author": "INFIMIND",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.0.4",
30
+ "node-fetch": "^3.3.2"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.17.10",
34
+ "typescript": "^5.7.2"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ }
39
+ }