@kolbo/mcp 1.74.0 → 1.75.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.74.0",
3
+ "version": "1.75.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": {
@@ -15,6 +15,8 @@ const { widgetPage } = require('../html');
15
15
  * title: 'Your Projects',
16
16
  * items: [{
17
17
  * id, title, subtitle, // main two lines
18
+ * thumbnail, // optional — real image; falls back to the letter monogram on
19
+ * // missing/failed load, same idiom as catalog.js model icons
18
20
  * badge, // small pill, e.g. role/status ("owner", "default", "shared")
19
21
  * meta, // small trailing text, e.g. a date or count
20
22
  * open_url, // optional — renders an "Open" button (openLink)
@@ -71,8 +73,11 @@ function apply(result) {
71
73
 
72
74
  function itemHTML(item, i) {
73
75
  var clickable = !!item.use_hint;
76
+ var avatar = item.thumbnail
77
+ ? '<img class="k-audio-art" src="' + esc(item.thumbnail) + '" alt="" loading="lazy" onerror="this.outerHTML=monogram(\\'' + esc(item.title || '?').replace(/'/g, '') + '\\')">'
78
+ : monogram(item.title || '?');
74
79
  return '<div class="k-audio-row" data-i="' + i + '"' + (clickable ? ' style="cursor:pointer"' : '') + '>' +
75
- monogram(item.title || '?') +
80
+ avatar +
76
81
  '<div class="k-audio-meta"><div class="k-audio-title">' + esc(item.title || '') + '</div>' +
77
82
  (item.subtitle ? '<div class="k-audio-sub">' + esc(item.subtitle) + '</div>' : '') + '</div>' +
78
83
  (item.badge ? '<span class="k-chip" style="flex:none">' + esc(item.badge) + '</span>' : '') +
@@ -32,6 +32,7 @@ function registerProjectTools(server, client) {
32
32
  role: p.role,
33
33
  is_default: !!p.is_default,
34
34
  is_archived: !!p.is_archived,
35
+ thumbnail_url: p.thumbnail_url || null,
35
36
  open_url: buildProjectUrl(p.id, { is_default: !!p.is_default })
36
37
  }));
37
38
  const text = JSON.stringify({
@@ -48,6 +49,7 @@ function registerProjectTools(server, client) {
48
49
  id: p.id,
49
50
  title: p.name,
50
51
  subtitle: p.role + (p.is_default ? ' · default' : '') + (p.is_archived ? ' · archived' : ''),
52
+ thumbnail: p.thumbnail_url,
51
53
  open_url: p.open_url,
52
54
  use_hint: 'Use my "{TITLE}" project (project_id: {ID}) for what I do next.'
53
55
  })),
@@ -30,10 +30,12 @@ function registerVisualDnaTools(server, client, options = {}) {
30
30
  prompt_helper: z.string().optional().describe('Optional description/notes to guide DNA extraction'),
31
31
  images: z.array(z.string()).optional().describe('Array of image sources (URLs or absolute local paths). Max 4.'),
32
32
  video: z.string().optional().describe('Optional video source (URL or absolute local path)'),
33
- audio: z.string().optional().describe('Optional audio source (URL or absolute local path)'),
33
+ audio: z.string().optional().describe('Optional audio source (URL or absolute local path) — the character\'s voice, 5-30s of clean speech. Stored on the DNA and used two ways: (1) as REFERENCE AUDIO in video generation — attaching this DNA to an image-to-video generation on a model with audio slots (Seedance 2.x, Wan 3.0) auto-attaches the clip and tells the model it is that character\'s voice; (2) as the source for a real speaking voice, but ONLY when you ask for one — see `voice_source`.'),
34
+ voice_source: z.enum(['none', 'clone', 'assign', 'design']).optional().describe('What to do about a SPEAKING voice. **Pass "none" when the audio is just a reference clip** (the usual case for video work) — the clip is stored and usable as video reference audio, and nothing else happens. "clone" mints an ElevenLabs voice from the uploaded audio, which consumes a voice slot and may EVICT another of the user\'s voices to free one; it also makes the DNA addressable as `dna_<id>` in text-to-speech. "assign" points at an existing voice (pass `assigned_voice_id`). "design" generates a voice from the character\'s look. ⚠️ Omitting this while passing `audio` keeps the legacy behaviour and CLONES — pass "none" explicitly unless the user asked for a voice.'),
35
+ assigned_voice_id: z.string().optional().describe('Voice to attach when voice_source="assign" — a `custom_<id>` from the user\'s clones or a voice_id from `list_voices`.'),
34
36
  character_sheet_url: z.string().optional().describe('URL of a reference sheet (from `generate_character_sheet`, any sheet_type) to set as the DNA\'s primary reference. Works for ALL DNA types — character turnaround, product detail sheet, location sheet, or style board — and is the single biggest consistency booster. Omit only when the user declines.')
35
37
  },
36
- async ({ name, dna_type, prompt_helper, images, video, audio, character_sheet_url }) => {
38
+ async ({ name, dna_type, prompt_helper, images, video, audio, voice_source, assigned_voice_id, character_sheet_url }) => {
37
39
  if (!name || !name.trim()) {
38
40
  throw new Error('name is required');
39
41
  }
@@ -58,6 +60,10 @@ function registerVisualDnaTools(server, client, options = {}) {
58
60
  if (dna_type) form.append('dnaType', dna_type);
59
61
  if (prompt_helper) form.append('promptHelper', prompt_helper);
60
62
  if (character_sheet_url) form.append('characterSheetUrl', character_sheet_url);
63
+ // Omitted stays omitted: the server infers 'clone' from a present audio clip, which is the
64
+ // long-standing behaviour older installs depend on. Only an explicit choice is forwarded.
65
+ if (voice_source) form.append('voiceSource', voice_source);
66
+ if (assigned_voice_id) form.append('assignedVoiceId', assigned_voice_id);
61
67
 
62
68
  for (const f of imageFiles) {
63
69
  form.append('images', f.buffer, { filename: f.filename, contentType: f.contentType });
@@ -125,8 +131,11 @@ function registerVisualDnaTools(server, client, options = {}) {
125
131
  const total = result.total != null ? result.total : (result.count || dnas.length);
126
132
  // Full profiles measured 74,310 chars — the embedded analysis/description
127
133
  // blobs are large and the model only needs enough to pick an id.
134
+ // `has_voice_reference` rides along because it changes what a DNA DOES in a generation:
135
+ // such a DNA brings its own voice as reference audio on models with audio slots. Without
136
+ // it the model has to fetch each DNA in full just to find out.
128
137
  const text = compactList(dnas, {
129
- fields: ['id', 'name', 'type', 'folder_id', 'tags', 'thumbnail'],
138
+ fields: ['id', 'name', 'type', 'folder_id', 'tags', 'thumbnail', 'has_voice_reference'],
130
139
  cap: 60,
131
140
  total,
132
141
  note: 'Narrow with `search`, `tags`, or `collection`, or pass `page`/`limit` for the rest; get_visual_dna returns one in full.',