@kolbo/mcp 1.75.8 → 1.76.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.75.8",
3
+ "version": "1.76.1",
4
4
  "description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -655,6 +655,13 @@ async function uiCompleted(p, textPayload, extraContent) {
655
655
  // above which assume everything finished together. Only set when the
656
656
  // caller actually has this shape; every existing caller is unaffected.
657
657
  ...(Array.isArray(p.items) ? { items: p.items } : {}),
658
+ // The RAW generation state, when the caller has one. `phase` above is
659
+ // hardcoded 'completed' — it means "this tool call finished", not "the
660
+ // generation finished" — so a status check on a still-running job looked
661
+ // done to every reader. A live generation card polls get_generation_status
662
+ // from its own iframe and reads `state` FIRST; without it, a processing job
663
+ // resolved as completed-with-no-media and painted a red failure card.
664
+ ...(p.state ? { state: p.state } : {}),
658
665
  credits_used: p.credits_used,
659
666
  open_url: buildOpenUrl(p.tool, p.gen),
660
667
  };
@@ -108,6 +108,17 @@ async function pollBatch(client, batch, { interval, timeout }, toolName) {
108
108
  }, text);
109
109
  }
110
110
 
111
+ // Same extension sniff the generation widget already does (kindFromTool in
112
+ // apps/widgets/generation.js) — a status check knows a url came back, not what
113
+ // kind of media it is, and the card needs the media kind to pick a renderer.
114
+ function mediaKind(url) {
115
+ const u = String(url || '').split('?')[0].toLowerCase();
116
+ if (/\.(mp4|mov|webm|mkv)$/.test(u)) return 'video';
117
+ if (/\.(mp3|wav|m4a|aac|ogg|flac)$/.test(u)) return 'audio';
118
+ if (/\.(glb|gltf|fbx|obj|usdz)$/.test(u)) return '3d';
119
+ return 'image';
120
+ }
121
+
111
122
  // ─── Widget settings block ──────────────────────────────────────────────────
112
123
  // What the CALLER actually asked for, for the generation card AND for the model
113
124
  // reading the tool result. Undefined/false keys are dropped by JSON.stringify, so
@@ -508,13 +519,13 @@ function registerGenerateTools(server, client, options = {}) {
508
519
  );
509
520
 
510
521
  // ─── generate_video ────────────────────────────────────────
