@kolbo/mcp 1.110.3 → 1.112.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/README.md CHANGED
@@ -147,7 +147,7 @@ Without the optional skill, the config block alone already exposes every tool
147
147
  | `generate_lipsync` | Source image/video + audio → lipsynced video (Sync-3 adds active-speaker selection, emotion, model mode, temperature) |
148
148
  | `generate_creative_director` | One brief → N coordinated scenes (image or video) |
149
149
  | `generate_music` | Text (+ optional lyrics) → song. Style, title, negative tags, length, and Suno fine-controls (style weight, weirdness, audio weight, persona / singing voice) |
150
- | `generate_speech` | Text + voice → spoken audio. Full expressive/style control: Google/Gemini named voice-direction presets (`style_instructions_preset_id`: warm/dramatic/whisper/…) or free-form `style_instructions`, preset styles + emotions (DeepDub / MiniMax / Cartesia), speed, accent/`language`, and per-provider voice settings (ElevenLabs similarity/style, DeepDub accent/variance/tempo, MiniMax pitch/volume/intensity/timbre). Status returns the same fields for reuse. |
150
+ | `generate_speech` | Text + voice → spoken audio. `eleven_model` (eleven_v4 / eleven_v3 / eleven_multilingual_v2) picks which ElevenLabs model speaks an ElevenLabs voice (stock or own clone; omit = the voice's default; same price; `turbo` needs v4). Full expressive/style control: Google/Gemini named voice-direction presets (`style_instructions_preset_id`: warm/dramatic/whisper/…) or free-form `style_instructions`, preset styles + emotions (DeepDub / MiniMax / Cartesia), speed, accent/`language`, and per-provider voice settings (ElevenLabs stability/similarity/style/speaker boost/seed, `enforce_language`, `output_format` mp3/wav, `text_normalization`; DeepDub accent/variance/tempo, MiniMax pitch/volume/intensity/timbre), plus `audio_effect` (phone/radio/megaphone/small_room/hall/distant) + `audio_effect_strength` for any voice at no extra credits. Status returns the same fields for reuse. |
151
151
  | `generate_sound` | Text → sound effect. Duration, prompt influence, and per-provider controls (Stable Audio guidance, Kie loop/tempo/key, Seed-Audio voice/speed/volume/pitch + reference audio/image) |
152
152
  | `generate_3d` | Text or reference images → 3D model (GLB/FBX/OBJ/USDZ) |
153
153
  | `transcribe_audio` | Audio/video URL or file → text + SRT subtitles. Language, speaker diarization, audio-event tagging, and SRT formatting (words/line, lines/subtitle, caption stretch) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.110.3",
3
+ "version": "1.112.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": {
@@ -230,6 +230,47 @@ function modelLabel(sc) {
230
230
  }
231
231
  function voiceLabel(sc) { return sc.voice_name || sc.voice || (sc.settings || {}).voice; }
232
232
 
233
+ // generate_speech controls. What the API reports it APPLIED wins: \`speech\`
234
+ // (status / completed payload; a key there is authoritative even when null),
235
+ // then the raw status result's own fields, then what the caller submitted.
236
+ var SPEECH_KEYS = ['eleven_model', 'turbo', 'speaking_speed', 'stability', 'similarity_boost', 'enforce_language',
237
+ 'output_format', 'audio_effect', 'audio_effect_strength', 'seed'];
238
+ var SPEECH_MODELS = { eleven_v4: 'V4', eleven_v3: 'V3', eleven_multilingual_v2: 'V2' };
239
+ var SPEECH_FORMATS = { mp3_44100_128: 'MP3 128', mp3_44100_192: 'MP3 192', wav_44100: 'WAV' };
240
+ function withSpeech(settings, a) {
241
+ SPEECH_KEYS.forEach(function (k) { if (a && a[k] != null) settings[k] = a[k]; });
242
+ return settings;
243
+ }
244
+ function speechVal(sc, k) {
245
+ if (sc.speech && Object.prototype.hasOwnProperty.call(sc.speech, k)) return sc.speech[k];
246
+ var vs = sc.voice_settings || {};
247
+ if (vs[k] != null) return vs[k];
248
+ if (sc[k] != null) return sc[k];
249
+ return (sc.settings || {})[k];
250
+ }
251
+ function num(v) { return String(Math.round(Number(v) * 100) / 100); }
252
+ function speechChipsHTML(sc) {
253
+ if (sc.tool !== 'generate_speech' && !sc.speech) return '';
254
+ var h = '', v;
255
+ v = speechVal(sc, 'eleven_model'); if (v) h += chip('Model ' + esc(SPEECH_MODELS[v] || v));
256
+ if (speechVal(sc, 'turbo') === true) h += chip('Turbo');
257
+ v = speechVal(sc, 'speaking_speed'); if (v != null) h += chip('Speed ' + esc(num(v)) + '×');
258
+ v = speechVal(sc, 'stability'); if (v != null) h += chip('Stability ' + esc(num(v)));
259
+ v = speechVal(sc, 'similarity_boost'); if (v != null) h += chip('Similarity ' + esc(num(v)));
260
+ v = speechVal(sc, 'enforce_language');
261
+ h += chip('Language ' + (v ? esc(String(v).toUpperCase()) : 'Auto'));
262
+ v = speechVal(sc, 'output_format'); if (v) h += chip(esc(SPEECH_FORMATS[v] || v));
263
+ v = speechVal(sc, 'audio_effect');
264
+ if (v && v !== 'none') {
265
+ var strength = speechVal(sc, 'audio_effect_strength');
266
+ var name = String(v).replace(/_/g, ' ');
267
+ h += chip(esc(name.charAt(0).toUpperCase() + name.slice(1)) +
268
+ (strength != null && Number(strength) < 1 ? ' ' + Math.round(Number(strength) * 100) + '%' : ''));
269
+ }
270
+ v = speechVal(sc, 'seed'); if (v != null) h += chip('Seed ' + esc(String(v)));
271
+ return h;
272
+ }
273
+
233
274
  // @VisualDNA / #Moodboard mentions are the tag syntax the server resolves into
234
275
  // real reference assets — rendering them as flat prose hid the single most
235
276
  // consequential part of the prompt. Escape FIRST, then wrap: the pattern only
@@ -340,6 +381,7 @@ function renderChips(sc) {
340
381
  : ICONS.mic;
341
382
  h += chip(face + ' ' + esc(voice));
342
383
  }
384
+ h += speechChipsHTML(sc);
343
385
  if (s.mode) h += chip(esc(editLabel(s.mode)));
344
386
  if (Array.isArray(s.details)) s.details.forEach(function (d) { if (d) h += chip(esc(String(d))); });
345
387
  if (many && !kind) h += chip(sc.count + ' items');
@@ -1358,7 +1400,7 @@ function preRefSc(toolName, a) {
1358
1400
  reference_videos: vid,
1359
1401
  reference_audio: aud,
1360
1402
  local_references: local,
1361
- settings: {
1403
+ settings: withSpeech({
1362
1404
  duration: a.duration,
1363
1405
  resolution: a.draft === true ? '480p-draft' : a.draft === false ? String(a.resolution || '').replace(/-draft$/i, '') : a.resolution,
1364
1406
  aspect_ratio: a.aspect_ratio,
@@ -1369,7 +1411,7 @@ function preRefSc(toolName, a) {
1369
1411
  moodboard_ids: a.moodboard_ids,
1370
1412
  moodboard_id: a.moodboard_id,
1371
1413
  preset_id: a.preset_id
1372
- }
1414
+ }, a)
1373
1415
  };
1374
1416
  }
1375
1417
 
@@ -1427,7 +1469,7 @@ function liveFromTimedOut(sc) {
1427
1469
  kind: kindFromTool(originTool, sc),
1428
1470
  prompt: originArgs.prompt || originArgs.text || '',
1429
1471
  model: sc.model || originArgs.model,
1430
- settings: {
1472
+ settings: withSpeech({
1431
1473
  duration: originArgs.duration,
1432
1474
  is_draft: originArgs.draft,
1433
1475
  resolution: originArgs.draft === true ? '480p-draft' : originArgs.draft === false ? String(originArgs.resolution || '').replace(/-draft$/i, '') : originArgs.resolution,
@@ -1436,7 +1478,7 @@ function liveFromTimedOut(sc) {
1436
1478
  mode: originArgs.operation,
1437
1479
  visual_dna_ids: originArgs.visual_dna_ids,
1438
1480
  moodboard_id: originArgs.moodboard_id
1439
- },
1481
+ }, originArgs),
1440
1482
  count: ids ? ids.length : (originArgs.num_images || 1),
1441
1483
  generation_id: sc.generation_id,
1442
1484
  poll_tool: 'get_generation_status',
@@ -1457,14 +1499,14 @@ function completedFromPlain(sc) {
1457
1499
  kind: kindFromTool(originTool, sc),
1458
1500
  prompt: originArgs.prompt || originArgs.text || sc.prompt_used || '',
1459
1501
  model: sc.model || originArgs.model,
1460
- settings: {
1502
+ settings: withSpeech({
1461
1503
  duration: sc.duration || originArgs.duration,
1462
1504
  is_draft: originArgs.draft,
1463
1505
  resolution: originArgs.draft === true ? '480p-draft' : originArgs.draft === false ? String(originArgs.resolution || '').replace(/-draft$/i, '') : originArgs.resolution,
1464
1506
  aspect_ratio: originArgs.aspect_ratio,
1465
1507
  quality: originArgs.quality,
1466
1508
  mode: originArgs.operation
1467
- },
1509
+ }, originArgs),
1468
1510
  urls: sc.urls || [],
1469
1511
  session_id: sc.session_id || originArgs.session_id,
1470
1512
  project_id: sc.project_id || originArgs.project_id,
@@ -1066,6 +1066,10 @@ async function uiCompleted(p, textPayload, extraContent) {
1066
1066
  // voice's face. Exactly the "never show a raw id on a card" rule, broken on
1067
1067
  // the one path the user actually ends up looking at.
1068
1068
  ...(p.voice ? { voice_name: p.voice.name, voice_thumbnail: p.voice.thumbnail } : {}),
1069
+ // Speech settings the API reports it applied (generate.js speechFromResult,
1070
+ // incl. eleven_model); passed through whole.
1071
+ // the card prefers them over the submitted `settings`.
1072
+ ...(p.speech ? { speech: p.speech } : {}),
1069
1073
  // The RAW generation state, when the caller has one. `phase` above is
1070
1074
  // hardcoded 'completed' — it means "this tool call finished", not "the
1071
1075
  // generation finished" — so a status check on a still-running job looked
@@ -151,6 +151,24 @@ function mediaKind(url) {
151
151
  return 'image';
152
152
  }
153
153
 
154
+ // The speech settings the API says it APPLIED (status result, kolbo-api
155
+ // sdk/controller.js extractResult). Keys are always present (null = not
156
+ // applied) so the card prefers these over what the caller asked for; only a
157
+ // speech result carries `enforce_language`, so every other result returns
158
+ // undefined and its card is untouched.
159
+ function speechFromResult(r) {
160
+ if (!r || typeof r !== 'object' || !Object.prototype.hasOwnProperty.call(r, 'enforce_language')) return undefined;
161
+ const vs = r.voice_settings || {};
162
+ const out = {
163
+ stability: vs.stability, similarity_boost: vs.similarity_boost, style: vs.style, use_speaker_boost: vs.use_speaker_boost,
164
+ speaking_speed: r.speaking_speed, enforce_language: r.enforce_language, output_format: r.output_format,
165
+ text_normalization: r.text_normalization, audio_effect: r.audio_effect,
166
+ audio_effect_strength: r.audio_effect_strength, seed: r.seed, eleven_model: r.eleven_model,
167
+ };
168
+ for (const k of Object.keys(out)) if (out[k] === undefined) out[k] = null;
169
+ return out;
170
+ }
171
+
154
172
  function preferOwned(urls) {
155
173
  const list = (urls || []).filter((item) => typeof item === 'string' && item);
156
174
  const ours = list.filter(ownedUrl);
@@ -1082,8 +1100,9 @@ function registerGenerateTools(server, client, options = {}) {
1082
1100
  {
1083
1101
  text: z.string().describe('The text to convert to speech'),
1084
1102
  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"'),
1085
- model: z.string().optional().describe('Model identifier. Use list_models type="text_to_speech" to see options. Default: eleven_v4 (ElevenLabs V4). ElevenLabs V4/V3 voices follow inline audio tags in the text: delivery ([whispers], [shouting], [sarcastic]), reactions ([laughs], [sighs], [clears throat]), pauses ([pause], [long pause]) and, on V4, free-form directions like [Low, steady voice, restrained urgency]. Tags are always English, even in Hebrew text. SSML such as <break> is NOT supported.'),
1103
+ model: z.string().optional().describe('Model identifier. Use list_models type="text_to_speech" to see options. Default: eleven_v4 (ElevenLabs V4). To choose which ElevenLabs model speaks a given ElevenLabs voice (stock or a cloned voice), use `eleven_model` instead. ElevenLabs V4/V3 voices follow inline audio tags in the text: delivery ([whispers], [shouting], [sarcastic]), reactions ([laughs], [sighs], [clears throat]), pauses ([pause], [long pause]) and, on V4, free-form directions like [Low, steady voice, restrained urgency]. Tags are always English, even in Hebrew text. SSML such as <break> is NOT supported.'),
1086
1104
  turbo: z.boolean().optional().describe('ElevenLabs V4 voices ONLY. Turbo: faster generation at half the credits, slightly lower quality. Rejected (TURBO_UNSUPPORTED) for any voice not on ElevenLabs V4. Default false.'),
1105
+ eleven_model: z.enum(['eleven_v4', 'eleven_v3', 'eleven_multilingual_v2']).optional().describe("ElevenLabs voices ONLY (stock voices and the user's own cloned/character ElevenLabs voices). Which ElevenLabs model speaks: eleven_v4 (default for new voices, most expressive, audio tags, Stability/Similarity/Turbo), eleven_v3 (audio tags), eleven_multilingual_v2 (no audio tags; supports `style`). Omit = the voice's own default. Same price for all. Rejected with 400 ELEVEN_MODEL_UNSUPPORTED for non-ElevenLabs voices. `turbo` requires eleven_v4."),
1087
1106
  language: z.string().optional().describe('Language / accent code (e.g., "en-US", "he-IL", "es-ES"). For Google/Gemini voices this is the Accent control (does not translate the text). Default: "en-US"'),
1088
1107
  // ── Expressive style / emotion (provider-specific) ──
1089
1108
  // Google/Gemini: prefer a named preset id from the Kolbo UI picker; free-form
@@ -1099,14 +1118,20 @@ function registerGenerateTools(server, client, options = {}) {
1099
1118
  emotion: z.string().optional().describe('MiniMax / Cartesia voices. Emotion: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper. Prefer this OR selected_style (both map to the same delivery control).'),
1100
1119
  speaking_speed: z.number().optional().describe('Speech speed 0.5 (slow) – 2.0 (fast); Cartesia clamped 0.6–1.5. Default 1.0. Applies to ElevenLabs / OpenAI / Google / MiniMax / Cartesia / DeepDub (as tempo when tempo omitted).'),
1101
1120
  // ── ElevenLabs voice settings ──
1102
- similarity_boost: z.number().optional().describe('ElevenLabs voice similarity, 0–1. Default 0.75. Higher hews closer to the original voice.'),
1103
- style: z.number().optional().describe('ElevenLabs style exaggeration, 0–1. Default 0.5. Higher = more expressive/dramatic.'),
1104
- use_speaker_boost: z.boolean().optional().describe('ElevenLabs speaker boost. Default true.'),
1121
+ stability: z.number().min(0).max(1).optional().describe('ElevenLabs Stability, 0–1. Default 0.5. Lower = more expressive and varied between takes (Creative); higher = steadier, more consistent delivery (Robust). Supported on V4.'),
1122
+ similarity_boost: z.number().optional().describe('ElevenLabs voice similarity, 0–1. Default 0.75. Higher hews closer to the original voice. Also applies to the user\'s own cloned/character ElevenLabs voices.'),
1123
+ style: z.number().optional().describe('ElevenLabs style exaggeration, 0–1. Default 0. Higher = more expressive/dramatic. V3/V2 voices only — ElevenLabs V4 ignores it (use stability or inline audio tags instead).'),
1124
+ use_speaker_boost: z.boolean().optional().describe('ElevenLabs speaker boost (presence/clarity). Default true.'),
1125
+ enforce_language: z.string().optional().describe('ElevenLabs voices ONLY. Force the spoken language with an ISO 639-1 code ("he", "en", "fr"; a locale like "he-IL" is also accepted). Omit (recommended) and ElevenLabs auto-detects the language from the text. Different from `language`, which stays the accent/locale control.'),
1126
+ output_format: z.enum(['mp3_44100_128', 'mp3_44100_192', 'wav_44100']).optional().describe('ElevenLabs voices ONLY. Audio file format: "mp3_44100_128" (default), "mp3_44100_192" (higher-bitrate MP3) or "wav_44100" (uncompressed; the result is a .wav file). ElevenLabs\' native render is delivered as is. Any other provider rejects it (OUTPUT_FORMAT_UNSUPPORTED).'),
1127
+ text_normalization: z.enum(['auto', 'on', 'off']).optional().describe('ElevenLabs voices. Spell out numbers, dates and abbreviations before speaking: "auto" (default), "on" (always), "off" (read as written).'),
1128
+ audio_effect: z.enum(['none', 'phone', 'radio', 'megaphone', 'small_room', 'hall', 'distant']).optional().describe('Kolbo post-processing effect applied to the finished voice: phone, radio, megaphone, small_room, hall, distant, or "none" (default). Works for ANY voice/provider, no extra credits.'),
1129
+ audio_effect_strength: z.number().min(0).max(1).optional().describe('Mix of audio_effect with the dry voice, 0–1. Default 1 (full effect). Ignored without audio_effect.'),
1105
1130
  // ── DeepDub controls ──
1106
1131
  variance: z.number().optional().describe('DeepDub voice variance, 0–1. Default 0.2. Higher = more takes/variation.'),
1107
1132
  tempo: z.number().optional().describe('DeepDub tempo multiplier, 0–2. Default 1.0.'),
1108
1133
  promptBoost: z.boolean().optional().describe('DeepDub prompt-fidelity boost. Default true.'),
1109
- seed: z.number().optional().describe('Reproducibility seed (DeepDub / Zonos). Same seed + inputs → same output.'),
1134
+ seed: z.number().optional().describe('Reproducibility seed (ElevenLabs / DeepDub / Zonos). Same seed + inputs → same output. ElevenLabs: integer 0–4294967295, best-effort determinism (not guaranteed).'),
1110
1135
  accentControl: z.object({
1111
1136
  accentBaseLocale: z.string().describe('Base accent locale, e.g. "en-US".'),
1112
1137
  accentLocale: z.string().describe('Target accent locale, e.g. "en-GB".'),
@@ -1121,7 +1146,7 @@ function registerGenerateTools(server, client, options = {}) {
1121
1146
  project_id: projectIdField,
1122
1147
  session_id: sessionIdField
1123
1148
  },
1124
- async ({ text, voice, model, turbo, language, style_instructions_preset_id, style_instructions, style_instructions_label, selected_style, emotion, speaking_speed, similarity_boost, style, use_speaker_boost, variance, tempo, promptBoost, seed, accentControl, voiceTitle, minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre, project_id, session_id }) => {
1149
+ async ({ text, voice, model, eleven_model, turbo, language, style_instructions_preset_id, style_instructions, style_instructions_label, selected_style, emotion, speaking_speed, stability, similarity_boost, style, use_speaker_boost, enforce_language, output_format, text_normalization, audio_effect, audio_effect_strength, variance, tempo, promptBoost, seed, accentControl, voiceTitle, minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre, project_id, session_id }) => {
1125
1150
  model = await canonicalModelId(client, model, 'text_to_speech'); // lenient id resolution ("z-image" → "z-image/turbo")
1126
1151
  // Resolve the requested voice against the REAL catalog (cached) so the card
1127
1152
  // can show its display name + portrait instead of a raw id, and so an id
@@ -1133,11 +1158,14 @@ function registerGenerateTools(server, client, options = {}) {
1133
1158
  const unknownVoice = voice && !voiceRecord && !/^custom_/i.test(voice)
1134
1159
  ? `Voice "${voice}" is not in the Kolbo voice catalog. Call list_voices and pass a voice_id it returns — an unrecognised id is NOT rejected, it is silently mapped to a different voice, so the audio will not be the voice you named.`
1135
1160
  : null;
1161
+ // ElevenLabs / effect controls carried on the card (undefined keys drop out of the JSON).
1162
+ const ttsControls = { eleven_model, turbo, stability, similarity_boost, style, use_speaker_boost, enforce_language, output_format, text_normalization, audio_effect, audio_effect_strength, seed };
1136
1163
  const gen = await client.post('/v1/generate/speech', {
1137
- text, voice, model, turbo, language,
1164
+ text, voice, model, eleven_model, turbo, language,
1138
1165
  style_instructions_preset_id, style_instructions, style_instructions_label,
1139
1166
  selected_style, emotion, speaking_speed,
1140
- similarity_boost, style, use_speaker_boost,
1167
+ stability, similarity_boost, style, use_speaker_boost,
1168
+ enforce_language, output_format, text_normalization, audio_effect, audio_effect_strength,
1141
1169
  variance, tempo, promptBoost, seed, accentControl, voiceTitle,
1142
1170
  minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre,
1143
1171
  project_id, session_id
@@ -1151,6 +1179,7 @@ function registerGenerateTools(server, client, options = {}) {
1151
1179
  style: selected_style || emotion || style_instructions_preset_id || style_instructions,
1152
1180
  speaking_speed,
1153
1181
  language,
1182
+ ...ttsControls,
1154
1183
  },
1155
1184
  warning: unknownVoice
1156
1185
  });
@@ -1178,7 +1207,9 @@ function registerGenerateTools(server, client, options = {}) {
1178
1207
  style: selected_style || emotion || style_instructions_preset_id || style_instructions,
1179
1208
  speaking_speed,
1180
1209
  language,
1210
+ ...ttsControls,
1181
1211
  },
1212
+ speech: speechFromResult(result.result),
1182
1213
  urls: result.result.urls,
1183
1214
  playback_urls: result.result.playback_urls,
1184
1215
  duration: result.result.duration,
@@ -1420,6 +1451,7 @@ function registerGenerateTools(server, client, options = {}) {
1420
1451
  txt_url: res.txt_url || undefined,
1421
1452
  audio_url: res.audio_url || undefined,
1422
1453
  prompt: res.prompt_used || res.prompt || undefined,
1454
+ speech: speechFromResult(res),
1423
1455
  credits_used: creditFields(single).credits_used,
1424
1456
  items: [{
1425
1457
  id: single.generation_id,