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.
- package/README.md +48 -97
- package/build/index.js +638 -152
- 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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
18
|
+
## 🛠️ Usage (Local STDIO)
|
|
10
19
|
|
|
11
|
-
|
|
20
|
+
For testing locally with Claude Desktop or running via `npx`:
|
|
12
21
|
|
|
13
22
|
```bash
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
+
### Option 2: Deploy on Railway
|
|
56
46
|
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
- `plan_id` *(optional)*: Filter to a specific plan (`free`, `starter`, `creator`, `studio`, `enterprise`)
|
|
53
|
+
## 🔗 Submitting to Smithery
|
|
62
54
|
|
|
63
|
-
|
|
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
|
-
|
|
71
|
-
Estimates how many AI Video Credits are needed to generate a clip.
|
|
63
|
+
## 🔑 Authentication
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
// ───
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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:
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
]
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
354
|
-
|
|
355
|
-
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
31
|
-
"
|
|
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
|
+
}
|