@kolbo/mcp 1.112.2 → 1.112.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.112.2",
3
+ "version": "1.112.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": {
@@ -1,6 +1,6 @@
1
1
  # AUTO-GENERATED — do not edit
2
2
 
3
- This tree is mirrored from kolbo-code@66aa15c, the single source of truth.
3
+ This tree is mirrored from kolbo-code@49cd478, the single source of truth.
4
4
  Canonical source: packages/opencode/skills/kolbo/
5
5
  Distribution: .github/workflows/sync-skill-to-plugin.yml
6
6
 
package/skill/SKILL.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- version: 0.9.22
2
+ version: 0.9.23
3
3
  name: kolbo
4
4
  description: |
5
5
  Generate, edit, analyze, and direct creative media through Kolbo AI: images,
@@ -155,7 +155,7 @@ Font tools (when exposed by the installed MCP): `list_fonts`, `get_font`, `uploa
155
155
  | `list_models` / `list_voices` / `check_credits` / `show_plans` / `get_generation_status` / `cancel_generation` / `get_session_usage` | Discovery + status. `list_models` with no args returns the recommended shortlist out of ~428 — pass `type` for a full category with per-model caps. `cancel_generation` stops an in-flight job and refunds what it can: use it when the user changes their mind mid-generation instead of letting it run. `show_plans` renders the balance + upgrade card for pricing/plan/upgrade questions. |
156
156
  | `download_media_from_url` / `get_download_status` / `cancel_download` | Download a public social/video page URL to Kolbo storage. Async job; use download status, not generation status. See `workflows/media-library.md`. |
157
157
  | `upload_media` / `create_upload_ticket` / `list_media` / `get_media` / `get_media_stats` / `favorite_media` / `unfavorite_media` / `delete_media` / `restore_media` / `permanently_delete_media` / `move_media` / `bulk_*_media` / `*_media_folder` | Media library — see `workflows/media-library.md`. Getting a LOCAL file in depends on where the server runs: `upload_media` with a path only works on a local (stdio) install; over a remote connector use `create_upload_ticket` and POST the file yourself. |
158
- | `create_visual_dna` / `update_visual_dna` / `generate_character_sheet` / `list_visual_dnas` / `get_visual_dna` / `delete_visual_dna` / `*_visual_dna_folder` (5 folder tools) | Visual DNA (+ character sheet, character folders) — see `workflows/visual-dna.md`. Edit with `update_visual_dna`; never delete+recreate. |
158
+ | `create_visual_dna` / `update_visual_dna` / `generate_character_sheet` / `list_visual_dnas` / `get_visual_dna` / `delete_visual_dna` / `*_visual_dna_folder` (5 folder tools) | Visual DNA (+ character sheet and character folders) — see `workflows/visual-dna.md`. Edit with `update_visual_dna`; never delete+recreate. Account transfers use the Kolbo UI until the MCP tools ship. |
159
159
  | `list_moodboards` / `get_moodboard` / `list_presets` / `list_cinematic_presets` | Style overlays + presets. `list_presets` spans FOUR distinct catalogs (`image`, `image_edit`, `video`, `music`; `text_to_video` is an alias for `video`, `shorts` is empty) — the `video` one holds 200+ Seedance shot recipes. `list_cinematic_presets` is a separate tool feeding the `cinematic` arg, never `preset_id`. Full doctrine + intent→catalog map: `references/workflows/presets.md`. Never omit `preset_id` after claiming a preset was used. |
160
160
  | `list_color_palettes` / `analyze_color_palette` / `create_color_palette` / `update_color_palette` / `delete_color_palette` / `activate_color_palette` / `deactivate_color_palette` | **Color DNA — sticky + account-wide; at most one palette active at a time**, and while active it strict-grades **every** image and video generation automatically. Per-generation opt-out: `skip_color_palette: true`. Details: `workflows/color-dna.md`. |
161
161
  | `list_agents` / `create_agent` / `update_agent` / `delete_agent` (same tools as `list_skills` / `create_skill` / `update_skill` / `delete_skill`) | Custom chat agents — reusable named personas for `chat_send_message`. The agent's `description` IS the system instruction. Resolve a name the user mentions ("use my SEO agent") to an id with `list_agents` (paged: `page` / `limit`, follow `pagination.has_more`), then pass `agent_id`. Global/preset agents are read-only; only the user's own can be updated or deleted. |
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.22
1
+ 0.9.23
@@ -1,5 +1,11 @@
1
1
  # Presets — Kolbo's stored instruction blocks
2
2
 
3
+ Generation widgets show the selected template name and thumbnail in a dedicated
4
+ Template section, plus every supplied reference in References. Local image sources
5
+ preview through their uploaded CDN URLs. Both sections persist through completion,
6
+ including image batches and standalone status checks. Keep passing the exact
7
+ `preset_id`; the thumbnail is a preview, not an extra conditioning image.
8
+
3
9
  A preset is a long, hand-tuned instruction block stored in Kolbo's catalog. Passing
4
10
  `preset_id` prepends it to the user's prompt at generation time. **The craft lives on
5
11
  the preset** — a three-panel character sheet, a Seedance shot-sequence structure, a
@@ -254,6 +254,18 @@ Tools: `list_visual_dna_folders`, `create_visual_dna_folder` (`name`, optional h
254
254
  - **Creating many characters for one production?** Create the folder FIRST, then `move_visual_dna_to_folder` each DNA right after `create_visual_dna` — don't leave a big cast unsorted at root.
255
255
  - To list a folder's contents: `list_visual_dnas` and filter by each profile's `folder_id` (there is no server-side folder filter).
256
256
 
257
+ ## Transfer through Kolbo UI — send DNAs to another account
258
+
259
+ Use the authenticated Kolbo UI for account transfers. Dedicated MCP transfer tools
260
+ are not in the current published tool surface; do not invent or call them.
261
+
262
+ - Recipient must already have a Kolbo account; nothing changes until they accept (7-day expiry).
263
+ - On accept the recipient gets their OWN copy — **new ids**, returned in `copies`. Use those ids and names for the recipient's generations, never the sender's ids. A name clash becomes "Name (1)".
264
+ - `keep_sender_copy: false` is a move: the sender's originals go to trash on accept. Default keeps them.
265
+ - Only the sender's personal, ready DNAs — not global, organization, or still-generating ones. Custom cloned/designed voices stay owned by the sender.
266
+ - GATE: confirm recipient email, the DNA list and keep/move with the user before sending through the UI; confirm before accepting.
267
+ - Whole project instead (cast comes along as copies)? Use `transfer_project`.
268
+
257
269
  ## Character sheet — default for production assets (not a catalog preset)
258
270
 
259
271
  Custom instructions live on the **image preset**. Resolve it silently, then generate:
package/src/apps/theme.js CHANGED
@@ -152,7 +152,16 @@ body {
152
152
  align-items: center; justify-content: center; font-family: inherit;
153
153
  }
154
154
  .k-chip img.k-voice-thumb { width: 18px; height: 18px; border-radius: 999px; margin-left: -3px; }
155
- .k-ref-thumb { width: 26px; height: 26px; border-radius: 6px; object-fit: cover; border: 1px solid var(--border-strong); }
155
+ .k-ref-thumb { width: 26px; height: 26px; border-radius: 6px; object-fit: cover; border: 1px solid var(--border-strong); }
156
+ .k-input-section { flex-basis: 100%; min-width: 0; margin-top: 6px; }
157
+ .k-input-label { display: block; color: var(--muted); font-size: 11px; margin-bottom: 6px; }
158
+ .k-input-media { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
159
+ .k-input-media .k-ref-thumb { width: 40px; height: 40px; object-fit: contain; background: var(--panel); }
160
+ .k-ref-audio { display: flex; flex-direction: column; gap: 6px; max-width: 100%; font-size: 11px; }
161
+ .k-ref-audio audio { width: 240px; max-width: 100%; height: 32px; }
162
+ .k-template { display: flex; align-items: center; gap: 12px; font-size: 12px; }
163
+ .k-template-thumb, .k-template-placeholder { width: 104px; height: 80px; border-radius: 8px; object-fit: contain; background: var(--panel); border: 1px solid var(--border-strong); }
164
+ .k-template-placeholder { display: flex; align-items: center; justify-content: center; color: var(--muted); }
156
165
  .k-peek-hit { cursor: zoom-in; }
157
166
  .k-peek {
158
167
  position: absolute; inset: 0; z-index: 20;
@@ -385,7 +385,7 @@ function renderChips(sc) {
385
385
  if (s.mode) h += chip(esc(editLabel(s.mode)));
386
386
  if (Array.isArray(s.details)) s.details.forEach(function (d) { if (d) h += chip(esc(String(d))); });
387
387
  if (many && !kind) h += chip(sc.count + ' items');
388
- h += referenceHTML(sc);
388
+ h += templateHTML(sc) + referenceHTML(sc);
389
389
  el('chips').innerHTML = h;
390
390
  }
391
391
 
@@ -429,7 +429,7 @@ function referenceHTML(sc) {
429
429
  h += '<video class="k-ref-thumb k-peek-hit" src="' + url + '#t=0.1" muted playsinline preload="metadata" title="'
430
430
  + title + '"' + peek + ' onerror="this.style.display=\\'none\\'"></video>';
431
431
  } else if (refs[i].kind === 'audio') {
432
- h += '<span class="k-chip" title="' + title + '">' + ICONS.sound + ' audio ref</span>';
432
+ h += '<span class="k-ref-audio" title="' + title + '">' + ICONS.sound + ' audio ref<audio controls preload="none" src="' + url + '"></audio></span>';
433
433
  } else {
434
434
  h += '<img class="k-ref-thumb k-peek-hit" src="' + url + '" alt="" loading="lazy" title="' + title + '"'
435
435
  + peek + ' onerror="this.style.display=\\'none\\'">';
@@ -438,12 +438,23 @@ function referenceHTML(sc) {
438
438
  // Local files the caller passed by path: the iframe cannot load them and they
439
439
  // are only uploaded during the run, so name them instead of showing nothing.
440
440
  // The finished card carries the uploaded thumbnails and drops these.
441
- if (sc.phase !== 'completed' && Array.isArray(sc.local_references)) {
442
- sc.local_references.slice(0, 6).forEach(function (name) {
441
+ if (sc.phase !== 'completed' && Array.isArray(sc.local_references) && !refs.length) {
442
+ sc.local_references.forEach(function (name) {
443
443
  h += '<span class="k-chip" title="' + esc(name) + '">' + ICONS.image + ' ' + esc(String(name).slice(0, 24)) + '</span>';
444
444
  });
445
445
  }
446
- return h;
446
+ return h ? '<div class="k-input-section"><span class="k-input-label">References</span><div class="k-input-media">' + h + '</div></div>' : '';
447
+ }
448
+ function templateHTML(sc) {
449
+ var s = sc.settings || {};
450
+ if (!s.preset_id && !s.preset_name && !s.preset_thumbnail) return '';
451
+ var name = s.preset_name || 'Selected template';
452
+ var url = s.preset_thumbnail;
453
+ var media = url ? '<img class="k-template-thumb k-peek-hit" src="' + esc(url) + '" alt="' + esc(name)
454
+ + '" loading="lazy" data-peek="' + esc(url) + '" data-peek-kind="image" data-peek-cap="' + esc(name) + '">'
455
+ : '<span class="k-template-placeholder">' + ICONS.image + '</span>';
456
+ return '<div class="k-input-section"><span class="k-input-label">Template</span><div class="k-template">'
457
+ + media + '<span>' + esc(name) + '</span></div></div>';
447
458
  }
448
459
  // Which characters/looks are locked into this generation — by face and name,
449
460
  // resolved from visual_dna_ids server-side. "1 Visual DNA" told the user nothing
@@ -830,6 +841,7 @@ function poll(sc) {
830
841
 
831
842
  var done = Object.assign({}, sc, r, {
832
843
  phase: 'completed',
844
+ settings: Object.assign({}, sc.settings || {}, r.settings || {}),
833
845
  urls: r.urls || st.urls || [],
834
846
  credits_used: st.credits_used != null ? st.credits_used : sc.credits_used,
835
847
  // Status structuredContent used to set open_url:undefined and wipe the
@@ -1462,7 +1474,7 @@ function kindFromTool(tool, sc) {
1462
1474
  function liveFromTimedOut(sc) {
1463
1475
  if (!sc || !sc.generation_id || isTerminal(sc.state) || Array.isArray(sc.urls)) return null;
1464
1476
  var ids = Array.isArray(sc.generation_ids) && sc.generation_ids.length > 1 ? sc.generation_ids : null;
1465
- var live = {
1477
+ var live = Object.assign({}, preRefSc(originTool, originArgs), {
1466
1478
  widget: 'generation',
1467
1479
  phase: 'generating',
1468
1480
  tool: originTool,
@@ -1477,7 +1489,8 @@ function liveFromTimedOut(sc) {
1477
1489
  quality: originArgs.quality,
1478
1490
  mode: originArgs.operation,
1479
1491
  visual_dna_ids: originArgs.visual_dna_ids,
1480
- moodboard_id: originArgs.moodboard_id
1492
+ moodboard_id: originArgs.moodboard_id,
1493
+ preset_id: originArgs.preset_id
1481
1494
  }, originArgs),
1482
1495
  count: ids ? ids.length : (originArgs.num_images || 1),
1483
1496
  generation_id: sc.generation_id,
@@ -1485,14 +1498,14 @@ function liveFromTimedOut(sc) {
1485
1498
  status_args: ids ? { generation_ids: ids, wait: true } : { generation_id: sc.generation_id, wait: true },
1486
1499
  session_id: sc.session_id || originArgs.session_id,
1487
1500
  project_id: sc.project_id || originArgs.project_id
1488
- };
1501
+ });
1489
1502
  if (ids) { live.generation_ids = ids; live.prompts = originArgs.prompts || []; }
1490
1503
  return live;
1491
1504
  }
1492
1505
 
1493
1506
  function completedFromPlain(sc) {
1494
1507
  if (!sc || (!Array.isArray(sc.urls) && !Array.isArray(sc.scenes))) return null;
1495
- return Object.assign({}, sc, {
1508
+ return Object.assign({}, preRefSc(originTool, originArgs), sc, {
1496
1509
  widget: 'generation',
1497
1510
  phase: 'completed',
1498
1511
  tool: originTool,
@@ -1505,7 +1518,8 @@ function completedFromPlain(sc) {
1505
1518
  resolution: originArgs.draft === true ? '480p-draft' : originArgs.draft === false ? String(originArgs.resolution || '').replace(/-draft$/i, '') : originArgs.resolution,
1506
1519
  aspect_ratio: originArgs.aspect_ratio,
1507
1520
  quality: originArgs.quality,
1508
- mode: originArgs.operation
1521
+ mode: originArgs.operation,
1522
+ preset_id: originArgs.preset_id
1509
1523
  }, originArgs),
1510
1524
  urls: sc.urls || [],
1511
1525
  session_id: sc.session_id || originArgs.session_id,
@@ -110,7 +110,7 @@ async function submitBatch(rawItems, submitOne) {
110
110
  // multi-id branch, same fix: always ship structuredContent via the shared
111
111
  // kind:'status' grid, which already renders any mix of completed/processing
112
112
  // items correctly.
113
- async function pollBatch(client, batch, { interval, timeout }, toolName, submittedModel) {
113
+ async function pollBatch(client, batch, { interval, timeout }, toolName, submittedModel, inputs = {}) {
114
114
  const polls = await Promise.all(batch.ids.map((id) => pollOrTimedOut(client, id, { interval, timeout })));
115
115
  const generations = polls.map((p, i) => p.timedOut
116
116
  ? { prompt: batch.ok[i].prompt, generation_id: batch.ids[i], status: 'processing', note: 'Still running — call get_generation_status with wait=true to collect it.' }
@@ -126,8 +126,9 @@ async function pollBatch(client, batch, { interval, timeout }, toolName, submitt
126
126
  return uiCompleted({
127
127
  tool: toolName, kind: 'status', client,
128
128
  model: modelsRan.length === 1 ? modelsRan[0] : (submittedModel || 'Generations'),
129
- gen: { generation_id: batch.ids[0], session_id: batch.ok[0].gen.session_id },
130
- settings: {},
129
+ gen: { generation_id: batch.ids[0], session_id: batch.ok[0].gen.session_id },
130
+ settings: {},
131
+ ...inputs,
131
132
  items: generations.map(g => ({
132
133
  id: g.generation_id,
133
134
  state: g.status,
@@ -338,10 +339,11 @@ function registerGenerateTools(server, client, options = {}) {
338
339
  if (!prompt && !(prompts && prompts.length)) throw new Error('Provide prompt or prompts');
339
340
  model = await canonicalModelId(client, model, 'text_to_img'); // lenient id resolution ("z-image" → "z-image/turbo")
340
341
  aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, 'text_to_img');
341
- const shared = {
342
- model, aspect_ratio, enhance_prompt, font_ids,
343
- reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, background, output_format, output_compression, moderation, mask_image_url, preset_id, cinematic, skip_color_palette, project_id, session_id
344
- };
342
+ const shared = await rehostLocalPaths(client, {
343
+ model, aspect_ratio, enhance_prompt, font_ids,
344
+ reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, background, output_format, output_compression, moderation, mask_image_url, preset_id, cinematic, skip_color_palette, project_id, session_id
345
+ }, { allowLocalFiles: !options.remote });
346
+ reference_images = [...(shared.reference_images || []), shared.mask_image_url].filter(Boolean);
345
347
 
346
348
  // Batch mode: N different prompts, one widget owning all generation ids.
347
349
  if (prompts && prompts.length) {
@@ -354,7 +356,7 @@ function registerGenerateTools(server, client, options = {}) {
354
356
  status_args: { generation_ids: batch.ids, wait: true },
355
357
  reference_images
356
358
  });
357
- return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image', model);
359
+ return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image', model, { settings: imageSettings(shared), reference_images });
358
360
  }
359
361
 
360
362
  const gen = await client.post('/v1/generate/image', { ...shared, prompt, num_images });
@@ -429,10 +431,12 @@ function registerGenerateTools(server, client, options = {}) {
429
431
  if (!prompt && !(prompts && prompts.length)) throw new Error('Provide prompt or prompts');
430
432
  model = await canonicalModelId(client, model, 'image_editing'); // lenient id resolution ("z-image" → "z-image/turbo")
431
433
  aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, 'image_editing');
432
- const shared = {
433
- model, source_images, reference_images, aspect_ratio, enhance_prompt, font_ids,
434
- visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, background, output_format, output_compression, moderation, mask_image_url, preset_id, cinematic, skip_color_palette, project_id, session_id
435
- };
434
+ const shared = await rehostLocalPaths(client, {
435
+ model, source_images, reference_images, aspect_ratio, enhance_prompt, font_ids,
436
+ visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, background, output_format, output_compression, moderation, mask_image_url, preset_id, cinematic, skip_color_palette, project_id, session_id
437
+ }, { allowLocalFiles: !options.remote });
438
+ source_images = shared.source_images;
439
+ reference_images = [...(shared.reference_images || []), shared.mask_image_url].filter(Boolean);
436
440
  const settings = imageSettings(shared);
437
441
 
438
442
  // Batch mode: N different edit instructions against the SAME source
@@ -449,7 +453,7 @@ function registerGenerateTools(server, client, options = {}) {
449
453
  status_args: { generation_ids: batch.ids, wait: true },
450
454
  reference_images: [...(source_images || []), ...(reference_images || [])]
451
455
  });
452
- return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image_edit', model);
456
+ return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image_edit', model, { settings, reference_images: [...(source_images || []), ...reference_images] });
453
457
  }
454
458
 
455
459
  const gen = await client.post('/v1/generate/image-edit', { ...shared, prompt, num_images });
@@ -1441,7 +1445,14 @@ function registerGenerateTools(server, client, options = {}) {
1441
1445
  // the live card merges this payload over its submit-time state, so
1442
1446
  // the finished card shows every reference, including server-side
1443
1447
  // additions (DNA stills, merged sources) the tool call never named.
1444
- reference_images: res.reference_images,
1448
+ reference_images: res.reference_images,
1449
+ reference_videos: res.reference_videos,
1450
+ reference_audio: res.reference_audio,
1451
+ ...(res.preset ? { settings: {
1452
+ preset_id: res.preset.id,
1453
+ preset_name: res.preset.name,
1454
+ preset_thumbnail: res.preset.thumbnail_url,
1455
+ } } : {}),
1445
1456
  // Transcription results ride the same status tool as media
1446
1457
  // generations; without these the transcript widget merges a payload
1447
1458
  // with no transcript in it.