@kolbo/mcp 1.77.3 → 1.79.0

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.77.3",
3
+ "version": "1.79.0",
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": {
@@ -31,7 +31,12 @@ function registerColorPaletteTools(server, client, options = {}) {
31
31
  const palettes = result.color_palettes || [];
32
32
  const text = JSON.stringify({ color_palettes: palettes, pagination: result.pagination }, null, 2);
33
33
 
34
- if (ui()) {
34
+ // Ship structuredContent UNCONDITIONALLY. Gating on ui() left every host that
35
+ // renders widgets without advertising MCP Apps (Kolbo Code) with text-only rows
36
+ // that carry no thumbnail field at all — and its BY_TOOL map still force-mounts
37
+ // the media grid on them, so the card rendered one broken-file glyph per cell.
38
+ // media.js and listResult() have always done it this way; these five lagged.
39
+ {
35
40
  return uiResult(UI.mediaGrid, text, {
36
41
  widget: 'media-grid',
37
42
  title: 'Color DNA Palettes',
@@ -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. 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.',
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. Seedance 2/2.5 PERFORM quoted dialogue natively (synced voice, lip movement, room tone) — do not route scene dialogue to generate_speech or generate_lipsync; write it in ENGLISH (other languages, Hebrew included, do not perform reliably). Resolution is a credit MULTIPLIER (vs 720p: 480p x0.44, 1080p x2.25, 4k x4.95), so draft at 480p and re-run only the approved cut at delivery resolution. 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'),
@@ -621,7 +621,7 @@ function registerGenerateTools(server, client, options = {}) {
621
621
  // ─── generate_video_from_image ─────────────────────────────
622
622
  server.tool(
623
623
  'generate_video_from_image',
624
- 'Animate an existing still image into a video using Kolbo AI. The image comes from `image_url`; `prompt` describes the motion (not the subject — the subject is already in the image). For generating a video from scratch, use generate_video. ANIMATING SEVERAL SHOTS OF THE SAME SEQUENCE? Pass them ALL in `items` in ONE call (one combined widget) — never a series of separate calls, which buries the chat under one widget per clip. Across calls (a sequence longer than the batch cap), make the first call without `session_id`, take the `session_id` from its result, and pass that same `session_id` on every following call — otherwise each clip becomes its own session and the user gets a stack of near-identical single-clip sessions in the Kolbo sidebar. Returns the final video URL(s) when complete.',
624
+ 'Animate an existing still image into a video using Kolbo AI. The image comes from `image_url`; `prompt` describes the motion (not the subject — the subject is already in the image). For generating a video from scratch, use generate_video. FOR A MULTI-SHOT FILM with recurring characters or locations, prefer ONE generate_elements call carrying the whole DNA-anchored sequence over a series of separate image-to-video clips. ANIMATING SEVERAL SHOTS OF THE SAME SEQUENCE? Pass them ALL in `items` in ONE call (one combined widget) — never a series of separate calls, which buries the chat under one widget per clip. Across calls (a sequence longer than the batch cap), make the first call without `session_id`, take the `session_id` from its result, and pass that same `session_id` on every following call — otherwise each clip becomes its own session and the user gets a stack of near-identical single-clip sessions in the Kolbo sidebar. Returns the final video URL(s) when complete.',
625
625
  {
626
626
  image_url: z.string().optional().describe('URL of the source image to animate. Required unless `items` is provided.'),
627
627
  prompt: z.string().optional().describe('Text description of the desired MOTION (e.g., "camera slowly pans right while the character walks forward"). Required unless `items` is provided.'),
@@ -772,7 +772,7 @@ function registerGenerateTools(server, client, options = {}) {
772
772
  // ─── generate_speech ───────────────────────────────────────
773
773
  server.tool(
774
774
  'generate_speech',
775
- 'Convert text to speech using Kolbo AI. Default provider is ElevenLabs. To pick a specific voice by language/gender, call list_voices first and pass the returned voice_id (or a voice display name — both work). Every voice belongs to a provider (ElevenLabs, DeepDub, MiniMax, Google/Gemini, OpenAI, Zonos) and each provider exposes its own expressive/style controls below — the engine ignores any control that does not apply to the chosen voice\'s provider, so it is safe to pass only what you need. Returns the final audio URL when complete.',
775
+ 'Convert text to speech using Kolbo AI. SCOPE: narration, voiceover, and standalone audio (ads, explainers, audiobooks, announcements). This is NOT the route for character dialogue in a film you are generating with Seedance 2/2.5 — those models PERFORM quoted dialogue natively (synced voices, lip movement, room tone), so sending scene dialogue here just adds a second, unsynced voice track. Default provider is ElevenLabs. To pick a specific voice by language/gender, call list_voices first and pass the returned voice_id (or a voice display name — both work). Every voice belongs to a provider (ElevenLabs, DeepDub, MiniMax, Google/Gemini, OpenAI, Zonos) and each provider exposes its own expressive/style controls below — the engine ignores any control that does not apply to the chosen voice\'s provider, so it is safe to pass only what you need. Returns the final audio URL when complete.',
776
776
  {
777
777
  text: z.string().describe('The text to convert to speech'),
778
778
  voice: z.string().optional().describe('Voice ID or display name — MUST come from a `list_voices` result, never constructed. Google/Gemini ids in particular are not validated provider-side: an id that is not in the catalog is silently mapped to another voice (or a default one) and the audio comes back in a voice nobody asked for. Do not pattern-match a locale onto an id you saw for another language. Default: "Rachel"'),
@@ -1185,7 +1185,7 @@ function registerGenerateTools(server, client, options = {}) {
1185
1185
  // ─── generate_elements ─────────────────────────────────────
1186
1186
  server.tool(
1187
1187
  'generate_elements',
1188
- 'Generate a video from reference elements (images, videos, and/or audio) + a text prompt. Use when the user wants to animate specific uploaded/referenced assets — e.g. "animate this product", "put these 3 characters into a scene". PROMPT CONTRACT (Seedance / Elements): Locked Intro only — Total line, then [GLOBAL LOOK] / [CAST] / [LOCATION] / SHOT N. Do NOT write SCENE CONTEXT / OPTICS / ACTION department packs. Every Visual DNA in visual_dna_ids MUST also appear in the prompt as @ExactDNAName (e.g. "@Zohar walks…") — never "Zohar\'s" or "the man on the left" as a substitute. IMPORTANT: different models accept different numbers of inputs — call list_models type="elements" and read elements_max_images / elements_max_videos / elements_max_audio on the chosen model before generating. For text-only → video use generate_video instead. For animating a single still image use generate_video_from_image. Returns the final video URL when complete.',
1188
+ 'Generate a video from reference elements (images, videos, and/or audio) + a text prompt. Use when the user wants to animate specific uploaded/referenced assets — e.g. "animate this product", "put these 3 characters into a scene". PRIMARY ROUTE FOR A DNA-ANCHORED MULTI-SHOT FILM: one call can carry the whole sequence — seedance-2-5 takes 4-30s, up to 30 shots and 20 Visual DNAs in a SINGLE generation (seedance-2: 4-15s, 9 DNAs) — instead of a stack of separate clips. DIALOGUE IS PERFORMED NATIVELY: quoted dialogue in the prompt comes back as synced voices with lip movement, room tone and the SFX named in the AUDIO block — never route scene dialogue to generate_speech or generate_lipsync. Write dialogue in ENGLISH; other languages (Hebrew included) do not perform reliably. COST: resolution is a MULTIPLIER, not a flat fee (vs 720p: 480p x0.44, 1080p x2.25, 4k x4.95) — run blocking/draft passes at 480p and re-run only the approved cut at delivery resolution. PROMPT CONTRACT (Seedance / Elements): Locked Intro only — Total line, then [GLOBAL LOOK] / [CAST] / [LOCATION] / SHOT N. Do NOT write SCENE CONTEXT / OPTICS / ACTION department packs. Every Visual DNA in visual_dna_ids MUST also appear in the prompt as @ExactDNAName (e.g. "@Zohar walks…") — never "Zohar\'s" or "the man on the left" as a substitute. IMPORTANT: different models accept different numbers of inputs — call list_models type="elements" and read elements_max_images / elements_max_videos / elements_max_audio on the chosen model before generating. For text-only → video use generate_video instead. For animating a single still image use generate_video_from_image. Returns the final video URL when complete.',
1189
1189
  {
1190
1190
  prompt: z.string().describe('Locked Intro prompt (Seedance/Elements): Total line, [GLOBAL LOOK], [CAST] with @ExactDNAName for every visual_dna_ids entry, [LOCATION], then SHOT N. Not SCENE CONTEXT/OPTICS/ACTION packs. Never substitute "the left man" or "Zohar\'s" for @Name.'),
1191
1191
  model: z.string().optional().describe('Model identifier. If the user already named a family (Grok / Kling / Veo / Seedance / …), pass THAT family — never default to Seedance because Elements often uses it. Use list_models type="elements" for exact ids and elements_max_* caps. Do NOT omit (omitting = Smart Select).'),
@@ -1462,7 +1462,7 @@ function registerGenerateTools(server, client, options = {}) {
1462
1462
  // ─── generate_lipsync ──────────────────────────────────────
1463
1463
  server.tool(
1464
1464
  'generate_lipsync',
1465
- 'Lipsync an audio track to a source image or video. Both `source` (image or video) and `audio` can be provided as URLs or as absolute local file paths. Pass a text_prompt only if the model supports it (some lipsync models do character performance from a prompt). **Validate before submitting: for `lipsync-video` sources, the input video duration must fall within `min_video_duration`-`max_video_duration` from list_models; audio duration must fall within `min_audio_duration`-`max_audio_duration` (and if `audio_max_follows_video_duration: true`, audio is also capped at the video duration); audio format must be in `supported_audio_formats` when specified.** Returns a lipsynced video URL.',
1465
+ 'Lipsync an audio track to a source image or video. SCOPE: it syncs an EXISTING audio waveform to a face in footage you already have (dubbing, translation, fixing a delivered take). It is NOT how you get characters to speak in a film you are generating — Seedance 2/2.5 perform quoted dialogue natively with synced lip movement, so a scene generated there needs no lipsync pass. Both `source` (image or video) and `audio` can be provided as URLs or as absolute local file paths. Pass a text_prompt only if the model supports it (some lipsync models do character performance from a prompt). **Validate before submitting: for `lipsync-video` sources, the input video duration must fall within `min_video_duration`-`max_video_duration` from list_models; audio duration must fall within `min_audio_duration`-`max_audio_duration` (and if `audio_max_follows_video_duration: true`, audio is also capped at the video duration); audio format must be in `supported_audio_formats` when specified.** Returns a lipsynced video URL.',
1466
1466
  {
1467
1467
  source: z.string().describe('URL or absolute local path to the source image or video (the face to animate). For lipsync-video: duration must fall within `min_video_duration`-`max_video_duration` from list_models.'),
1468
1468
  audio: z.string().describe('URL or absolute local path to the audio track (the voice to sync to). Duration must fall within `min_audio_duration`-`max_audio_duration` from list_models; format must be in `supported_audio_formats` (when set).'),
@@ -1581,7 +1581,7 @@ function registerGenerateTools(server, client, options = {}) {
1581
1581
  // ─── generate_video_from_video ─────────────────────────────
1582
1582
  server.tool(
1583
1583
  'generate_video_from_video',
1584
- 'Restyle / transform an existing video (video-to-video). Use for style transfer, scene restyling, subject swap, motion transfer, character replacement, or burning in styled subtitles (VEED Subtitles). Source video can be a URL or absolute local path. `prompt` is OPTIONAL: most models need it, but prompt-less models (VEED Subtitles, Act Two, Wan Animate, Kling Motion Control) ignore it. For VEED Subtitles, pass a `preset` style and optional `source_language` / `translation_language` instead of a prompt. IMPORTANT: different models support different extra inputs — call list_models type="video_to_video" and read max_images / max_videos / max_elements on the chosen model before generating. Pass reference_images for models with max_images > 0 (e.g. Kling O1/O3, Aleph, WAN VACE), reference_videos for models with max_videos > 1 (e.g. WAN 2.6 reference-to-video accepts up to 3), and elements for models with max_elements > 0. For animating a still image use generate_video_from_image instead. For text-only → video use generate_video.',
1584
+ 'Restyle / transform an existing video (video-to-video). Use for style transfer, scene restyling, subject swap, motion transfer, character replacement, or burning in styled subtitles (VEED Subtitles). Source video can be a URL or absolute local path. `prompt` is OPTIONAL: most models need it, but prompt-less models (VEED Subtitles, Act Two, Wan Animate, Kling Motion Control) ignore it. For VEED Subtitles, pass a `preset` style and optional `source_language` / `translation_language` instead of a prompt. IMPORTANT: different models support different extra inputs — call list_models type="video_to_video" and read max_images / max_videos / max_elements on the chosen model before generating. Pass reference_images for models with max_images > 0 (e.g. Kling O1/O3, Aleph, WAN VACE), reference_videos for models with max_videos > 1 (e.g. WAN 2.6 reference-to-video accepts up to 3), and elements for models with max_elements > 0. REPAIR / RETIME (not restyling, no prompt needed — these keep the footage and fix or retime it): model "topaz/deblur/video" removes lens, motion and compression blur; "topaz/colorize/video" colorizes black-and-white footage; "topaz/interpolate/video" is SLOW MOTION and frame-rate conversion (see slowdown_factor / target_fps); "topaz/sdr-to-hdr/video" masters SDR footage to HDR (see output_format). Reach for these when the user says blurry, shaky-detail, black-and-white, slow motion, smoother frame rate, or HDR — a restyle model would repaint the video instead of repairing it. For animating a still image use generate_video_from_image instead. For text-only → video use generate_video.',
1585
1585
  {
1586
1586
  source_video: z.string().describe('URL or absolute local path to the primary source video to restyle. **Source duration must fall within `min_video_duration`-`max_video_duration` from list_models for the chosen model** — videos outside that range are rejected (or silently truncated by some upstream providers). For models that use reference_videos as their primary input (e.g. WAN 2.6 reference-to-video), pass the first reference video here and also include it in reference_videos.'),
1587
1587
  prompt: z.string().optional().describe('Text description of the desired restyle / transformation. Required by most video-to-video models; omit for prompt-less models (VEED Subtitles, Act Two, Wan Animate, Kling Motion Control).'),
@@ -1898,7 +1898,7 @@ function registerGenerateTools(server, client, options = {}) {
1898
1898
 
1899
1899
  // ── upscale ────────────────────────────────────────────
1900
1900
  scale: z.number().optional()
1901
- .describe('Upscale factor: 1, 2, 4 or 8. Used with operation="upscale". Default: 2.'),
1901
+ .describe('Upscale factor: 1, 2 or 4. Used with operation="upscale". Default: 2. There is NO 8 — every Topaz split endpoint declares upscale_factor max 4 in its OpenAPI, so an 8 is a guaranteed provider error. Output size is otherwise unbounded: 4x on a 4K source is a 16K result and is priced pro-rata, not refused.'),
1902
1902
 
1903
1903
  enhancement_model: z.string().optional()
1904
1904
  .describe('Topaz engine. With operation="upscale" on model "topaz/upscale/image" it selects the engine family: "Standard V2" / "High Fidelity V3" / "CGI" / "Text Refine" (faithful), "Wonder 3.5" (rebuilds natural detail), "Bloom 2" (reinvents detail — most expensive), "Transparent" (keeps the alpha channel). With operation="enhance" it selects the correction engine for the chosen model. Omit for the model default. Call list_models to see the engines a model offers.'),
@@ -33,7 +33,12 @@ function registerPresetTools(server, client, options = {}) {
33
33
  note: 'Filter with `type` (image | image_edit | video | music | text_to_video) to see a focused set. Pass the chosen exact id as `preset_id` on the next generation call.',
34
34
  });
35
35
 
36
- if (ui()) {
36
+ // Ship structuredContent UNCONDITIONALLY. Gating on ui() left every host that
37
+ // renders widgets without advertising MCP Apps (Kolbo Code) with text-only rows
38
+ // that carry no thumbnail field at all — and its BY_TOOL map still force-mounts
39
+ // the media grid on them, so the card rendered one broken-file glyph per cell.
40
+ // media.js and listResult() have always done it this way; these five lagged.
41
+ {
37
42
  return uiResult(UI.mediaGrid, text, {
38
43
  widget: 'media-grid',
39
44
  title: 'Presets' + (type ? ' — ' + type : ''),
@@ -78,7 +78,12 @@ function registerStockLibraryTools(server, client, options = {}) {
78
78
  : '';
79
79
  const text = `${head}\n\n${shownAssets.map(assetLine).join('\n\n')}${moreHint}\n\nUse [source:sourceId] with get_stock_asset for full variants, or import_stock_asset to copy it into the media library.`;
80
80
 
81
- if (ui()) {
81
+ // Ship structuredContent UNCONDITIONALLY. Gating on ui() left every host that
82
+ // renders widgets without advertising MCP Apps (Kolbo Code) with text-only rows
83
+ // that carry no thumbnail field at all — and its BY_TOOL map still force-mounts
84
+ // the media grid on them, so the card rendered one broken-file glyph per cell.
85
+ // media.js and listResult() have always done it this way; these five lagged.
86
+ {
82
87
  const items = assets.slice(0, 24).map((a) => {
83
88
  const mt = widgetMediaType(a.mediaType);
84
89
  return {
@@ -160,7 +165,12 @@ function registerStockLibraryTools(server, client, options = {}) {
160
165
  total: result.count,
161
166
  });
162
167
 
163
- if (ui()) {
168
+ // Ship structuredContent UNCONDITIONALLY. Gating on ui() left every host that
169
+ // renders widgets without advertising MCP Apps (Kolbo Code) with text-only rows
170
+ // that carry no thumbnail field at all — and its BY_TOOL map still force-mounts
171
+ // the media grid on them, so the card rendered one broken-file glyph per cell.
172
+ // media.js and listResult() have always done it this way; these five lagged.
173
+ {
164
174
  const collections = result.collections || [];
165
175
  const items = collections.slice(0, 24).map((c) => ({
166
176
  id: c.id,
@@ -207,7 +207,7 @@ function registerVisualDnaTools(server, client, options = {}) {
207
207
  // ─── generate_character_sheet ──────────────────────────────
208
208
  server.tool(
209
209
  'generate_character_sheet',
210
- 'Generate a reference sheet for a Visual DNA from 1+ reference image URLs — the same step the in-app Visual DNA wizard offers, for EVERY DNA type via `sheet_type`: character = multi-angle turnaround, product = angles + branding/material/construction close-ups, environment = location angles + one signature detail, style = a style board (the same look applied to six varied subjects). The sheet is the single strongest consistency booster for a DNA, and it always preserves the reference\'s original art style (2D stays 2D, photo stays photo). CHARGES CREDITS, so when the user is about to create a DNA, OFFER this first ("want me to generate a reference sheet for stronger consistency? it costs a few credits") and only run it on a yes. Returns `character_sheet_url` — pass it as `character_sheet_url` to `create_visual_dna` with the matching `dna_type`.',
210
+ 'STANDARD FIRST STEP OF THE ASSET PASS for any film/ad/scene: inventory the characters, locations and props the script needs, generate a sheet for each, create its Visual DNA from that sheet, confirm the whole set with the user, and only THEN generate video. Sheets for cinematic environments and invented characters run well on `mirage-film-2` (3cr); use `nano-banana-2` (10cr) or `gpt-image-2` (12cr) when reference fidelity or legible text matters. Generate a reference sheet for a Visual DNA from 1+ reference image URLs — the same step the in-app Visual DNA wizard offers, for EVERY DNA type via `sheet_type`: character = multi-angle turnaround, product = angles + branding/material/construction close-ups, environment = location angles + one signature detail, style = a style board (the same look applied to six varied subjects). The sheet is the single strongest consistency booster for a DNA, and it always preserves the reference\'s original art style (2D stays 2D, photo stays photo). CHARGES CREDITS, so when the user is about to create a DNA, OFFER this first ("want me to generate a reference sheet for stronger consistency? it costs a few credits") and only run it on a yes. Returns `character_sheet_url` — pass it as `character_sheet_url` to `create_visual_dna` with the matching `dna_type`.',
211
211
  {
212
212
  image_urls: z.array(z.string()).min(1).describe('Reference image URLs of the subject (for characters: front/side/varied angles work best). Use generated-image URLs or upload_media output.'),
213
213
  sheet_type: z.enum(['character', 'character_headless', 'character_bible', 'product', 'environment', 'style']).optional().describe('Sheet layout. character = front/back/face turnaround. character_headless = wardrobe/body refs with a headless front panel (use when clothing must change without fighting the face sheet). character_bible = denser production model-sheet (turnaround + faces + wardrobe + color swatches). product / environment / style = matching DNA types. Defaults to character.')
@@ -80,7 +80,12 @@ function registerVoiceTools(server, client, options = {}) {
80
80
  const range = `${start + 1}–${start + shownVoices.length}`;
81
81
  const text = `Available voices (showing ${range} of ${voices.length}, page ${pageNum}/${pageCount}):\n\n${lines.join('\n\n')}${narrowHint}\n\nUse the "voice_id" value in generate_speech calls.`;
82
82
 
83
- if (ui()) {
83
+ // Ship structuredContent UNCONDITIONALLY. Gating on ui() left every host that
84
+ // renders widgets without advertising MCP Apps (Kolbo Code) with text-only rows
85
+ // that carry no thumbnail field at all — and its BY_TOOL map still force-mounts
86
+ // the media grid on them, so the card rendered one broken-file glyph per cell.
87
+ // media.js and listResult() have always done it this way; these five lagged.
88
+ {
84
89
  return uiResult(UI.mediaGrid, text, {
85
90
  widget: 'media-grid',
86
91
  title: 'Voices',