velora-mcp-server 1.0.0 → 1.1.1

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.
Files changed (3) hide show
  1. package/README.md +48 -97
  2. package/build/index.js +638 -152
  3. package/package.json +9 -7
package/README.md CHANGED
@@ -1,120 +1,71 @@
1
- # Velora MCP Server
1
+ # Velora AI Video Studio MCP Server
2
2
 
3
- Official [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for [Velora AI Video Studio](https://velorastudio.in).
3
+ The official Model Context Protocol (MCP) server for Velora AI Video Studio. This server allows any MCP-compatible AI assistant (like Claude Desktop) to:
4
+ - Browse Velora's subscription plans and credit allocations
5
+ - Check prices and credit costs for different AI models
6
+ - **Create AI videos** with scripts, voices, music, and subtitles
7
+ - **Check video status** and **retrieve completed videos**
4
8
 
5
- Connect any MCP-compatible AI assistant (Claude, Cursor, Windsurf, etc.) to Velora's data — so it can help users choose plans, estimate costs, and discover AI models **without leaving their workflow**.
9
+ ## 🚀 Features
6
10
 
7
- ---
11
+ - **`get_velora_plans`**: List pricing, features, and compute credits for all plans.
12
+ - **`estimate_video_cost`**: Calculate the AI Video Credits required for a specific model and duration.
13
+ - **`list_ai_models`**: View all available video/image models and their pricing tiers.
14
+ - **`create_video`**: Generate a video using your Velora API key.
15
+ - **`check_video_status`**: Poll generation progress.
16
+ - **`get_video_result`**: Get the final download URL.
8
17
 
9
- ## 🚀 Quick Install (via Smithery)
18
+ ## 🛠️ Usage (Local STDIO)
10
19
 
11
- The easiest way to install is through [Smithery](https://smithery.ai/server/velora-mcp-server):
20
+ For testing locally with Claude Desktop or running via `npx`:
12
21
 
13
22
  ```bash
14
- npx -y @smithery/cli install velora-mcp-server --client claude
15
- ```
16
-
17
- ---
18
-
19
- ## 🛠️ Manual Install
20
-
21
- ### Claude Desktop
22
-
23
- Add this to your `claude_desktop_config.json`:
23
+ # Build the server
24
+ npm install
25
+ npm run build
24
26
 
25
- ```json
26
- {
27
- "mcpServers": {
28
- "velora": {
29
- "command": "npx",
30
- "args": ["-y", "velora-mcp-server"]
31
- }
32
- }
33
- }
27
+ # Run via npx
28
+ npx -y @velorastudio/mcp-server
34
29
  ```
35
30
 
36
- **Config file location:**
37
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
38
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
39
-
40
- ### Cursor / Windsurf
31
+ ## 🌍 Deployment (HTTP / SSE for Smithery Listing)
41
32
 
42
- Add to your MCP settings:
33
+ To list this server permanently on the Smithery directory, it is configured to run as an HTTP/SSE server. You can deploy it for free on Render or Railway.
43
34
 
44
- ```json
45
- {
46
- "velora": {
47
- "command": "npx",
48
- "args": ["-y", "velora-mcp-server"]
49
- }
50
- }
51
- ```
35
+ ### Option 1: Deploy on Render (Recommended)
52
36
 
53
- ---
37
+ 1. Push this repository to GitHub.
38
+ 2. Sign up at [Render.com](https://render.com).
39
+ 3. Click **New > Web Service**.
40
+ 4. Connect your GitHub repository.
41
+ 5. Render will automatically detect the `render.yaml` and `Dockerfile` config.
42
+ 6. Make sure the Environment Variable `PORT` is set (e.g., `3000`).
43
+ 7. Deploy! Your server will be live at `https://your-service-name.onrender.com`.
54
44
 
55
- ## 🔧 Available Tools
45
+ ### Option 2: Deploy on Railway
56
46
 
57
- ### `get_velora_plans`
58
- Returns all Velora subscription plans with pricing, credit allocations, features, and storage limits.
47
+ 1. Push this repository to GitHub.
48
+ 2. Sign up at [Railway.app](https://railway.app).
49
+ 3. Click **New Project > Deploy from GitHub repo**.
50
+ 4. Railway will automatically detect the `railway.json` and `Dockerfile` config.
51
+ 5. It will assign a domain automatically.
59
52
 
60
- **Parameters:**
61
- - `plan_id` *(optional)*: Filter to a specific plan (`free`, `starter`, `creator`, `studio`, `enterprise`)
53
+ ## 🔗 Submitting to Smithery
62
54
 
63
- **Example prompts:**
64
- - *"What's included in the Velora Creator plan?"*
65
- - *"Compare all Velora plans"*
66
- - *"How much does Velora Studio cost?"*
55
+ Once your server is deployed via Render or Railway:
67
56
 
68
- ---
57
+ 1. Go to [Smithery.ai](https://smithery.ai)
58
+ 2. Submit your MCP server using the URL:
59
+ - Provide the **SSE endpoint**: `https://your-service-name.onrender.com/mcp`
60
+ 3. Fill out the description and list your tools.
61
+ 4. Your server will now be permanently listed in the Smithery directory for 10,000+ users!
69
62
 
70
- ### `estimate_video_cost`
71
- Estimates how many AI Video Credits are needed to generate a clip.
63
+ ## 🔑 Authentication
72
64
 
73
- **Parameters:**
74
- - `model` *(required)*: The AI model name (e.g. `kling_3_0`, `veo3_1`, `sora_2`, `wan2_5`)
75
- - `duration_seconds` *(required)*: Clip length in seconds (1–15)
65
+ Most read-only tools do not require authentication.
66
+ However, **video generation tools** (`create_video`, `check_video_status`, `get_video_result`) require a Velora API key.
76
67
 
77
- **Example prompts:**
78
- - *"How many credits does a 10-second Sora 2 video cost on Velora?"*
79
- - *"What plan do I need to generate Veo 3.1 videos?"*
68
+ The AI assistant will prompt the user to provide their API key when using these tools. Users can generate their API keys at: [https://velorastudio.in/settings/api-keys](https://velorastudio.in/settings/api-keys)
80
69
 
81
70
  ---
82
-
83
- ### `list_ai_models`
84
- Lists all AI video and image generation models available on Velora, grouped by quality tier, with credit costs.
85
-
86
- **Parameters:**
87
- - `category` *(optional)*: `video`, `image`, or `all` (default: `all`)
88
-
89
- **Example prompts:**
90
- - *"What AI video models does Velora support?"*
91
- - *"Show me budget video models on Velora"*
92
- - *"What image generation models are available?"*
93
-
94
- ---
95
-
96
- ## 💡 Why This Exists
97
-
98
- When you connect this MCP server to your AI assistant:
99
-
100
- - Any AI can **accurately answer questions** about Velora pricing, without hallucinating
101
- - Users planning video projects get **instant cost estimates** from inside Claude/Cursor
102
- - Velora gets discovered **organically** by developers and creators using AI tools daily
103
-
104
- ---
105
-
106
- ## 📦 Running Locally
107
-
108
- ```bash
109
- git clone https://github.com/Jivan1727/velora-backend
110
- cd mcp-server
111
- npm install
112
- npm run build
113
- node build/index.js
114
- ```
115
-
116
- ## 🔗 Links
117
-
118
- - **Website**: https://velorastudio.in
119
- - **Pricing**: https://velorastudio.in/pricing
120
- - **Contact / Enterprise**: office@velorastudio.in
71
+ *Built by [Velora AI](https://velorastudio.in)*
package/build/index.js CHANGED
@@ -1,10 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
8
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
10
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
11
+ const node_crypto_1 = require("node:crypto");
6
12
  const zod_1 = require("zod");
7
- // ─── Velora Plan Data (source of truth) ──────────────────────────────────────
13
+ const express_1 = __importDefault(require("express"));
14
+ // ─── Velora Plan Data ─────────────────────────────────────────────────────────
8
15
  const PLANS = [
9
16
  {
10
17
  id: "free",
@@ -125,7 +132,6 @@ const PLANS = [
125
132
  },
126
133
  ];
127
134
  // ─── AI Video Model Credit Costs ─────────────────────────────────────────────
128
- // Format: { model_id: { 5: credits_5s, 10: credits_10s, 15: credits_15s } }
129
135
  const VIDEO_MODEL_COSTS = {
130
136
  grok_video: { 5: 2, 10: 5, 15: 8 },
131
137
  seedance_1_0_fast: { 5: 2, 10: 3, 15: 5 },
@@ -179,43 +185,56 @@ const IMAGE_MODEL_COSTS = {
179
185
  ideogram_3: 80,
180
186
  imagen_4: 160,
181
187
  };
182
- // ─── MCP Server setup ─────────────────────────────────────────────────────────
183
- const server = new mcp_js_1.McpServer({
184
- name: "velora-mcp-server",
185
- version: "1.0.0",
186
- });
187
- // ─── Tool: get_velora_plans ───────────────────────────────────────────────────
188
- server.tool("get_velora_plans", "Get all Velora AI Video Studio subscription plans with their pricing (INR & USD), credit allocations, features, and storage limits. Use this to help users choose the right plan or answer questions about Velora pricing.", {
189
- plan_id: zod_1.z
190
- .string()
191
- .optional()
192
- .describe("Optional: filter to a specific plan ID (free, starter, creator, studio, enterprise). Omit to get all plans."),
193
- }, async ({ plan_id }) => {
194
- const results = plan_id
195
- ? PLANS.filter((p) => p.id === plan_id)
196
- : PLANS;
197
- if (results.length === 0) {
198
- return {
199
- content: [
200
- {
201
- type: "text",
202
- text: `Plan "${plan_id}" not found. Available plans: free, starter, creator, studio, enterprise.`,
203
- },
204
- ],
205
- };
206
- }
207
- const formatted = results.map((p) => {
208
- const price = p.price_inr === -1
209
- ? "Custom pricing — contact office@velorastudio.in"
210
- : `₹${p.price_inr.toLocaleString("en-IN")}/month (≈ $${p.price_usd}/month)`;
211
- const credits = p.compute_credits === -1
212
- ? "Custom credit allocation"
213
- : [
214
- `⚡ ${p.compute_credits.toLocaleString()} Compute Credits`,
215
- `🎬 ${p.video_credits} AI Video Credits`,
216
- `🎙️ ${p.el_minutes > 0 ? p.el_minutes + " min/mo ElevenLabs Premium Voice" : "No premium voice"}`,
217
- ].join("\n ");
218
- return `## ${p.name} Plan
188
+ // ─── Supported Languages ──────────────────────────────────────────────────────
189
+ const SUPPORTED_LANGUAGES = [
190
+ "English", "Hindi", "Spanish", "French", "German", "Portuguese", "Italian",
191
+ "Japanese", "Korean", "Chinese (Simplified)", "Chinese (Traditional)", "Arabic",
192
+ "Russian", "Dutch", "Swedish", "Norwegian", "Danish", "Finnish", "Polish",
193
+ "Turkish", "Indonesian", "Malay", "Thai", "Vietnamese", "Greek", "Hebrew",
194
+ "Czech", "Hungarian", "Romanian", "Slovak", "Croatian", "Bulgarian", "Ukrainian",
195
+ "Catalan", "Tamil", "Telugu", "Kannada", "Malayalam", "Bengali", "Gujarati",
196
+ "Marathi", "Punjabi", "Urdu", "Swahili", "Afrikaans",
197
+ ];
198
+ // ─── Velora API Base URL ──────────────────────────────────────────────────────
199
+ const VELORA_API_BASE = "https://api.velorastudio.in";
200
+ // ─── Factory: creates a fresh MCP server instance ─────────────────────────────
201
+ function createServer() {
202
+ const server = new mcp_js_1.McpServer({
203
+ name: "velora-mcp-server",
204
+ version: "2.0.0",
205
+ });
206
+ // ─── Tool: get_velora_plans ──────────────────────────────────────────────
207
+ server.tool("get_velora_plans", "Get all Velora AI Video Studio subscription plans with their pricing (INR & USD), credit allocations, features, and storage limits. Use this to help users choose the right plan or answer questions about Velora pricing.", {
208
+ plan_id: zod_1.z
209
+ .string()
210
+ .optional()
211
+ .describe("Optional: filter to a specific plan ID (free, starter, creator, studio, enterprise). Omit to get all plans."),
212
+ }, async ({ plan_id }) => {
213
+ const results = plan_id
214
+ ? PLANS.filter((p) => p.id === plan_id)
215
+ : PLANS;
216
+ if (results.length === 0) {
217
+ return {
218
+ content: [
219
+ {
220
+ type: "text",
221
+ text: `Plan "${plan_id}" not found. Available plans: free, starter, creator, studio, enterprise.`,
222
+ },
223
+ ],
224
+ };
225
+ }
226
+ const formatted = results.map((p) => {
227
+ const price = p.price_inr === -1
228
+ ? "Custom pricing — contact office@velorastudio.in"
229
+ : `₹${p.price_inr.toLocaleString("en-IN")}/month (≈ $${p.price_usd}/month)`;
230
+ const credits = p.compute_credits === -1
231
+ ? "Custom credit allocation"
232
+ : [
233
+ `⚡ ${p.compute_credits.toLocaleString()} Compute Credits`,
234
+ `🎬 ${p.video_credits} AI Video Credits`,
235
+ `🎙️ ${p.el_minutes > 0 ? p.el_minutes + " min/mo ElevenLabs Premium Voice" : "No premium voice"}`,
236
+ ].join("\n ");
237
+ return `## ${p.name} Plan
219
238
  Tagline: ${p.tagline}
220
239
  Price: ${price}
221
240
  Credits:
@@ -224,135 +243,602 @@ Storage: ${p.storage}
224
243
  Export: ${p.export}
225
244
  Features:
226
245
  ${p.features.map((f) => ` - ${f}`).join("\n")}`;
227
- });
228
- return {
229
- content: [
230
- {
231
- type: "text",
232
- text: `# Velora AI Video Studio — Plans\nWebsite: https://velorastudio.in | Pricing: https://velorastudio.in/pricing\n\n` +
233
- formatted.join("\n\n---\n\n"),
234
- },
235
- ],
236
- };
237
- });
238
- // ─── Tool: estimate_video_cost ────────────────────────────────────────────────
239
- server.tool("estimate_video_cost", "Estimate the number of AI Video Credits required to generate a video clip on Velora. Provide the model name and duration. Also returns the equivalent plan recommendation.", {
240
- model: zod_1.z
241
- .string()
242
- .describe("The AI video model to use. Examples: kling_3_0, veo3_1, sora_2, wan2_5, seedance_2_0, grok_video, hailuo_2_3, pika, luma_ray_2, runway_gen_4"),
243
- duration_seconds: zod_1.z
244
- .number()
245
- .int()
246
- .min(1)
247
- .max(15)
248
- .describe("Duration of the clip in seconds. Valid values: 1-15."),
249
- }, async ({ model, duration_seconds }) => {
250
- const normalized = model.toLowerCase().replace(/-/g, "_").replace(/\./g, "_");
251
- const pricing = VIDEO_MODEL_COSTS[normalized];
252
- if (!pricing) {
253
- const available = Object.keys(VIDEO_MODEL_COSTS).join(", ");
246
+ });
254
247
  return {
255
248
  content: [
256
249
  {
257
250
  type: "text",
258
- text: `Model "${model}" not found. Available models: ${available}`,
251
+ text: `# Velora AI Video Studio — Plans\nWebsite: https://velorastudio.in | Pricing: https://velorastudio.in/pricing\n\n` +
252
+ formatted.join("\n\n---\n\n"),
259
253
  },
260
254
  ],
261
255
  };
262
- }
263
- const tier = duration_seconds <= 5 ? 5 : duration_seconds <= 10 ? 10 : 15;
264
- const cost = pricing[tier];
265
- // Find suitable plans
266
- const suitablePlans = PLANS.filter((p) => p.video_credits === -1 || p.video_credits >= cost);
267
- const planNames = suitablePlans.map((p) => p.name).join(", ");
268
- return {
269
- content: [
270
- {
271
- type: "text",
272
- text: [
273
- `# Video Cost Estimate`,
274
- `Model: ${model}`,
275
- `Duration: ${duration_seconds}s (billed at ${tier}s tier)`,
276
- `Cost: **${cost} AI Video Credits**`,
277
- ``,
278
- `## Suitable Plans`,
279
- `${planNames}`,
280
- ``,
281
- `## Credit Top-Ups Available`,
282
- `- 100 AI Video Credits: ₹699`,
283
- `- 300 AI Video Credits: ₹1,999`,
284
- `- 1,000 AI Video Credits: ₹6,499`,
285
- ``,
286
- `Sign up at https://velorastudio.in`,
287
- ].join("\n"),
288
- },
289
- ],
290
- };
291
- });
292
- // ─── Tool: list_ai_models ─────────────────────────────────────────────────────
293
- server.tool("list_ai_models", "List all AI video and image generation models available on Velora, along with their credit costs per duration tier. Useful for helping users pick the best model for their budget and quality needs.", {
294
- category: zod_1.z
295
- .enum(["video", "image", "all"])
296
- .optional()
297
- .default("all")
298
- .describe("Filter by model category: 'video', 'image', or 'all'."),
299
- }, async ({ category }) => {
300
- const lines = ["# Velora — Available AI Models\n"];
301
- if (category === "video" || category === "all") {
302
- lines.push("## 🎬 AI Video Models\n");
303
- lines.push("Format: Model Name → 5s cost / 10s cost / 15s cost (AI Video Credits)\n");
304
- const videoGroups = {
305
- "🏆 Premium (Best Quality)": [
306
- "sora_2", "sora_2_pro", "veo3_1", "veo3_1_fast", "kling_3_0", "kling_o3", "kling_3_0_pro", "kling_o1", "omni_human"
307
- ],
308
- "⚡ Standard (Great Quality)": [
309
- "kling2_5", "seedance_2_0", "seedance_2_0_fast", "seedance_1_5_pro", "luma_ray_2", "hailuo_2_3", "wan2_5", "wan2_6", "runway_gen_4", "runway_gen_4_5"
310
- ],
311
- "💰 Budget (Fast & Affordable)": [
312
- "grok_video", "grok_video_i2v", "pika", "pixverse", "mochi_2", "ltx_2_3_fast", "ltx_2_3_pro", "seedance_1_0_fast"
256
+ });
257
+ // ─── Tool: estimate_video_cost ────────────────────────────────────────────
258
+ server.tool("estimate_video_cost", "Estimate the number of AI Video Credits required to generate a video clip on Velora. Provide the model name and duration. Also returns the equivalent plan recommendation.", {
259
+ model: zod_1.z
260
+ .string()
261
+ .describe("The AI video model to use. Examples: kling_3_0, veo3_1, sora_2, wan2_5, seedance_2_0, grok_video, hailuo_2_3, pika, luma_ray_2, runway_gen_4"),
262
+ duration_seconds: zod_1.z
263
+ .number()
264
+ .int()
265
+ .min(1)
266
+ .max(15)
267
+ .describe("Duration of the clip in seconds. Valid values: 1-15."),
268
+ }, async ({ model, duration_seconds }) => {
269
+ const normalized = model.toLowerCase().replace(/-/g, "_").replace(/\./g, "_");
270
+ const pricing = VIDEO_MODEL_COSTS[normalized];
271
+ if (!pricing) {
272
+ const available = Object.keys(VIDEO_MODEL_COSTS).join(", ");
273
+ return {
274
+ content: [
275
+ {
276
+ type: "text",
277
+ text: `Model "${model}" not found. Available models: ${available}`,
278
+ },
279
+ ],
280
+ };
281
+ }
282
+ const tier = duration_seconds <= 5 ? 5 : duration_seconds <= 10 ? 10 : 15;
283
+ const cost = pricing[tier];
284
+ const suitablePlans = PLANS.filter((p) => p.video_credits === -1 || p.video_credits >= cost);
285
+ const planNames = suitablePlans.map((p) => p.name).join(", ");
286
+ return {
287
+ content: [
288
+ {
289
+ type: "text",
290
+ text: [
291
+ `# Video Cost Estimate`,
292
+ `Model: ${model}`,
293
+ `Duration: ${duration_seconds}s (billed at ${tier}s tier)`,
294
+ `Cost: **${cost} AI Video Credits**`,
295
+ ``,
296
+ `## Suitable Plans`,
297
+ `${planNames}`,
298
+ ``,
299
+ `## Credit Top-Ups Available`,
300
+ `- 100 AI Video Credits: ₹699`,
301
+ `- 300 AI Video Credits: ₹1,999`,
302
+ `- 1,000 AI Video Credits: ₹6,499`,
303
+ ``,
304
+ `Sign up at https://velorastudio.in`,
305
+ ].join("\n"),
306
+ },
313
307
  ],
314
308
  };
315
- for (const [groupName, models] of Object.entries(videoGroups)) {
316
- lines.push(`### ${groupName}`);
317
- for (const m of models) {
318
- const pricing = VIDEO_MODEL_COSTS[m];
319
- if (pricing) {
320
- lines.push(`- ${m}: ${pricing[5]} cr / ${pricing[10]} cr / ${pricing[15]} cr`);
309
+ });
310
+ // ─── Tool: list_ai_models ─────────────────────────────────────────────────
311
+ server.tool("list_ai_models", "List all AI video and image generation models available on Velora, along with their credit costs per duration tier. Useful for helping users pick the best model for their budget and quality needs.", {
312
+ category: zod_1.z
313
+ .enum(["video", "image", "all"])
314
+ .optional()
315
+ .default("all")
316
+ .describe("Filter by model category: 'video', 'image', or 'all'."),
317
+ }, async ({ category }) => {
318
+ const lines = ["# Velora — Available AI Models\n"];
319
+ if (category === "video" || category === "all") {
320
+ lines.push("## 🎬 AI Video Models\n");
321
+ lines.push("Format: Model Name → 5s cost / 10s cost / 15s cost (AI Video Credits)\n");
322
+ const videoGroups = {
323
+ "🏆 Premium (Best Quality)": [
324
+ "sora_2", "sora_2_pro", "veo3_1", "veo3_1_fast", "kling_3_0", "kling_o3", "kling_3_0_pro", "kling_o1", "omni_human"
325
+ ],
326
+ "⚡ Standard (Great Quality)": [
327
+ "kling2_5", "seedance_2_0", "seedance_2_0_fast", "seedance_1_5_pro", "luma_ray_2", "hailuo_2_3", "wan2_5", "wan2_6", "runway_gen_4", "runway_gen_4_5"
328
+ ],
329
+ "💰 Budget (Fast & Affordable)": [
330
+ "grok_video", "grok_video_i2v", "pika", "pixverse", "mochi_2", "ltx_2_3_fast", "ltx_2_3_pro", "seedance_1_0_fast"
331
+ ],
332
+ };
333
+ for (const [groupName, models] of Object.entries(videoGroups)) {
334
+ lines.push(`### ${groupName}`);
335
+ for (const m of models) {
336
+ const pricing = VIDEO_MODEL_COSTS[m];
337
+ if (pricing) {
338
+ lines.push(`- ${m}: ${pricing[5]} cr / ${pricing[10]} cr / ${pricing[15]} cr`);
339
+ }
321
340
  }
341
+ lines.push("");
322
342
  }
323
- lines.push("");
324
343
  }
325
- }
326
- if (category === "image" || category === "all") {
327
- lines.push("## 🖼️ AI Image Models\n");
328
- lines.push("Format: Model Name → credits per image\n");
329
- const imageTiers = {
330
- "Budget (5-10 cr)": ["z_image_turbo", "wan_image", "gpt_image_1_5", "seedream_4_0", "gpt_image_1", "seedream_4_5", "nano_banana_2"],
331
- "Standard (20-50 cr)": ["nano_banana", "nano_banana_pro", "grok_image", "flux2_flex", "flux2_klein_4b", "qwen_2", "flux2_klein_9b", "flux_kontext_pro", "flux2_pro"],
332
- "Premium (70-160 cr)": ["flux2_max", "flux_kontext_max", "ideogram_3", "imagen_4"],
333
- };
334
- for (const [tierName, models] of Object.entries(imageTiers)) {
335
- lines.push(`### ${tierName}`);
336
- for (const m of models) {
337
- const cost = IMAGE_MODEL_COSTS[m];
338
- if (cost !== undefined) {
339
- lines.push(`- ${m}: ${cost} Compute Credits`);
344
+ if (category === "image" || category === "all") {
345
+ lines.push("## 🖼️ AI Image Models\n");
346
+ lines.push("Format: Model Name credits per image\n");
347
+ const imageTiers = {
348
+ "Budget (5-10 cr)": ["z_image_turbo", "wan_image", "gpt_image_1_5", "seedream_4_0", "gpt_image_1", "seedream_4_5", "nano_banana_2"],
349
+ "Standard (20-50 cr)": ["nano_banana", "nano_banana_pro", "grok_image", "flux2_flex", "flux2_klein_4b", "qwen_2", "flux2_klein_9b", "flux_kontext_pro", "flux2_pro"],
350
+ "Premium (70-160 cr)": ["flux2_max", "flux_kontext_max", "ideogram_3", "imagen_4"],
351
+ };
352
+ for (const [tierName, models] of Object.entries(imageTiers)) {
353
+ lines.push(`### ${tierName}`);
354
+ for (const m of models) {
355
+ const cost = IMAGE_MODEL_COSTS[m];
356
+ if (cost !== undefined) {
357
+ lines.push(`- ${m}: ${cost} Compute Credits`);
358
+ }
340
359
  }
360
+ lines.push("");
341
361
  }
342
- lines.push("");
343
362
  }
344
- }
345
- lines.push("---");
346
- lines.push("Sign up at https://velorastudio.in | Pricing: https://velorastudio.in/pricing");
347
- return {
348
- content: [{ type: "text", text: lines.join("\n") }],
349
- };
350
- });
351
- // ─── Start server ─────────────────────────────────────────────────────────────
363
+ lines.push("---");
364
+ lines.push("Sign up at https://velorastudio.in | Pricing: https://velorastudio.in/pricing");
365
+ return {
366
+ content: [{ type: "text", text: lines.join("\n") }],
367
+ };
368
+ });
369
+ // ─── Tool: create_video ───────────────────────────────────────────────────
370
+ server.tool("create_video", "🎬 Create a full AI-generated video using Velora AI Video Studio. Provide a topic or script, choose language, voice type, aspect ratio, subtitle options, and video model. Returns a video_id you can use with check_video_status and get_video_result. Requires a valid Velora API key from https://velorastudio.in/settings/api-keys", {
371
+ api_key: zod_1.z
372
+ .string()
373
+ .describe("Your Velora API key. Get one at https://velorastudio.in/settings/api-keys"),
374
+ topic: zod_1.z
375
+ .string()
376
+ .describe("The topic or idea for your video. Be specific and descriptive. Example: 'The history of the Roman Empire' or 'How to make sourdough bread at home'."),
377
+ language: zod_1.z
378
+ .string()
379
+ .optional()
380
+ .default("English")
381
+ .describe(`Language for the video narration and subtitles. Supported languages: ${SUPPORTED_LANGUAGES.join(", ")}. Defaults to English.`),
382
+ duration_minutes: zod_1.z
383
+ .number()
384
+ .optional()
385
+ .default(1)
386
+ .describe("Target duration of the video in minutes. Use 0.5 for ~30 seconds, 1 for ~1 minute, 2 for ~2 minutes, etc. Maximum: 10 minutes."),
387
+ aspect_ratio: zod_1.z
388
+ .enum(["16:9", "9:16", "1:1"])
389
+ .optional()
390
+ .default("16:9")
391
+ .describe("Aspect ratio for the video. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts (portrait), 1:1 for Instagram square."),
392
+ voice_type: zod_1.z
393
+ .enum(["standard", "enhanced", "premium"])
394
+ .optional()
395
+ .default("enhanced")
396
+ .describe("Voice quality for narration. 'standard' = basic TTS (free), 'enhanced' = high-quality neural voice (Starter+), 'premium' = ElevenLabs ultra-realistic voice (uses ElevenLabs minutes)."),
397
+ video_model: zod_1.z
398
+ .string()
399
+ .optional()
400
+ .default("veo3_1_fast")
401
+ .describe("AI video generation model for clip visuals. Defaults to veo3_1_fast. Other options: kling_3_0, sora_2, wan2_5, seedance_2_0, hailuo_2_3, grok_video. See list_ai_models for full list with credit costs."),
402
+ include_subtitles: zod_1.z
403
+ .boolean()
404
+ .optional()
405
+ .default(true)
406
+ .describe("Whether to include auto-generated subtitles/captions on the video. Recommended for social media videos."),
407
+ subtitle_style: zod_1.z
408
+ .enum(["default", "minimal", "bold", "cinematic", "neon", "netflix"])
409
+ .optional()
410
+ .default("default")
411
+ .describe("Style of the subtitle captions. Options: default, minimal, bold, cinematic, neon, netflix."),
412
+ video_style: zod_1.z
413
+ .enum(["auto", "cinematic", "documentary", "educational", "news", "social_media", "corporate", "storytelling"])
414
+ .optional()
415
+ .default("auto")
416
+ .describe("Overall style/theme of the video. 'auto' lets Velora AI choose the best style for your topic."),
417
+ script: zod_1.z
418
+ .string()
419
+ .optional()
420
+ .describe("Optional: provide your own pre-written script/narration text. If omitted, Velora AI will auto-generate a script from your topic."),
421
+ include_background_music: zod_1.z
422
+ .boolean()
423
+ .optional()
424
+ .default(true)
425
+ .describe("Whether to include background music in the video."),
426
+ webhook_url: zod_1.z
427
+ .string()
428
+ .optional()
429
+ .describe("Optional webhook URL to receive a POST callback when the video is completed. The callback payload will include video_id and output_url."),
430
+ }, async ({ api_key, topic, language, duration_minutes, aspect_ratio, voice_type, video_model, include_subtitles, subtitle_style, video_style, script, include_background_music, webhook_url, }) => {
431
+ try {
432
+ const payload = {
433
+ topic,
434
+ language,
435
+ duration_minutes,
436
+ theme: video_style === "auto" ? "documentary" : video_style,
437
+ aspect_ratio,
438
+ };
439
+ if (webhook_url)
440
+ payload.webhook_url = webhook_url;
441
+ const response = await fetch(`${VELORA_API_BASE}/v1/videos/generate`, {
442
+ method: "POST",
443
+ headers: {
444
+ "Content-Type": "application/json",
445
+ "Authorization": `Bearer ${api_key}`,
446
+ },
447
+ body: JSON.stringify(payload),
448
+ });
449
+ const data = await response.json();
450
+ if (!response.ok) {
451
+ const errorMsg = typeof data?.detail === 'string'
452
+ ? data.detail
453
+ : JSON.stringify(data?.detail || data?.message || response.statusText);
454
+ return {
455
+ content: [
456
+ {
457
+ type: "text",
458
+ text: [
459
+ `❌ **Video creation failed**`,
460
+ ``,
461
+ `Error: ${errorMsg}`,
462
+ ``,
463
+ `**Troubleshooting:**`,
464
+ `- Make sure your API key is valid: https://velorastudio.in/settings/api-keys`,
465
+ `- Check you have enough credits: https://velorastudio.in/billing`,
466
+ `- Ensure your plan supports the selected voice_type and video_model`,
467
+ ].join("\n"),
468
+ },
469
+ ],
470
+ isError: true,
471
+ };
472
+ }
473
+ const videoId = data.video_id || data.id || "unknown";
474
+ return {
475
+ content: [
476
+ {
477
+ type: "text",
478
+ text: [
479
+ `🎬 **Video Creation Started Successfully!**`,
480
+ ``,
481
+ `**Video ID:** \`${videoId}\``,
482
+ `**Topic:** ${topic}`,
483
+ `**Language:** ${language}`,
484
+ `**Duration:** ~${duration_minutes} minute(s)`,
485
+ `**Aspect Ratio:** ${aspect_ratio}`,
486
+ `**Voice:** ${voice_type}`,
487
+ `**Model:** ${video_model}`,
488
+ `**Subtitles:** ${include_subtitles ? `Yes (${subtitle_style} style)` : "No"}`,
489
+ `**Style:** ${video_style}`,
490
+ ``,
491
+ `⏳ **Status:** Processing (videos typically take 2-5 minutes)`,
492
+ ``,
493
+ `**Next Steps:**`,
494
+ `1. Use \`check_video_status\` with video_id \`${videoId}\` to poll progress`,
495
+ `2. Use \`get_video_result\` with video_id \`${videoId}\` to get the download URL when done`,
496
+ `3. Or visit your dashboard: https://velorastudio.in/dashboard`,
497
+ ].join("\n"),
498
+ },
499
+ ],
500
+ };
501
+ }
502
+ catch (error) {
503
+ return {
504
+ content: [
505
+ {
506
+ type: "text",
507
+ text: `❌ Network error connecting to Velora API: ${error.message}\n\nPlease check your internet connection and try again.`,
508
+ },
509
+ ],
510
+ isError: true,
511
+ };
512
+ }
513
+ });
514
+ // ─── Tool: check_video_status ─────────────────────────────────────────────
515
+ server.tool("check_video_status", "Check the processing status of a video created with create_video. Returns current status (pending, processing, completed, failed) and progress information. Poll this every 30-60 seconds until status is 'completed' or 'failed'.", {
516
+ api_key: zod_1.z
517
+ .string()
518
+ .describe("Your Velora API key from https://velorastudio.in/settings/api-keys"),
519
+ video_id: zod_1.z
520
+ .string()
521
+ .describe("The video_id returned by create_video."),
522
+ }, async ({ api_key, video_id }) => {
523
+ try {
524
+ const response = await fetch(`${VELORA_API_BASE}/api/videos/${video_id}`, {
525
+ method: "GET",
526
+ headers: {
527
+ "Authorization": `Bearer ${api_key}`,
528
+ },
529
+ });
530
+ const data = await response.json();
531
+ if (!response.ok) {
532
+ const errorMsg = data?.detail || data?.message || response.statusText;
533
+ return {
534
+ content: [
535
+ {
536
+ type: "text",
537
+ text: `❌ Could not fetch video status.\n\nError: ${errorMsg}\n\nMake sure your API key is valid and the video_id is correct.`,
538
+ },
539
+ ],
540
+ isError: true,
541
+ };
542
+ }
543
+ const status = data.status || "unknown";
544
+ const progress = data.progress_percent ?? data.progress ?? null;
545
+ const createdAt = data.created_at ? new Date(data.created_at).toLocaleString() : "N/A";
546
+ let statusEmoji = "⏳";
547
+ let statusMsg = "";
548
+ switch (status) {
549
+ case "completed":
550
+ statusEmoji = "✅";
551
+ statusMsg = "Your video is ready! Use `get_video_result` to get the download URL.";
552
+ break;
553
+ case "processing":
554
+ statusEmoji = "⚙️";
555
+ statusMsg = "Video is being generated. Check again in 30-60 seconds.";
556
+ break;
557
+ case "pending":
558
+ statusEmoji = "🕐";
559
+ statusMsg = "Video is queued. Check again in 30-60 seconds.";
560
+ break;
561
+ case "failed":
562
+ statusEmoji = "❌";
563
+ statusMsg = `Generation failed. Reason: ${data.error || data.error_message || "Unknown error"}. Please try creating the video again.`;
564
+ break;
565
+ default:
566
+ statusMsg = "Status unknown. Check your Velora dashboard.";
567
+ }
568
+ const lines = [
569
+ `${statusEmoji} **Video Status**`,
570
+ ``,
571
+ `**Video ID:** \`${video_id}\``,
572
+ `**Status:** ${status.toUpperCase()}`,
573
+ ];
574
+ if (progress !== null) {
575
+ lines.push(`**Progress:** ${progress}%`);
576
+ }
577
+ if (data.title || data.topic) {
578
+ lines.push(`**Title/Topic:** ${data.title || data.topic}`);
579
+ }
580
+ lines.push(`**Created:** ${createdAt}`);
581
+ lines.push(``, statusMsg);
582
+ if (status === "completed" && (data.output_path || data.output_url)) {
583
+ lines.push(``, `**Output URL:** ${data.output_path || data.output_url}`);
584
+ }
585
+ lines.push(``, `📊 Dashboard: https://velorastudio.in/dashboard`);
586
+ return {
587
+ content: [{ type: "text", text: lines.join("\n") }],
588
+ };
589
+ }
590
+ catch (error) {
591
+ return {
592
+ content: [
593
+ {
594
+ type: "text",
595
+ text: `❌ Network error: ${error.message}\n\nCheck your connection and try again.`,
596
+ },
597
+ ],
598
+ isError: true,
599
+ };
600
+ }
601
+ });
602
+ // ─── Tool: get_video_result ───────────────────────────────────────────────
603
+ server.tool("get_video_result", "Get the final download URL and full details for a completed video. Use this after check_video_status shows 'completed'. Returns the video download URL, thumbnail, duration, and metadata.", {
604
+ api_key: zod_1.z
605
+ .string()
606
+ .describe("Your Velora API key from https://velorastudio.in/settings/api-keys"),
607
+ video_id: zod_1.z
608
+ .string()
609
+ .describe("The video_id returned by create_video."),
610
+ }, async ({ api_key, video_id }) => {
611
+ try {
612
+ const response = await fetch(`${VELORA_API_BASE}/api/videos/${video_id}`, {
613
+ method: "GET",
614
+ headers: {
615
+ "Authorization": `Bearer ${api_key}`,
616
+ },
617
+ });
618
+ const data = await response.json();
619
+ if (!response.ok) {
620
+ const errorMsg = data?.detail || data?.message || response.statusText;
621
+ return {
622
+ content: [
623
+ {
624
+ type: "text",
625
+ text: `❌ Could not fetch video result.\n\nError: ${errorMsg}`,
626
+ },
627
+ ],
628
+ isError: true,
629
+ };
630
+ }
631
+ const status = data.status || "unknown";
632
+ if (status !== "completed") {
633
+ return {
634
+ content: [
635
+ {
636
+ type: "text",
637
+ text: [
638
+ `⏳ **Video not ready yet**`,
639
+ ``,
640
+ `**Video ID:** \`${video_id}\``,
641
+ `**Current Status:** ${status.toUpperCase()}`,
642
+ ``,
643
+ `Use \`check_video_status\` to monitor progress. Come back when status is COMPLETED.`,
644
+ ].join("\n"),
645
+ },
646
+ ],
647
+ };
648
+ }
649
+ const outputUrl = data.output_path || data.output_url || null;
650
+ const thumbnail = data.thumbnail_url || null;
651
+ const duration = data.duration_seconds ? `${data.duration_seconds}s` : "N/A";
652
+ const resolution = data.resolution || data.quality || "1080p";
653
+ const fileSize = data.file_size_mb ? `${data.file_size_mb} MB` : "N/A";
654
+ const createdAt = data.created_at ? new Date(data.created_at).toLocaleString() : "N/A";
655
+ if (!outputUrl) {
656
+ return {
657
+ content: [
658
+ {
659
+ type: "text",
660
+ text: `✅ Video is completed but the download URL is not yet available. Please check your dashboard: https://velorastudio.in/dashboard`,
661
+ },
662
+ ],
663
+ };
664
+ }
665
+ const lines = [
666
+ `✅ **Video Ready!**`,
667
+ ``,
668
+ `**Video ID:** \`${video_id}\``,
669
+ `**Status:** COMPLETED`,
670
+ ``,
671
+ `## 📥 Download`,
672
+ `**Video URL:** ${outputUrl}`,
673
+ ];
674
+ if (thumbnail)
675
+ lines.push(`**Thumbnail:** ${thumbnail}`);
676
+ lines.push(``, `## 📊 Details`, `**Duration:** ${duration}`, `**Resolution:** ${resolution}`, `**File Size:** ${fileSize}`, `**Created:** ${createdAt}`);
677
+ if (data.title || data.topic) {
678
+ lines.push(`**Title/Topic:** ${data.title || data.topic}`);
679
+ }
680
+ if (data.language)
681
+ lines.push(`**Language:** ${data.language}`);
682
+ lines.push(``, `🎬 **View in Dashboard:** https://velorastudio.in/dashboard`, ``, `_Tip: Share this video directly or import it into your editor._`);
683
+ return {
684
+ content: [{ type: "text", text: lines.join("\n") }],
685
+ };
686
+ }
687
+ catch (error) {
688
+ return {
689
+ content: [
690
+ {
691
+ type: "text",
692
+ text: `❌ Network error: ${error.message}`,
693
+ },
694
+ ],
695
+ isError: true,
696
+ };
697
+ }
698
+ });
699
+ // ─── Tool: list_supported_languages ──────────────────────────────────────
700
+ server.tool("list_supported_languages", "List all 46+ languages supported by Velora AI Video Studio for video narration and subtitles.", {}, async () => {
701
+ return {
702
+ content: [
703
+ {
704
+ type: "text",
705
+ text: [
706
+ `# Velora — Supported Languages (${SUPPORTED_LANGUAGES.length} Languages)`,
707
+ ``,
708
+ SUPPORTED_LANGUAGES.map((lang, i) => `${i + 1}. ${lang}`).join("\n"),
709
+ ``,
710
+ `All languages support: AI narration, auto-subtitles, and multi-language subtitle export.`,
711
+ ``,
712
+ `Sign up at https://velorastudio.in`,
713
+ ].join("\n"),
714
+ },
715
+ ],
716
+ };
717
+ });
718
+ return server;
719
+ }
720
+ // ─── Transport: HTTP or STDIO ─────────────────────────────────────────────────
352
721
  async function main() {
353
- const transport = new stdio_js_1.StdioServerTransport();
354
- await server.connect(transport);
355
- console.error("Velora MCP Server running on stdio");
722
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
723
+ if (port) {
724
+ // ── HTTP mode (for Railway / Render / Smithery) ──────────────────────
725
+ const app = (0, express_1.default)();
726
+ app.use(express_1.default.json());
727
+ // Map to store transports per session (for session resumability)
728
+ const transports = {};
729
+ // Health check (required by Railway/Render)
730
+ app.get("/", (_req, res) => {
731
+ res.json({
732
+ name: "velora-mcp-server",
733
+ version: "2.0.0",
734
+ status: "ok",
735
+ description: "Official Velora AI Video Studio MCP Server",
736
+ website: "https://velorastudio.in",
737
+ mcp_endpoint: "/mcp",
738
+ });
739
+ });
740
+ app.get("/health", (_req, res) => {
741
+ res.json({ status: "ok", timestamp: new Date().toISOString() });
742
+ });
743
+ // MCP POST — handles new sessions and existing session requests
744
+ app.post("/mcp", async (req, res) => {
745
+ const sessionId = req.headers["mcp-session-id"];
746
+ try {
747
+ let transport;
748
+ if (sessionId && transports[sessionId]) {
749
+ // Reuse existing transport for this session
750
+ transport = transports[sessionId];
751
+ }
752
+ else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
753
+ // New initialization request — create a fresh server + transport
754
+ transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
755
+ sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
756
+ onsessioninitialized: (sid) => {
757
+ transports[sid] = transport;
758
+ },
759
+ });
760
+ transport.onclose = () => {
761
+ if (transport.sessionId && transports[transport.sessionId]) {
762
+ delete transports[transport.sessionId];
763
+ }
764
+ };
765
+ const server = createServer();
766
+ await server.connect(transport);
767
+ await transport.handleRequest(req, res, req.body);
768
+ return;
769
+ }
770
+ else {
771
+ res.status(400).json({
772
+ jsonrpc: "2.0",
773
+ error: { code: -32000, message: "Bad Request: No valid session ID provided" },
774
+ id: null,
775
+ });
776
+ return;
777
+ }
778
+ await transport.handleRequest(req, res, req.body);
779
+ }
780
+ catch (err) {
781
+ console.error("MCP POST error:", err);
782
+ if (!res.headersSent) {
783
+ res.status(500).json({
784
+ jsonrpc: "2.0",
785
+ error: { code: -32603, message: "Internal server error" },
786
+ id: null,
787
+ });
788
+ }
789
+ }
790
+ });
791
+ // MCP GET — SSE stream for server-to-client notifications
792
+ app.get("/mcp", async (req, res) => {
793
+ const sessionId = req.headers["mcp-session-id"];
794
+ if (!sessionId || !transports[sessionId]) {
795
+ res.status(400).send("Invalid or missing session ID");
796
+ return;
797
+ }
798
+ await transports[sessionId].handleRequest(req, res);
799
+ });
800
+ // MCP DELETE — session termination
801
+ app.delete("/mcp", async (req, res) => {
802
+ const sessionId = req.headers["mcp-session-id"];
803
+ if (!sessionId || !transports[sessionId]) {
804
+ res.status(400).send("Invalid or missing session ID");
805
+ return;
806
+ }
807
+ try {
808
+ await transports[sessionId].handleRequest(req, res);
809
+ }
810
+ catch (err) {
811
+ if (!res.headersSent)
812
+ res.status(500).send("Error processing session termination");
813
+ }
814
+ });
815
+ // Graceful shutdown
816
+ const cleanup = async () => {
817
+ console.log("Shutting down...");
818
+ for (const sid of Object.keys(transports)) {
819
+ try {
820
+ await transports[sid].close();
821
+ delete transports[sid];
822
+ }
823
+ catch (_) { /* ignore */ }
824
+ }
825
+ process.exit(0);
826
+ };
827
+ process.on("SIGTERM", cleanup);
828
+ process.on("SIGINT", cleanup);
829
+ app.listen(port, () => {
830
+ console.log(`✅ Velora MCP Server (HTTP) running on port ${port}`);
831
+ console.log(` MCP endpoint: http://localhost:${port}/mcp`);
832
+ console.log(` Health check: http://localhost:${port}/health`);
833
+ });
834
+ }
835
+ else {
836
+ // ── STDIO mode (for local / Claude Desktop / npx) ────────────────────
837
+ const server = createServer();
838
+ const transport = new stdio_js_1.StdioServerTransport();
839
+ await server.connect(transport);
840
+ console.error("✅ Velora MCP Server running on stdio");
841
+ }
356
842
  }
357
843
  main().catch((err) => {
358
844
  console.error("Fatal error:", err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "velora-mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Official Velora AI Video Studio MCP Server — exposes Velora's plans, pricing, and AI model data to any MCP-compatible AI assistant.",
5
5
  "main": "build/index.js",
6
6
  "bin": {
@@ -9,8 +9,7 @@
9
9
  "scripts": {
10
10
  "build": "tsc",
11
11
  "start": "node build/index.js",
12
- "dev": "ts-node src/index.ts",
13
- "prepare": "npm run build"
12
+ "dev": "ts-node src/index.ts"
14
13
  },
15
14
  "keywords": [
16
15
  "mcp",
@@ -23,12 +22,15 @@
23
22
  "author": "Velora AI <office@velorastudio.in>",
24
23
  "license": "MIT",
25
24
  "dependencies": {
26
- "@modelcontextprotocol/sdk": "^1.12.0"
25
+ "@modelcontextprotocol/sdk": "^1.29.0",
26
+ "express": "^5.2.1",
27
+ "zod": "^4.4.3"
27
28
  },
28
29
  "devDependencies": {
30
+ "@types/express": "^5.0.6",
29
31
  "@types/node": "^20.0.0",
30
- "typescript": "^5.0.0",
31
- "ts-node": "^10.9.0"
32
+ "ts-node": "^10.9.0",
33
+ "typescript": "^5.0.0"
32
34
  },
33
35
  "engines": {
34
36
  "node": ">=18.0.0"
@@ -40,4 +42,4 @@
40
42
  "publishConfig": {
41
43
  "access": "public"
42
44
  }
43
- }
45
+ }