@kolbo/mcp 1.14.1 → 1.15.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/package.json +56 -56
- package/src/polling.js +4 -3
- package/src/tools/generate.js +110 -25
package/package.json
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@kolbo/mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
|
|
5
|
-
"main": "src/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"kolbo-mcp": "./bin/kolbo-mcp.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"start": "node src/index.js",
|
|
11
|
-
"smoke": "node scripts/smoke.js",
|
|
12
|
-
"check-parity": "node scripts/check-parity.js",
|
|
13
|
-
"prepublishOnly": "node scripts/smoke.js && node scripts/check-parity.js"
|
|
14
|
-
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
"kolbo",
|
|
17
|
-
"mcp",
|
|
18
|
-
"ai",
|
|
19
|
-
"image-generation",
|
|
20
|
-
"video-generation",
|
|
21
|
-
"music-generation",
|
|
22
|
-
"text-to-speech",
|
|
23
|
-
"claude-code",
|
|
24
|
-
"claude-desktop",
|
|
25
|
-
"model-context-protocol"
|
|
26
|
-
],
|
|
27
|
-
"license": "MIT",
|
|
28
|
-
"repository": {
|
|
29
|
-
"type": "git",
|
|
30
|
-
"url": "https://github.com/Zoharvan12/kolbo-code",
|
|
31
|
-
"directory": "packages/kolbo-mcp"
|
|
32
|
-
},
|
|
33
|
-
"homepage": "https://docs.kolbo.ai/developer-api/claude-code-skill",
|
|
34
|
-
"author": "Kolbo AI <support@kolbo.ai>",
|
|
35
|
-
"publishConfig": {
|
|
36
|
-
"access": "public"
|
|
37
|
-
},
|
|
38
|
-
"files": [
|
|
39
|
-
"src/",
|
|
40
|
-
"bin/",
|
|
41
|
-
"README.md"
|
|
42
|
-
],
|
|
43
|
-
"dependencies": {
|
|
44
|
-
"@modelcontextprotocol/sdk": "1.29.0",
|
|
45
|
-
"form-data": "^4.0.5",
|
|
46
|
-
"zod": "^3.25.0"
|
|
47
|
-
},
|
|
48
|
-
"overrides": {
|
|
49
|
-
"hono": "^4.12.12",
|
|
50
|
-
"@hono/node-server": "^1.19.13",
|
|
51
|
-
"path-to-regexp": "^8.4.2"
|
|
52
|
-
},
|
|
53
|
-
"engines": {
|
|
54
|
-
"node": ">=18.0.0"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@kolbo/mcp",
|
|
3
|
+
"version": "1.15.1",
|
|
4
|
+
"description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kolbo-mcp": "./bin/kolbo-mcp.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/index.js",
|
|
11
|
+
"smoke": "node scripts/smoke.js",
|
|
12
|
+
"check-parity": "node scripts/check-parity.js",
|
|
13
|
+
"prepublishOnly": "node scripts/smoke.js && node scripts/check-parity.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"kolbo",
|
|
17
|
+
"mcp",
|
|
18
|
+
"ai",
|
|
19
|
+
"image-generation",
|
|
20
|
+
"video-generation",
|
|
21
|
+
"music-generation",
|
|
22
|
+
"text-to-speech",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"claude-desktop",
|
|
25
|
+
"model-context-protocol"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/Zoharvan12/kolbo-code",
|
|
31
|
+
"directory": "packages/kolbo-mcp"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://docs.kolbo.ai/developer-api/claude-code-skill",
|
|
34
|
+
"author": "Kolbo AI <support@kolbo.ai>",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"src/",
|
|
40
|
+
"bin/",
|
|
41
|
+
"README.md"
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
45
|
+
"form-data": "^4.0.5",
|
|
46
|
+
"zod": "^3.25.0"
|
|
47
|
+
},
|
|
48
|
+
"overrides": {
|
|
49
|
+
"hono": "^4.12.12",
|
|
50
|
+
"@hono/node-server": "^1.19.13",
|
|
51
|
+
"path-to-regexp": "^8.4.2"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/polling.js
CHANGED
|
@@ -6,9 +6,10 @@ class PollingTimeoutError extends Error {
|
|
|
6
6
|
constructor(generationId, timeoutMs) {
|
|
7
7
|
const seconds = Math.round(timeoutMs / 1000);
|
|
8
8
|
super(
|
|
9
|
-
`Generation timed out after ${seconds}s of polling. The generation
|
|
10
|
-
`
|
|
11
|
-
`
|
|
9
|
+
`Generation timed out after ${seconds}s of polling. The generation is STILL RUNNING on the server — ` +
|
|
10
|
+
`DO NOT re-issue the original generation tool. A retry would burn credits while the original keeps producing the result you already paid for. ` +
|
|
11
|
+
`Instead, IMMEDIATELY call get_generation_status with generation_id="${generationId}" (it block-polls server-side, no need to loop). ` +
|
|
12
|
+
`Videos, deep-think chat, and large batches can legitimately take 5–15+ minutes beyond the default polling window.`
|
|
12
13
|
);
|
|
13
14
|
this.name = 'PollingTimeoutError';
|
|
14
15
|
this.generationId = generationId;
|
package/src/tools/generate.js
CHANGED
|
@@ -32,9 +32,14 @@ function registerGenerateTools(server, client) {
|
|
|
32
32
|
reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, preset_id
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
// 60s here (not 120s) so a stuck generation surfaces faster — the
|
|
36
|
+
// PollingTimeoutError tells the model to switch to get_generation_status,
|
|
37
|
+
// which block-polls for 10min by default. Same total wait, but the
|
|
38
|
+
// first hand-off happens at 60s instead of 120s so the user isn't
|
|
39
|
+
// staring at a frozen spinner.
|
|
35
40
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
36
41
|
interval: (gen.poll_interval_hint || 3) * 1000,
|
|
37
|
-
timeout:
|
|
42
|
+
timeout: 60000
|
|
38
43
|
});
|
|
39
44
|
|
|
40
45
|
return {
|
|
@@ -74,13 +79,16 @@ function registerGenerateTools(server, client) {
|
|
|
74
79
|
visual_dna_ids, moodboard_id, enable_web_search, resolution
|
|
75
80
|
});
|
|
76
81
|
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
82
|
+
// Flux/Seedance/DNA-anchored edits routinely run 3-5 min. Old 120s
|
|
83
|
+
// timeout forced every call into the timeout-and-recover path via
|
|
84
|
+
// get_generation_status — and when the model fired multiple parallel
|
|
85
|
+
// edits, some recovered, some got abandoned with their URLs lost
|
|
86
|
+
// (the user's "lost generations" bug). 360s/480s covers the realistic
|
|
87
|
+
// p99 single-call duration without making the model poll forever.
|
|
80
88
|
const heavy = (source_images && source_images.length > 1) || (visual_dna_ids && visual_dna_ids.length > 0);
|
|
81
89
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
82
90
|
interval: (gen.poll_interval_hint || 3) * 1000,
|
|
83
|
-
timeout: heavy ?
|
|
91
|
+
timeout: heavy ? 480000 : 360000
|
|
84
92
|
});
|
|
85
93
|
|
|
86
94
|
return {
|
|
@@ -109,6 +117,7 @@ function registerGenerateTools(server, client) {
|
|
|
109
117
|
aspect_ratio: z.string().optional().describe('Aspect ratio applied to every scene (e.g., "1:1", "16:9", "9:16"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "1:1"'),
|
|
110
118
|
workflow_type: z.string().optional().describe('"image" (default) or "video"'),
|
|
111
119
|
duration: z.number().optional().describe('Duration in seconds per scene (video mode only). Must be a value in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. E.g., 5 or 10.'),
|
|
120
|
+
sound_enabled: z.boolean().optional().describe('Video mode only. Enable (`true`) or disable (`false`) AI-generated synced audio on every scene. Only honored by models with `sound_generation_type: "native"` from list_models (Veo 3.1, Kling V3/2.6/O3, PixVerse V6). Omit to use each model\'s `sound_enabled_by_default`. Pass `false` when the user says no sound / silent / mute / without audio.'),
|
|
112
121
|
enhance_prompt: z.boolean().optional().describe('Enhance prompts per scene. Default: true'),
|
|
113
122
|
reference_images: z.array(z.string()).optional().describe('Array of reference image URLs to guide style/composition of every scene. **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model.**'),
|
|
114
123
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply consistently across every scene. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model.** This is the ideal way to keep a character or product looking the same in all scenes of a campaign.'),
|
|
@@ -116,9 +125,9 @@ function registerGenerateTools(server, client) {
|
|
|
116
125
|
moodboard_ids: z.array(z.string()).optional().describe('Multiple moodboard IDs when blending styles. Prefer `moodboard_id` for single moodboards.'),
|
|
117
126
|
resolution: z.string().optional().describe('Resolution tier applied to every scene. Images: "1K" / "2K" / "3K" / "4K". Videos: "720p" / "1080p" / "1440p" / "2160p". Values are model-dependent — call list_models and read supported_resolutions on the target model. Multiplied across every scene.')
|
|
118
127
|
},
|
|
119
|
-
async ({ prompt, scene_count, model, aspect_ratio, workflow_type, duration, enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids, resolution }) => {
|
|
128
|
+
async ({ prompt, scene_count, model, aspect_ratio, workflow_type, duration, sound_enabled, enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids, resolution }) => {
|
|
120
129
|
const gen = await client.post('/v1/generate/creative-director', {
|
|
121
|
-
prompt, scene_count, model, aspect_ratio, workflow_type, duration,
|
|
130
|
+
prompt, scene_count, model, aspect_ratio, workflow_type, duration, sound_enabled,
|
|
122
131
|
enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids, resolution
|
|
123
132
|
});
|
|
124
133
|
|
|
@@ -168,11 +177,12 @@ function registerGenerateTools(server, client) {
|
|
|
168
177
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
169
178
|
reference_images: z.array(z.string()).optional().describe('Array of image URLs used as visual references (style / composition / subject). **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model — exceeding it is a deterministic 400.**'),
|
|
170
179
|
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Some models use labels like "512P"/"1024P"/"768P"/"1080P". Model-dependent — call list_models and read supported_resolutions. Read resolution_multipliers to predict cost.'),
|
|
180
|
+
sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Only honored by models with `sound_generation_type: "native"` from list_models (e.g. Veo 3.1, Kling V3/2.6, PixVerse V6). On `sound_generation_type: "none"` models the flag has no effect. Omit to use the model\'s `sound_enabled_by_default`. Pass `false` when the user says no sound / silent / mute / without audio. Enabling sound may apply `sound_credit_multiplier` to cost.'),
|
|
171
181
|
preset_id: z.string().optional().describe('Preset ID from list_presets type="video" to apply a saved motion/style preset to this generation.')
|
|
172
182
|
},
|
|
173
|
-
async ({ prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, preset_id }) => {
|
|
183
|
+
async ({ prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, sound_enabled, preset_id }) => {
|
|
174
184
|
const gen = await client.post('/v1/generate/video', {
|
|
175
|
-
prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, preset_id
|
|
185
|
+
prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, sound_enabled, preset_id
|
|
176
186
|
});
|
|
177
187
|
|
|
178
188
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -209,11 +219,12 @@ function registerGenerateTools(server, client) {
|
|
|
209
219
|
duration: z.number().optional().describe('Duration in seconds. Must be in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. Default: 5'),
|
|
210
220
|
enhance_prompt: z.boolean().optional().describe('Enhance the motion prompt. Default: true'),
|
|
211
221
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to maintain consistency with prior characters / styles. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model; if `supports_visual_dna: false` the model ignores DNA entirely.**'),
|
|
212
|
-
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Some models use labels like "512P"/"1024P"/"768P"/"1080P". Model-dependent — call list_models and read supported_resolutions. Read resolution_multipliers to predict cost.')
|
|
222
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Some models use labels like "512P"/"1024P"/"768P"/"1080P". Model-dependent — call list_models and read supported_resolutions. Read resolution_multipliers to predict cost.'),
|
|
223
|
+
sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Only honored by models with `sound_generation_type: "native"` from list_models (e.g. Veo 3.1 Lite, Kling V3 4K, PixVerse V6, Kling 2.6/v3). On `sound_generation_type: "none"` models the flag has no effect. Omit to use the model\'s `sound_enabled_by_default`. Pass `false` when the user says no sound / silent / mute / without audio. Enabling sound may apply `sound_credit_multiplier` to cost.')
|
|
213
224
|
},
|
|
214
|
-
async ({ image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution }) => {
|
|
225
|
+
async ({ image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, sound_enabled }) => {
|
|
215
226
|
const gen = await client.post('/v1/generate/video/from-image', {
|
|
216
|
-
image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution
|
|
227
|
+
image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, sound_enabled
|
|
217
228
|
});
|
|
218
229
|
|
|
219
230
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -380,16 +391,80 @@ function registerGenerateTools(server, client) {
|
|
|
380
391
|
}
|
|
381
392
|
);
|
|
382
393
|
|
|
394
|
+
// Track how many consecutive get_generation_status polls land on the same
|
|
395
|
+
// id within a short window. Models routinely ignore the "stop polling"
|
|
396
|
+
// hint and re-call, burning minutes of wall-clock and confusing the user.
|
|
397
|
+
// After STATUS_POLL_CAP attempts in a row, we hard-abandon: return a
|
|
398
|
+
// distinct shape (no `still_pending`) so the model can't pattern-match
|
|
399
|
+
// its way back into the loop.
|
|
400
|
+
const STATUS_POLL_CAP = 2;
|
|
401
|
+
const STATUS_POLL_RESET_MS = 10 * 60 * 1000; // a quiet 10min resets the counter
|
|
402
|
+
const statusPollHistory = new Map(); // id → { count, lastAt }
|
|
403
|
+
|
|
383
404
|
// ─── get_generation_status ─────────────────────────────────
|
|
384
405
|
server.tool(
|
|
385
406
|
'get_generation_status',
|
|
386
|
-
'Resume polling a generation after a timeout. Pass the generation_id from a prior generation tool that timed out. This call BLOCKS server-side, polling internally — you do NOT need to call it again in a loop. Defaults to a 10-minute internal poll which covers most image edits and short videos; pass `wait_seconds` up to 1700 (~28 min) for long video / 3D / batch generations. If
|
|
407
|
+
'Resume polling a generation after a timeout. Pass the generation_id from a prior generation tool that timed out. This call BLOCKS server-side, polling internally — you do NOT need to call it again in a loop. Defaults to a 10-minute internal poll which covers most image edits and short videos; pass `wait_seconds` up to 1700 (~28 min) for long video / 3D / batch generations. **If you fired multiple generations in parallel and they all timed out, you MUST call get_generation_status for EACH generation_id in this same turn — do not give up on any of them, or their URLs will be lost forever.** A response with `abandoned: true` is TERMINAL — the generation will not be polled again in this turn; report it to the user and move on. A response with `still_pending: true` means the job is genuinely slow — STOP polling THAT id immediately, tell the user, and wait for them to ask again. Never call this tool more than ONCE per generation_id consecutively in the same turn.',
|
|
387
408
|
{
|
|
388
409
|
generation_id: z.string().describe('The generation ID to check'),
|
|
389
|
-
wait_seconds: z.number().int().min(
|
|
410
|
+
wait_seconds: z.number().int().min(60).max(1700).optional().describe('How long to block-poll internally before giving up (60–1700 seconds, default 600). Values below 300 are clamped up to 300 server-side because shorter waits cause the model to abandon still-running generations. Use higher values for video / 3D / large batches that can legitimately take 15+ minutes.'),
|
|
390
411
|
},
|
|
391
412
|
async ({ generation_id, wait_seconds }) => {
|
|
392
|
-
|
|
413
|
+
// Clamp the floor: shorter waits caused models to abandon generations
|
|
414
|
+
// that completed seconds later (URLs lost forever, paid-for compute
|
|
415
|
+
// wasted). 300s is the floor that survived real-world cases.
|
|
416
|
+
const requested = wait_seconds ?? 600;
|
|
417
|
+
const timeoutMs = Math.max(requested, 300) * 1000;
|
|
418
|
+
|
|
419
|
+
// Update the consecutive-call counter for this id. We reset if the
|
|
420
|
+
// last attempt was a long time ago (new user turn), so this only
|
|
421
|
+
// catches within-turn looping. Opportunistic sweep keeps the map
|
|
422
|
+
// bounded on long-lived MCP servers (no setInterval — that would
|
|
423
|
+
// keep the Node event loop alive forever).
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
if (statusPollHistory.size > 64) {
|
|
426
|
+
const cutoff = now - STATUS_POLL_RESET_MS;
|
|
427
|
+
for (const [k, v] of statusPollHistory) {
|
|
428
|
+
if (v.lastAt < cutoff) statusPollHistory.delete(k);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const prior = statusPollHistory.get(generation_id);
|
|
432
|
+
const count = prior && now - prior.lastAt < STATUS_POLL_RESET_MS ? prior.count + 1 : 1;
|
|
433
|
+
statusPollHistory.set(generation_id, { count, lastAt: now });
|
|
434
|
+
|
|
435
|
+
// Hard abandon — the model has hit the cap. Return a non-pending
|
|
436
|
+
// shape so it has nothing left to keep polling against.
|
|
437
|
+
if (count > STATUS_POLL_CAP) {
|
|
438
|
+
const current = await client
|
|
439
|
+
.get(`/v1/generate/${encodeURIComponent(generation_id)}/status`)
|
|
440
|
+
.catch(() => null);
|
|
441
|
+
// If the generation actually finished in the meantime, surface it.
|
|
442
|
+
if (current?.state === 'completed') {
|
|
443
|
+
statusPollHistory.delete(generation_id);
|
|
444
|
+
return {
|
|
445
|
+
content: [{ type: 'text', text: JSON.stringify(current, null, 2) }],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
content: [
|
|
450
|
+
{
|
|
451
|
+
type: 'text',
|
|
452
|
+
text: JSON.stringify(
|
|
453
|
+
{
|
|
454
|
+
generation_id,
|
|
455
|
+
state: current?.state ?? 'unknown',
|
|
456
|
+
abandoned: true,
|
|
457
|
+
poll_attempts: count,
|
|
458
|
+
_note: `This generation has been polled ${count} consecutive times in this turn and is still not done. ABANDONED — do NOT call get_generation_status for this id again. Tell the user the generation is taking unusually long and ask them to check back later.`,
|
|
459
|
+
},
|
|
460
|
+
null,
|
|
461
|
+
2,
|
|
462
|
+
),
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
393
468
|
let result;
|
|
394
469
|
try {
|
|
395
470
|
result = await pollUntilDone(client, generation_id, {
|
|
@@ -408,13 +483,17 @@ function registerGenerateTools(server, client) {
|
|
|
408
483
|
state: current?.state ?? 'unknown',
|
|
409
484
|
still_pending: true,
|
|
410
485
|
waited_seconds: Math.round(timeoutMs / 1000),
|
|
411
|
-
|
|
486
|
+
poll_attempts: count,
|
|
487
|
+
_note: `Polled for ${minutes} minute(s) — generation is taking longer than usual. STOP calling get_generation_status now. Tell the user the generation is still running and ask them to prompt you to check again later. Do NOT loop — one more consecutive call for this id will be hard-abandoned.`,
|
|
412
488
|
}, null, 2),
|
|
413
489
|
}],
|
|
414
490
|
};
|
|
415
491
|
}
|
|
416
492
|
throw err;
|
|
417
493
|
}
|
|
494
|
+
// Completed cleanly — clear the counter so a future generation reusing
|
|
495
|
+
// the id (theoretically possible) starts fresh.
|
|
496
|
+
statusPollHistory.delete(generation_id);
|
|
418
497
|
return {
|
|
419
498
|
content: [{
|
|
420
499
|
type: 'text',
|
|
@@ -445,9 +524,10 @@ function registerGenerateTools(server, client) {
|
|
|
445
524
|
preset_id: z.string().optional().describe('Preset ID from list_presets type="video" (optional)'),
|
|
446
525
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
447
526
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency across outputs. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model.**'),
|
|
448
|
-
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.')
|
|
527
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
|
|
528
|
+
sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output. Only honored by models with `sound_generation_type: "native"` from list_models (e.g. Kling O3 4K, Kling O3 via KIE). On other models the flag has no effect. Omit to use the model\'s `sound_enabled_by_default`. Pass `false` when the user says no sound / silent / mute / without audio. Enabling sound may apply `sound_credit_multiplier` to cost.')
|
|
449
529
|
},
|
|
450
|
-
async ({ prompt, model, reference_images, reference_videos, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution }) => {
|
|
530
|
+
async ({ prompt, model, reference_images, reference_videos, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, sound_enabled }) => {
|
|
451
531
|
if (!prompt) throw new Error('prompt is required');
|
|
452
532
|
|
|
453
533
|
let startResponse;
|
|
@@ -467,6 +547,7 @@ function registerGenerateTools(server, client) {
|
|
|
467
547
|
if (reference_videos) form.append('reference_videos', JSON.stringify(reference_videos));
|
|
468
548
|
if (audio_url) form.append('audio_url', audio_url);
|
|
469
549
|
if (resolution) form.append('resolution', resolution);
|
|
550
|
+
if (sound_enabled !== undefined) form.append('sound_enabled', String(sound_enabled));
|
|
470
551
|
for (const f of resolved) {
|
|
471
552
|
form.append('files', f.buffer, { filename: f.filename, contentType: f.contentType });
|
|
472
553
|
}
|
|
@@ -474,7 +555,7 @@ function registerGenerateTools(server, client) {
|
|
|
474
555
|
} else {
|
|
475
556
|
// URL-only mode: plain JSON.
|
|
476
557
|
startResponse = await client.post('/v1/generate/elements', {
|
|
477
|
-
prompt, model, reference_images, reference_videos, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution
|
|
558
|
+
prompt, model, reference_images, reference_videos, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, sound_enabled
|
|
478
559
|
});
|
|
479
560
|
}
|
|
480
561
|
|
|
@@ -513,9 +594,10 @@ function registerGenerateTools(server, client) {
|
|
|
513
594
|
aspect_ratio: z.string().optional().describe('Aspect ratio (auto-detected from first frame if not provided). Must be in `supported_aspect_ratios` from list_models when set. Default: "16:9"'),
|
|
514
595
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
515
596
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model; if `supports_visual_dna: false`, DNA is silently ignored.**'),
|
|
516
|
-
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.')
|
|
597
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
|
|
598
|
+
sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Only honored by models with `sound_generation_type: "native"` from list_models (e.g. Veo 3.1 Lite, Kling V3 4K, PixVerse V6). On other models the flag has no effect. Omit to use the model\'s `sound_enabled_by_default`. Pass `false` when the user says no sound / silent / mute / without audio.')
|
|
517
599
|
},
|
|
518
|
-
async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution }) => {
|
|
600
|
+
async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution, sound_enabled }) => {
|
|
519
601
|
const urlMode = first_frame_url && last_frame_url;
|
|
520
602
|
const fileMode = first_frame && last_frame;
|
|
521
603
|
if (!urlMode && !fileMode) {
|
|
@@ -541,10 +623,11 @@ function registerGenerateTools(server, client) {
|
|
|
541
623
|
if (enhance_prompt !== undefined) form.append('enhance_prompt', String(enhance_prompt));
|
|
542
624
|
if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
|
|
543
625
|
if (resolution) form.append('resolution', resolution);
|
|
626
|
+
if (sound_enabled !== undefined) form.append('sound_enabled', String(sound_enabled));
|
|
544
627
|
startResponse = await client.postMultipart('/v1/generate/first-last-frame', form);
|
|
545
628
|
} else {
|
|
546
629
|
startResponse = await client.post('/v1/generate/first-last-frame', {
|
|
547
|
-
first_frame_url, last_frame_url, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution
|
|
630
|
+
first_frame_url, last_frame_url, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution, sound_enabled
|
|
548
631
|
});
|
|
549
632
|
}
|
|
550
633
|
|
|
@@ -654,9 +737,10 @@ function registerGenerateTools(server, client) {
|
|
|
654
737
|
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
|
|
655
738
|
reference_images: z.array(z.string()).optional().describe('Array of reference image URLs for models that support additional image inputs. **Cap: pass at most `max_images` URLs from list_models — if `max_images === 0` the model does not accept image refs.** Examples: character reference images for Kling O1/O3, style reference for Aleph/gen4_aleph, character image for WAN VACE video-edit.'),
|
|
656
739
|
reference_videos: z.array(z.string()).optional().describe('Array of additional reference video URLs for models that support multiple video inputs. **Cap: pass at most `max_videos` URLs from list_models — if `max_videos <= 1` only the source_video is accepted.** Example: WAN 2.6 reference-to-video accepts 1–3 reference videos.'),
|
|
657
|
-
elements: z.array(z.string()).optional().describe('Array of element image URLs. **Cap: pass at most `max_elements` URLs from list_models — if `max_elements === 0` the model does not accept elements.** Elements are style or character reference assets alongside the main video.')
|
|
740
|
+
elements: z.array(z.string()).optional().describe('Array of element image URLs. **Cap: pass at most `max_elements` URLs from list_models — if `max_elements === 0` the model does not accept elements.** Elements are style or character reference assets alongside the main video.'),
|
|
741
|
+
sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Only honored by models with `sound_generation_type: "native"` from list_models (e.g. Kling v3 via KIE). On other models the flag has no effect. Omit to use the model\'s `sound_enabled_by_default`. Pass `false` when the user says no sound / silent / mute / without audio.')
|
|
658
742
|
},
|
|
659
|
-
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, reference_images, reference_videos, elements }) => {
|
|
743
|
+
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, reference_images, reference_videos, elements, sound_enabled }) => {
|
|
660
744
|
if (!source_video) throw new Error('source_video is required');
|
|
661
745
|
if (!prompt) throw new Error('prompt is required');
|
|
662
746
|
|
|
@@ -665,7 +749,7 @@ function registerGenerateTools(server, client) {
|
|
|
665
749
|
if (isUrl) {
|
|
666
750
|
startResponse = await client.post('/v1/generate/video-from-video', {
|
|
667
751
|
video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution,
|
|
668
|
-
reference_images, reference_videos, elements
|
|
752
|
+
reference_images, reference_videos, elements, sound_enabled
|
|
669
753
|
});
|
|
670
754
|
} else {
|
|
671
755
|
const resolved = await resolveToBuffer(source_video, 'video');
|
|
@@ -681,6 +765,7 @@ function registerGenerateTools(server, client) {
|
|
|
681
765
|
if (reference_images) form.append('reference_images', JSON.stringify(reference_images));
|
|
682
766
|
if (reference_videos) form.append('reference_videos', JSON.stringify(reference_videos));
|
|
683
767
|
if (elements) form.append('elements', JSON.stringify(elements));
|
|
768
|
+
if (sound_enabled !== undefined) form.append('sound_enabled', String(sound_enabled));
|
|
684
769
|
startResponse = await client.postMultipart('/v1/generate/video-from-video', form);
|
|
685
770
|
}
|
|
686
771
|
|