@kolbo/mcp 1.76.3 → 1.76.5

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.76.3",
3
+ "version": "1.76.5",
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": {
@@ -110,6 +110,7 @@ These elevate rich cinematic / reference-anchored sequences. For a short, tight,
110
110
  ## Dialogue & expression
111
111
 
112
112
  - Dialogue goes in quotes and may be in ANY language (Hebrew included). For silent tension, deliver it as expression, not speech: `He does not speak. His expression clearly says: "…"`.
113
+ - **Seedance PERFORMS quoted dialogue natively** — synced voices, lip movement, and room tone come out of the video model itself. Never route scene dialogue through TTS (`generate_speech`) or `generate_lipsync`; write each line in quotes inside its shot beat (`DANIEL says: "…"`) and generate once.
113
114
 
114
115
  ## Content tone
115
116
 
@@ -9,7 +9,7 @@ Load this file when the user wants a **Seedance 2.5** video (they said "2.5" / "
9
9
 
10
10
  **Kolbo MCP routing:** `generate_video` or `generate_elements` (refs / Visual DNA / first-last). Run `list_models({ type: "text_to_video" })` and pick the Seedance 2.5 variant by name.
11
11
 
12
- **Audio:** Seedance 2.5 still emits real synced audio. `list_models` may show `sound_generation_type: none` because there is no in-app toggle (`sound_baked_in: true`). Do not tell the user the model is silent.
12
+ **Audio:** Seedance 2.5 still emits real synced audio. `list_models` may show `sound_generation_type: none` because there is no in-app toggle (`sound_baked_in: true`). Do not tell the user the model is silent. Quoted dialogue in the prompt is PERFORMED — synced voices, lip movement, room tone — so scene dialogue never goes through `generate_speech` or `generate_lipsync`; write the lines in quotes inside their shot beats.
13
13
 
14
14
  ## What's NEW in 2.5 (verified — never hedge)
15
15
 
@@ -668,15 +668,21 @@ function renderAudio(sc, urls) {
668
668
  var titleBase = track.title || sc.title || (TOOL_TITLES[sc.tool] || 'Audio');
669
669
  var title = titleBase + (urls.length > 1 ? ' — Track ' + (i + 1) : '');
670
670
  var duration = track.duration != null ? track.duration : sc.duration;
671
- var artwork = track.thumbnail_url || sc.thumbnail_url;
671
+ // The voice's own portrait is the artwork for speech a generic note glyph
672
+ // told the user nothing about the one thing that defines the take.
673
+ var artwork = track.thumbnail_url || sc.thumbnail_url || sc.voice_thumbnail;
674
+ var placeholder = sc.tool === 'generate_speech' ? ICONS.mic : ICONS.audio;
672
675
  return '<div class="k-audio-row k-generated-audio">' +
673
- (artwork ? '<img class="k-audio-art" src="' + esc(artwork) + '" alt="" loading="lazy">' :
674
- '<div class="k-audio-art k-audio-placeholder">' + ICONS.audio + '</div>') +
676
+ (artwork ? '<img class="k-audio-art" src="' + esc(artwork) + '" alt="" loading="lazy" onerror="this.style.display=\\'none\\'">' :
677
+ '<div class="k-audio-art k-audio-placeholder">' + placeholder + '</div>') +
675
678
  '<div class="k-audio-meta"><div class="k-audio-title">' + esc(title) + '</div>' +
676
- // Resolved name first: a per-track model field is the raw id, and every
677
- // track in one generation came from the same model anyway.
678
- '<div class="k-audio-sub">' + esc(modelLabel(sc) || track.model || '') +
679
- (duration ? ' · ' + fmtDur(duration) : '') + '</div></div>' +
679
+ // Voice FIRST where there is one on a speech row, who is speaking is the
680
+ // thing that defines the take; the engine is secondary. Resolved names
681
+ // only: a per-track model field is the raw id, and every track in one
682
+ // generation came from the same model anyway.
683
+ '<div class="k-audio-sub">' +
684
+ [voiceLabel(sc), modelLabel(sc) || track.model, duration ? fmtDur(duration) : '']
685
+ .filter(Boolean).map(esc).join(' · ') + '</div></div>' +
680
686
  '<button class="k-btn k-audio-download" data-audio-download="' + esc(u) +
681
687
  '" aria-label="Download ' + esc(title) + '">' + ICONS.download + ' Download</button>' +
682
688
  '<audio class="k-audio-player" src="' + esc(u) + '" controls preload="none" aria-label="Play ' +
@@ -611,6 +611,7 @@ async function uiGenerating(p) {
611
611
  ? { failed_submissions: p.failed_submissions } : {}),
612
612
  ...(p.warning ? { _warning: p.warning } : {}),
613
613
  _widget_note: 'A live Kolbo widget is rendering this generation for the user (progress + final result + action buttons). Tell the user it is generating and the card above will update — do NOT poll in a loop. If you need the output URLs (e.g. for a follow-up edit or a report), call get_generation_status ONCE with wait=true — it blocks until done. Tracking several generations? Pass ALL their ids in generation_ids in that one call.',
614
+ _paid_note: 'This generation is RUNNING and the user is paying for it. If you now realize the tool, model, or parameters were wrong, call cancel_generation with this generation_id FIRST, then start the replacement — never leave a wrong generation running alongside its retry (the user gets two cards and two charges).',
614
615
  }, null, 2);
