@kolbo/mcp 1.119.2 → 1.120.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.119.2",
3
+ "version": "1.120.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": {
@@ -816,6 +816,9 @@ function poll(sc) {
816
816
  return renderTrackingIssue('This is still running longer than the tracking window. Do not retry it — any completed result will appear in your Kolbo library.');
817
817
  }
818
818
  var args = sc.status_args || { generation_id: sc.generation_id, wait: true };
819
+ // Host-bridged calls may not reset their timeout on progress: keep each
820
+ // wait window inside the common 60s request timeout.
821
+ if (args.wait) args = Object.assign({}, args, { wait_seconds: 45 });
819
822
  pollInFlight = true;
820
823
  window.kolbo.callTool(sc.poll_tool || 'get_generation_status', args).then(function (res) {
821
824
  pollInFlight = false;
@@ -1277,9 +1280,14 @@ function renderTrackingIssue(msg) {
1277
1280
  el('stage').innerHTML = '<div class="k-error">' + ICONS.clock + ' ' + esc(msg) + '</div>';
1278
1281
  el('actions').innerHTML = '<button class="k-btn primary" id="status-btn">' + ICONS.clock + ' Check status</button>' +
1279
1282
  '<button class="k-btn ghost" id="tracking-open">Open in Kolbo ' + ICONS.open + '</button>';
1283
+ // Resume tracking in place: the card re-checks the SAME generation ids
1284
+ // (never a resubmit). A chat message alone left the card red even after
1285
+ // the agent learned the job had finished.
1280
1286
  el('status-btn').onclick = function () {
1281
- window.kolbo.sendMessage('Check the existing Kolbo generation status without retrying it.' +
1282
- (state && state.generation_id ? '\\nGeneration ID: ' + state.generation_id : ''));
1287
+ if (!state) return;
1288
+ pollErrors = 0; pollStart = 0;
1289
+ setPhaseChip('Checking', true);
1290
+ schedulePoll(state);
1283
1291
  };
1284
1292
  el('tracking-open').onclick = function () {
1285
1293
  window.kolbo.openLink(kolboUrl(state));
@@ -94,6 +94,8 @@ function poll() {
94
94
  return trackingIssue('This transcription is still running longer than the tracking window. Do not retry it — any completed result will appear in your Kolbo library.');
95
95
  }
96
96
  var args = state.status_args || { generation_id: state.generation_id, wait: true };
97
+ // Keep each host-bridged wait inside the common 60s request timeout.
98
+ if (args.wait) args = Object.assign({}, args, { wait_seconds: 45 });
97
99
  window.kolbo.callTool(state.poll_tool || 'get_generation_status', args)
98
100
  .then(function (res) {
99
101
  var st = structured(res) || {};
@@ -129,9 +131,12 @@ function trackingIssue(msg) {
129
131
  el('stage').classList.remove('k-empty');
130
132
  el('stage').innerHTML = '<div class="k-error">' + ICONS.clock + ' ' + esc(msg) + '</div>';
131
133
  el('actions').innerHTML = '<button class="k-btn primary" id="transcript-status">' + ICONS.clock + ' Check status</button>';
134
+ // Resume tracking in place (same generation id, never a resubmit).
132
135
  el('transcript-status').onclick = function () {
133
- window.kolbo.sendMessage('Check the existing Kolbo transcription status without retrying it.' +
134
- (state && state.generation_id ? '\\nGeneration ID: ' + state.generation_id : ''));
136
+ if (!state) return;
137
+ pollErrors = 0;
138
+ el('actions').innerHTML = '';
139
+ boot(Object.assign({}, state, { phase: 'generating' }));
135
140
  };
136
141
  window.kolbo.notifySize();
137
142
  }
@@ -604,16 +604,17 @@ function registerGenerateTools(server, client, options = {}) {
604
604
  'Check the status of a Creative Director batch (from generate_creative_director) by its generation_id. Returns overall state ("processing" until EVERY scene is terminal, then "completed"/"failed") plus each scene\'s number, title, per-scene status, and image_urls/video_urls. Set wait=true to block until the batch is terminal or the wait window closes, whichever comes first — a batch longer than one window returns state="processing" and you simply call again with wait=true. Prefer this over the generic get_generation_status for Creative Director ids — the generic tool now returns the same scene data (it delegates here), but this one is the direct route.',
605
605
  {
606
606
  generation_id: z.string().describe('The Creative Director generation_id returned by generate_creative_director.'),
607
- wait: z.boolean().optional().describe(`If true, block until the batch is terminal, for at most ~${WAIT_WINDOW_S}s per call. A batch that outlives one window comes back state="processing" (not an error) — call again with wait=true until it is terminal. Always prefer this over polling with wait=false.`)
607
+ wait: z.boolean().optional().describe(`If true, block until the batch is terminal, for at most ~${WAIT_WINDOW_S}s per call. A batch that outlives one window comes back state="processing" (not an error) — call again with wait=true until it is terminal. Always prefer this over polling with wait=false.`),
608
+ wait_seconds: z.number().min(1).max(180).optional().describe('Optional cap on one wait=true window, in seconds (default and maximum: the transport window). Interactive cards pass 45 so each call fits a host 60s request timeout.')
608
609
  },
609
- async ({ generation_id, wait }) => {
610
+ async ({ generation_id, wait, wait_seconds }) => {
610
611
  const statusUrl = `/v1/generate/creative-director/${encodeURIComponent(generation_id)}/status`;
611
612
  let status;
612
613
  if (wait) {
613
614
  try {
614
615
  status = await pollUntilDone(client, generation_id, {
615
616
  interval: 15000,
616
- timeout: WAIT_WINDOW_MS,
617
+ timeout: wait_seconds ? Math.min(wait_seconds * 1000, WAIT_WINDOW_MS) : WAIT_WINDOW_MS,
617
618
  statusUrl
618
619
  });
619
620
  } catch (err) {
@@ -1348,14 +1349,20 @@ function registerGenerateTools(server, client, options = {}) {
1348
1349
  {
1349
1350
  generation_id: z.string().optional().describe('A single generation ID to check'),
1350
1351
  generation_ids: z.array(z.string()).max(MAX_STATUS_IDS).optional().describe(`Multiple generation IDs to check in ONE call (max ${MAX_STATUS_IDS}). Returns { all_done, pending, generations[] } — always prefer this over checking IDs one by one.`),
1351
- wait: z.boolean().optional().describe(`If true, block until every generation reaches a terminal state (completed/failed), for at most ~${WAIT_WINDOW_S}s per call, then return whatever state they are in. Anything still processing is reported, not errored — re-issue with wait=true and only the still-pending ids. This is always better than polling with wait=false.`)
1352
+ wait: z.boolean().optional().describe(`If true, block until every generation reaches a terminal state (completed/failed), for at most ~${WAIT_WINDOW_S}s per call, then return whatever state they are in. Anything still processing is reported, not errored — re-issue with wait=true and only the still-pending ids. This is always better than polling with wait=false.`),
1353
+ wait_seconds: z.number().min(1).max(180).optional().describe('Optional cap on one wait=true window, in seconds (default and maximum: the transport window). Interactive cards pass 45 so each call fits a host 60s request timeout.')
1352
1354
  },
1353
- async ({ generation_id, generation_ids, wait }) => {
1355
+ async ({ generation_id, generation_ids, wait, wait_seconds }) => {
1354
1356
  const ids = (generation_ids && generation_ids.length > 0)
1355
1357
  ? [...new Set(generation_ids)]
1356
1358
  : (generation_id ? [generation_id] : []);
1357
1359
  if (ids.length === 0) throw new Error('Provide generation_id or generation_ids');
1358
1360
  if (ids.length > MAX_STATUS_IDS) throw new Error(`At most ${MAX_STATUS_IDS} generation IDs can be checked at once.`);
1361
+ // Widget polls arrive through the host's iframe bridge, which (unlike the
1362
+ // model's own calls) need not reset its request timeout on our progress
1363
+ // ticks — a 180s stdio window there timed out every poll, stranding cards
1364
+ // on Generating and then "Tracking paused". Widgets pass wait_seconds.
1365
+ const windowMs = wait_seconds ? Math.min(wait_seconds * 1000, WAIT_WINDOW_MS) : WAIT_WINDOW_MS;
1359
1366
 
1360
1367
  // One status check (or blocking poll) per id. Never let one bad id
1361
1368
  // reject the whole batch — surface it as a failed entry instead.
@@ -1366,7 +1373,7 @@ function registerGenerateTools(server, client, options = {}) {
1366
1373
  // completion responsive without multiplying backend traffic for
1367
1374
  // every card left open in a host conversation. The window itself is
1368
1375
  // bounded by the transport — see WAIT_WINDOW_MS above.
1369
- const result = await pollUntilDone(client, id, { interval: 15000, timeout: WAIT_WINDOW_MS });
1376
+ const result = await pollUntilDone(client, id, { interval: 15000, timeout: windowMs });
1370
1377
  return { generation_id: id, ...result };
1371
1378
  }
1372
1379
  const result = await client.get(`/v1/generate/${encodeURIComponent(id)}/status`);
@@ -44,16 +44,17 @@ function registerPresetTools(server, client, options = {}) {
44
44
  : 'Pass search next time if you already know the name. Pass the chosen exact id as preset_id.',
45
45
  });
46
46
 
47
- if (lookup) return { content: [{ type: 'text', text }] };
48
-
49
47
  // TOOL_WIDGETS declares mediaGrid for this tool, so hosts that mount from
50
- // the declaration (Claude desktop) rendered a media grid fed a list-shaped
51
- // 8-item page with no page_tool: a "Load more" that could never load. Ship
52
- // the whole catalog (a few hundred small rows) to the grid it actually mounts.
48
+ // the declaration (Claude desktop) prepare a grid on EVERY call — the
49
+ // search lookup included. A text-only lookup left that grid guessing from
50
+ // the compact JSON: thumbnail-less file tiles under the UNFILTERED total.
51
+ // structuredContent also shadows the text on those hosts, so it carries
52
+ // the ids, total and note the agent needs.
53
+ const total = search && !result.search ? presets.length : (result.count || presets.length);
53
54
  return uiResult(UI.mediaGrid, text, {
54
55
  widget: 'mediaGrid',
55
56
  title: 'Presets' + (type ? ' — ' + type : ''),
56
- items: presets.slice(0, 300).map(p => ({
57
+ items: presets.slice(0, lookup ? 8 : 300).map(p => ({
57
58
  id: p.id,
58
59
  title: p.name,
59
60
  subtitle: p.category,
@@ -63,8 +64,10 @@ function registerPresetTools(server, client, options = {}) {
63
64
  url: p.thumbnail_url || p.thumbnail,
64
65
  use_hint: 'Use preset "{TITLE}" (preset_id: {ID}) for my next generation — ask me for the prompt.'
65
66
  })),
66
- total: result.count || presets.length,
67
- has_more: presets.length > 300
67
+ total,
68
+ has_more: presets.length > (lookup ? 8 : 300),
69
+ ...(lookup ? { _lookup: true, note: 'Pick the closest id and pass it as preset_id.' } : {}),
70
+ ...(result.warning ? { warning: result.warning } : {}),
68
71
  });
69
72
  }
70
73
  );