@kolbo/mcp 1.75.3 → 1.75.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.75.3",
3
+ "version": "1.75.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": {
@@ -77,22 +77,35 @@ async function submitBatch(rawItems, submitOne) {
77
77
  }
78
78
 
79
79
  // Blocking path for text-only hosts: wait for every batch member, aggregate.
80
- async function pollBatch(client, batch, { interval, timeout }) {
80
+ // This is the exact path Kolbo Code takes for a `prompts`-array batch call
81
+ // (ui() is false there, so the uiGenerating branch above never runs) — it was
82
+ // text-only, no structuredContent, so a batch of N prompts rendered as a bare
83
+ // JSON dump instead of N cards. Same class of bug as get_generation_status's
84
+ // multi-id branch, same fix: always ship structuredContent via the shared
85
+ // kind:'status' grid, which already renders any mix of completed/processing
86
+ // items correctly.
87
+ async function pollBatch(client, batch, { interval, timeout }, toolName) {
81
88
  const polls = await Promise.all(batch.ids.map((id) => pollOrTimedOut(client, id, { interval, timeout })));
82
89
  const generations = polls.map((p, i) => p.timedOut
83
90
  ? { 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.' }
84
91
  : { prompt: batch.ok[i].prompt, generation_id: batch.ids[i], status: 'completed', ...creditFields(polls[i].result), urls: p.result.result.urls });
85
- return {
86
- content: [{
87
- type: 'text',
88
- text: JSON.stringify({
89
- batch: true,
90
- session_id: batch.ok[0].gen.session_id,
91
- generations,
92
- failed_submissions: batch.failed.length ? batch.failed : undefined
93
- }, null, 2)
94
- }]
95
- };
92
+ const text = JSON.stringify({
93
+ batch: true,
94
+ session_id: batch.ok[0].gen.session_id,
95
+ generations,
96
+ failed_submissions: batch.failed.length ? batch.failed : undefined
97
+ }, null, 2);
98
+ return uiCompleted({
99
+ tool: toolName, kind: 'status', client,
100
+ model: 'Generations', gen: { generation_id: batch.ids[0], session_id: batch.ok[0].gen.session_id },
101
+ settings: {},
102
+ items: generations.map(g => ({
103
+ id: g.generation_id,
104
+ state: g.status,
105
+ title: g.prompt,
106
+ url: Array.isArray(g.urls) ? g.urls[0] : undefined,
107
+ })),
108
+ }, text);
96
109
  }
97
110
 
98
111
  // ─── Widget settings block ──────────────────────────────────────────────────
@@ -205,7 +218,7 @@ function registerGenerateTools(server, client, options = {}) {
205
218
  status_args: { generation_ids: batch.ids, wait: true },
206
219
  reference_images
207
220
  });
208
- return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 240000 });
221
+ return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 240000 }, 'generate_image');
209
222
  }
210
223
 
211
224
  const gen = await client.post('/v1/generate/image', { ...shared, prompt, num_images });
@@ -288,7 +301,7 @@ function registerGenerateTools(server, client, options = {}) {
288
301
  status_args: { generation_ids: batch.ids, wait: true },
289
302
  reference_images: source_images
290
303
  });
291
- return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 240000 });
304
+ return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 240000 }, 'generate_image_edit');
292
305
  }
293
306
 
294
307
  const gen = await client.post('/v1/generate/image-edit', { ...shared, prompt, num_images });
@@ -463,7 +476,7 @@ function registerGenerateTools(server, client, options = {}) {
463
476
  video_urls: s.video_urls || null
464
477
  }));
465
478
  const completed = scenes.filter(s => s.status === 'completed').length;