615
616
  return uiResult(UI.generation, text, structured);
616
617
  }
@@ -659,6 +660,14 @@ async function uiCompleted(p, textPayload, extraContent) {
659
660
  // above which assume everything finished together. Only set when the
660
661
  // caller actually has this shape; every existing caller is unaffected.
661
662
  ...(Array.isArray(p.items) ? { items: p.items } : {}),
663
+ // The voice, by name and portrait. uiGenerating has carried this since the
664
+ // chips were introduced; uiCompleted never did, so it silently dropped a
665
+ // resolved voice its caller had already looked up — every FINISHED speech
666
+ // card fell back to `settings.voice`, printing a raw ElevenLabs id where the
667
+ // name belongs and rendering the generic note placeholder instead of the
668
+ // voice's face. Exactly the "never show a raw id on a card" rule, broken on
669
+ // the one path the user actually ends up looking at.
670
+ ...(p.voice ? { voice_name: p.voice.name, voice_thumbnail: p.voice.thumbnail } : {}),
662
671
  // The RAW generation state, when the caller has one. `phase` above is
663
672
  // hardcoded 'completed' — it means "this tool call finished", not "the
664
673
  // generation finished" — so a status check on a still-running job looked
@@ -541,7 +541,7 @@ function registerGenerateTools(server, client, options = {}) {
541
541
  // retired textToVideoGeneration path and was stale.
542
542
  server.tool(
543
543
  'generate_video',
544
- '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.',
544
+ '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. ROUTE BEFORE CALLING: when reference images anchor IDENTITY (specific characters, a specific product, a location that must match) — especially 2+ of them — that is generate_elements, not this tool; reference_images here are loose style/composition hints. Decide the right tool FIRST: a mis-routed call still starts a PAID generation, and switching tools afterwards without cancel_generation leaves the user paying for both. Returns the final video URL when complete.',
545
545
  {
546
546
  prompt: z.string().optional().describe('Text description of the video to generate. Required unless `prompts` is provided.'),
547
547
  prompts: promptsField('videos'),
@@ -855,11 +855,19 @@ function registerGenerateTools(server, client, options = {}) {
855
855
  if (poll.timedOut) return poll.timedOut;
856
856
  const result = poll.result;
857
857
 
858
+ // What ACTUALLY ran, not what was asked for. The status result reports the
859
+ // engine's own choice (`model: "google_tts"`, `voice: "he-IL-Chirp3-HD-Kore"`)
860
+ // and for an omitted model that is the ONLY place the real answer appears —
861
+ // the card was labelling those "Smart Select", which is not even a text-to-
862
+ // speech option, and naming the voice the caller typed rather than the one
863
+ // that spoke. Same resolution addDisplayNames does for the polling path.
864
+ const ranVoice = (await voiceInfo(client, result.result?.voice).catch(() => null)) || voiceRecord;
858
865
  return uiCompleted({
859
- tool: 'generate_speech', kind: 'audio', gen, client, model, prompt: text,
860
- voice: voiceRecord,
866
+ tool: 'generate_speech', kind: 'audio', gen, client, prompt: text,
867
+ model: result.result?.model || model,
868
+ voice: ranVoice,
861
869
  settings: {
862
- voice: voice || 'Rachel',
870
+ voice: (ranVoice && ranVoice.name) || voice || 'Rachel',
863
871
  style: selected_style || emotion || style_instructions_preset_id || style_instructions,
864
872
  speaking_speed,
865
873
  language,