511
- // NOTE: text-to-video does NOT support Visual DNA the textToVideoGeneration
512
- // controller in kolbo-api never reads visualDnaIds. For character-consistent
513
- // video, use generate_elements (which DOES honor visual_dna_ids) or animate a
514
- // DNA-locked still via generate_video_from_image.
522
+ // Text-to-video is now a MODE inside videoGeneration/controller.js (sdk/controller.js
523
+ // posts mode:'text_to_video'), and that controller DOES read visualDnaIds so Visual
524
+ // DNA works here. The old "text-to-video never reads visualDnaIds" note described the
525
+ // retired textToVideoGeneration path and was stale.
515
526
  server.tool(
516
527
  'generate_video',
517
- 'Generate a video from a text prompt using Kolbo AI. For SEVERAL different videos, pass all their prompts in `prompts` in ONE call (one combined widget) — never a series of separate calls. For animating an existing still image into motion, use generate_video_from_image instead. For a coordinated multi-scene video campaign, use generate_creative_director with workflow_type="video". Supports reference images (for style/composition guidance). Does NOT support Visual DNA for character-consistent video use generate_elements or animate a DNA-locked still via generate_video_from_image. Returns the final video URL when complete.',
528
+ 'Generate a video from a text prompt using Kolbo AI. For SEVERAL different videos, pass all their prompts in `prompts` in ONE call (one combined widget) — never a series of separate calls. For animating an existing still image into motion, use generate_video_from_image instead. For a coordinated multi-scene video campaign, use generate_creative_director with workflow_type="video". Supports reference images (for style/composition guidance) and Visual DNA for character consistency. Returns the final video URL when complete.',
518
529
  {
519
530
  prompt: z.string().optional().describe('Text description of the video to generate. Required unless `prompts` is provided.'),
520
531
  prompts: promptsField('videos'),
@@ -525,16 +536,17 @@ function registerGenerateTools(server, client, options = {}) {
525
536
  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.**'),
526
537
  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.'),
527
538
  preset_id: z.string().optional().describe('Preset ID from list_presets type="video" to apply a saved motion/style preset to this generation.'),
539
+ visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency. Every DNA passed here MUST also be tagged in the prompt as @ExactDNAName. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model; if `supports_visual_dna: false`, DNA is silently ignored.**'),
528
540
  sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Honored by `sound_generation_type: "native"` models (Veo 3.1, Kling V3/2.6, PixVerse V6). Seedance 2.x reports type "none" (no toggle) but `sound_baked_in: true` — those still emit real audio; do not tell the user the model is silent. Omit to use `sound_enabled_by_default`. Pass `false` only when the user asks for silent AND the model is native (not baked-in). Enabling sound may apply `sound_credit_multiplier` to cost.'),
529
541
  skip_color_palette: z.boolean().optional().describe('Opt this single call OUT of the account\'s active Color DNA palette (see list_color_palettes / activate_color_palette). By default, if the user has an active palette it strict-grades every generation automatically — pass true only when the user explicitly wants this one video ungraded.'),
530
542
  project_id: projectIdField,
531
543
  session_id: sessionIdField
532
544
  },
533
- async ({ prompt, prompts, model, aspect_ratio, duration, enhance_prompt = false, reference_images, resolution, preset_id, sound_enabled, skip_color_palette, project_id, session_id }) => {
545
+ async ({ prompt, prompts, model, aspect_ratio, duration, enhance_prompt = false, reference_images, resolution, preset_id, visual_dna_ids, sound_enabled, skip_color_palette, project_id, session_id }) => {
534
546
  if (!prompt && !(prompts && prompts.length)) throw new Error('Provide prompt or prompts');
535
547
  model = await canonicalModelId(client, model, 'text_to_video'); // lenient id resolution ("z-image" → "z-image/turbo")
536
548
  const shared = {
537
- model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, preset_id, sound_enabled, skip_color_palette, project_id, session_id
549
+ model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, preset_id, visual_dna_ids, sound_enabled, skip_color_palette, project_id, session_id
538
550
  };
539
551
 
540
552
  // Batch mode: N different prompts, one widget owning all generation ids.
@@ -1015,10 +1027,29 @@ function registerGenerateTools(server, client, options = {}) {
1015
1027
  : pendingHint;
1016
1028
  const singleText = JSON.stringify(single, null, 2);
1017
1029
  const res = single.result || {};
1030
+ // A live generation card polls THIS branch from inside its own iframe,
1031
+ // and it reads structuredContent — never the text (apps/html.js
1032
+ // structured() returns structuredContent first and only falls back to
1033
+ // parsing content[].text). So preserving the flat TEXT shape, as the
1034
+ // comment above intended, protected nothing: the card does
1035
+ // `r = st.result || st` then `r.urls`, found only `items`, and painted
1036
+ // "No output received / Failed" over a generation that had completed
1037
+ // and billed. Every card rendered before v1.74 keeps that old iframe JS
1038
+ // forever, so the flat fields have to live in structuredContent too.
1039
+ const urls = Array.isArray(res.urls) ? res.urls : [];
1040
+ const done = single.state === 'completed' && urls.length > 0;
1018
1041
  return uiCompleted({
1019
- tool: 'get_generation_status', kind: 'status', client,
1020
- model: 'Generations', gen: { generation_id: single.generation_id },
1042
+ tool: 'get_generation_status', kind: done ? mediaKind(urls[0]) : 'status', client,
1043
+ // The model that ACTUALLY ran. A single-id check resolves one
1044
+ // generation, so 'Generations' here overwrote the real model name on
1045
+ // the finished card the moment the live widget merged this payload in.
1046
+ model: res.model || 'Generations', gen: { generation_id: single.generation_id },
1021
1047
  settings: {},
1048
+ state: single.state,
1049
+ urls: done ? urls : undefined,
1050
+ thumbnail_url: res.thumbnail_url,
1051
+ prompt: res.prompt_used || res.prompt || undefined,
1052
+ credits_used: creditFields(single).credits_used,
1022
1053
  items: [{
1023
1054
  id: single.generation_id,
1024
1055
  state: single.state,
@@ -1147,6 +1178,7 @@ function registerGenerateTools(server, client, options = {}) {
1147
1178
  enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
1148
1179
  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.**'),
1149
1180
  resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
1181
+ sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Honored by `sound_generation_type: "native"` models (Kling O3/V3, Veo 3.1, PixVerse V6). Omit to use `sound_enabled_by_default`. Enabling sound may apply `sound_credit_multiplier` to cost.'),
1150
1182
  keyframes: z.array(z.object({
1151
1183
  image_url: z.string().describe('Public URL of the keyframe image'),
1152
1184
  timestamp_seconds: z.number().describe('Moment on the OUTPUT timeline (seconds, 0 = first frame) where this image is pinned')
@@ -1157,7 +1189,7 @@ function registerGenerateTools(server, client, options = {}) {
1157
1189
  project_id: projectIdField,
1158
1190
  session_id: sessionIdField
1159
1191
  },
1160
- async ({ prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt = false, visual_dna_ids, resolution, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id }) => {
1192
+ async ({ prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt = false, visual_dna_ids, resolution, sound_enabled, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id }) => {
1161
1193
  model = await canonicalModelId(client, model, 'elements'); // lenient id resolution ("z-image" → "z-image/turbo")
1162
1194
  if (!prompt) throw new Error('prompt is required');
1163
1195
 
@@ -1179,6 +1211,7 @@ function registerGenerateTools(server, client, options = {}) {
1179
1211
  if (reference_audio_urls) form.append('reference_audio_urls', JSON.stringify(reference_audio_urls));
1180
1212
  if (audio_url) form.append('audio_url', audio_url);
1181
1213
  if (resolution) form.append('resolution', resolution);
1214
+ if (sound_enabled !== undefined) form.append('sound_enabled', String(sound_enabled));
1182
1215
  if (keyframes) form.append('keyframes', JSON.stringify(keyframes));
1183
1216
  if (multi_shots !== undefined) form.append('multi_shots', String(multi_shots));
1184
1217
  if (multi_shot_count !== undefined) form.append('multi_shot_count', String(multi_shot_count));
@@ -1192,7 +1225,7 @@ function registerGenerateTools(server, client, options = {}) {
1192
1225
  } else {
1193
1226
  // URL-only mode: plain JSON.
1194
1227
  startResponse = await client.post('/v1/generate/elements', {
1195
- prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id
1228
+ prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, sound_enabled, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id
1196
1229
  });
1197
1230
  }
1198
1231
 
@@ -1269,10 +1302,11 @@ function registerGenerateTools(server, client, options = {}) {
1269
1302
  enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
1270
1303
  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.**'),
1271
1304
  resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
1305
+ sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Honored by `sound_generation_type: "native"` models (Kling O3/V3, Veo 3.1, PixVerse V6). Omit to use `sound_enabled_by_default`. Enabling sound may apply `sound_credit_multiplier` to cost.'),
1272
1306
  project_id: projectIdField,
1273
1307
  session_id: sessionIdField
1274
1308
  },
1275
- async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt = false, visual_dna_ids, resolution, project_id, session_id }) => {
1309
+ async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt = false, visual_dna_ids, resolution, sound_enabled, project_id, session_id }) => {
1276
1310
  model = await canonicalModelId(client, model, 'firstlastgenerations'); // lenient id resolution ("z-image" → "z-image/turbo")
1277
1311
  const urlMode = first_frame_url && last_frame_url;
1278
1312
  const fileMode = first_frame && last_frame;
@@ -1299,12 +1333,13 @@ function registerGenerateTools(server, client, options = {}) {
1299
1333
  if (enhance_prompt !== undefined) form.append('enhance_prompt', String(enhance_prompt));
1300
1334
  if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
1301
1335
  if (resolution) form.append('resolution', resolution);
1336
+ if (sound_enabled !== undefined) form.append('sound_enabled', String(sound_enabled));
1302
1337
  if (project_id) form.append('project_id', project_id);
1303
1338
  if (session_id) form.append('session_id', session_id);
1304
1339
  startResponse = await client.postMultipart('/v1/generate/first-last-frame', form);
1305
1340
  } else {
1306
1341
  startResponse = await client.post('/v1/generate/first-last-frame', {
1307
- first_frame_url, last_frame_url, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution, project_id, session_id
1342
+ first_frame_url, last_frame_url, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution, sound_enabled, project_id, session_id
1308
1343
  });
1309
1344
  }
1310
1345
 
@@ -1477,6 +1512,7 @@ function registerGenerateTools(server, client, options = {}) {
1477
1512
  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.'),
1478
1513
  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.'),
1479
1514
  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.'),
1515
+ sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Honored by `sound_generation_type: "native"` models (Kling O3/V3, Veo 3.1, PixVerse V6). Omit to use `sound_enabled_by_default`. Enabling sound may apply `sound_credit_multiplier` to cost.'),
1480
1516
  // VEED Subtitles (model: veed/subtitles) — burns styled subtitles into the video
1481
1517
  preset: z.string().optional().describe('VEED Subtitles only: caption style preset (e.g. "glass", "whisper", "fusion", "simple", "vegas"). Call list_models type="video_to_video" for the veed/subtitles model. Ignored by other models.'),
1482
1518
  source_language: z.string().optional().describe('VEED Subtitles only: BCP-47 code of the spoken language to improve transcription accuracy (e.g. "en-US", "es-ES", "he-IL"). Omit to auto-detect.'),
@@ -1498,7 +1534,7 @@ function registerGenerateTools(server, client, options = {}) {
1498
1534
  project_id: projectIdField,
1499
1535
  session_id: sessionIdField
1500
1536
  },
1501
- async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, resolution, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, project_id, session_id }) => {
1537
+ async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, resolution, sound_enabled, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, project_id, session_id }) => {
1502
1538
  model = await canonicalModelId(client, model, 'video_to_video'); // lenient id resolution ("z-image" → "z-image/turbo")
1503
1539
  if (!source_video) throw new Error('source_video is required');
1504
1540
 
@@ -1506,7 +1542,7 @@ function registerGenerateTools(server, client, options = {}) {
1506
1542
  let startResponse;
1507
1543
  if (isUrl) {
1508
1544
  startResponse = await client.post('/v1/generate/video-from-video', {
1509
- video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution,
1545
+ video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, sound_enabled,
1510
1546
  reference_images, reference_videos, elements, preset, source_language, translation_language,
1511
1547
  srt_content, srt_file_url, vocabulary, customization, project_id, session_id
1512
1548
  });
@@ -1528,6 +1564,7 @@ function registerGenerateTools(server, client, options = {}) {
1528
1564
  if (enhance_prompt !== undefined) form.append('enhance_prompt', String(enhance_prompt));
1529
1565
  if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
1530
1566
  if (resolution) form.append('resolution', resolution);
1567
+ if (sound_enabled !== undefined) form.append('sound_enabled', String(sound_enabled));
1531
1568
  if (reference_images) form.append('reference_images', JSON.stringify(reference_images));
1532
1569
  if (reference_videos) form.append('reference_videos', JSON.stringify(reference_videos));
1533
1570
  if (elements) form.append('elements', JSON.stringify(elements));