466
- return { content: [{ type: 'text', text: JSON.stringify({
479
+ const text = JSON.stringify({
467
480
  ...creditFields(status),
468
481
  state: status.state,
469
482
  generation_id,
@@ -474,7 +487,23 @@ function registerGenerateTools(server, client, options = {}) {
474
487
  _hint: status.state === 'completed'
475
488
  ? 'All scenes terminal. Every completed scene\'s image_urls/video_urls are final.'
476
489
  : `Still running — not a failure. Each wait=true call blocks for at most ~${WAIT_WINDOW_S}s, and a video batch routinely outlasts several windows, so call get_creative_director_status again with wait=true and keep going until state is terminal. Scenes that already carry image_urls/video_urls are done; never re-run generate_creative_director.`
477
- }, null, 2) }] };
490
+ }, null, 2);
491
+ // Same text-only gap as get_generation_status/pollBatch, called out in
492
+ // this tool's own comment above ("especially parallel VIDEO scenes that
493
+ // exceed the blocking poll window") — a CD batch checked here after
494
+ // outliving one wait window rendered as a bare JSON dump, no scene
495
+ // thumbnails. Always ship the same kind:'status' grid, one item per scene.
496
+ return uiCompleted({
497
+ tool: 'get_creative_director_status', kind: 'status', client,
498
+ model: 'Generations', gen: { generation_id },
499
+ settings: {},
500
+ items: scenes.map(s => ({
501
+ id: generation_id + '-' + s.scene_number,
502
+ state: s.status,
503
+ title: s.title || ('Scene ' + s.scene_number),
504
+ url: (Array.isArray(s.image_urls) && s.image_urls[0]) || (Array.isArray(s.video_urls) && s.video_urls[0]) || undefined,
505
+ })),
506
+ }, text);
478
507
  }
479
508
  );
480
509
 
@@ -519,7 +548,7 @@ function registerGenerateTools(server, client, options = {}) {
519
548
  status_args: { generation_ids: batch.ids, wait: true },
520
549
  reference_images
521
550
  });
522
- return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 });
551
+ return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 }, 'generate_video');
523
552
  }
524
553
 
525
554
  const gen = await client.post('/v1/generate/video', { ...shared, prompt });
@@ -606,7 +635,7 @@ function registerGenerateTools(server, client, options = {}) {
606
635
  status_args: { generation_ids: batch.ids, wait: true },
607
636
  reference_images: items.map((item) => item.image_url)
608
637
  });
609
- return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 });
638
+ return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 }, 'generate_video_from_image');
610
639
  }
611
640
 
612
641
  const gen = await client.post('/v1/generate/video/from-image', { ...shared, image_url, prompt });
@@ -965,17 +994,38 @@ function registerGenerateTools(server, client, options = {}) {
965
994
  : '';
966
995
  const pendingHint = stillRunningHint(idsPhrase);
967
996
 
968
- // Single-id calls keep the original flat shape — the generation widget
969
- // waits on this tool with { generation_id, wait:true } and reads
970
- // state/result at the top level.
997
+ // Single-id calls keep the original flat TEXT shape — a live uiGenerating
998
+ // widget (ui()==true, still open from the original generate_* call) polls
999
+ // this tool directly from its own iframe JS and reads state/result at the
1000
+ // top level, so `text` must stay exactly what it always was.
1001
+ //
1002
+ // But a single id checked here often has NO live widget behind it at all:
1003
+ // Kolbo Code never opens one (ui() is false there), and even on hosts that
1004
+ // do, the original call can have returned poll.timedOut before uiGenerating
1005
+ // ever ran (see pollOrTimedOut) — a parallel batch is exactly when that
1006
+ // happens, since every concurrent generation competes for the same
1007
+ // per-call poll window. Either way this is then a FRESH, standalone tool
1008
+ // response that needs its own renderable card, same reasoning as the
1009
+ // multi-id fix just above. Reuse the same kind:'status' single-item grid
1010
+ // rather than inventing per-media-type rendering here.
971
1011
  if (!generation_ids || generation_ids.length === 0) {
972
1012
  const single = results[0];
973
1013
  single._hint = pending.length === 0
974
1014
  ? 'This generation is in a FINAL state — do not poll it again.'
975
1015
  : pendingHint;
976
- return {
977
- content: [{ type: 'text', text: JSON.stringify(single, null, 2) }]
978
- };
1016
+ const singleText = JSON.stringify(single, null, 2);
1017
+ const res = single.result || {};
1018
+ return uiCompleted({
1019
+ tool: 'get_generation_status', kind: 'status', client,
1020
+ model: 'Generations', gen: { generation_id: single.generation_id },
1021
+ settings: {},
1022
+ items: [{
1023
+ id: single.generation_id,
1024
+ state: single.state,
1025
+ title: res.prompt_used || res.prompt || undefined,
1026
+ url: Array.isArray(res.urls) ? res.urls[0] : undefined,
1027
+ }],
1028
+ }, singleText);
979
1029
  }
980
1030
 
981
1031
  const text = JSON.stringify({