@kolbo/mcp 1.84.1 → 1.84.2

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.84.1",
3
+ "version": "1.84.2",
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": {
package/src/apps/index.js CHANGED
@@ -88,6 +88,17 @@ const WIDGET_CSP = {
88
88
  'https://assets.sketchfab.com',
89
89
  'https://sketchfab-prod-media.s3.amazonaws.com',
90
90
 
91
+ // Voice PREVIEW audio hosts. list_voices ships every voice with a
92
+ // preview_url and the card renders a real <audio> for it, but 150 of the
93
+ // 864 production voices store that preview on a provider host rather than a
94
+ // Kolbo bucket: 138 google voices on storage.googleapis.com and 12 on
95
+ // api.us.elevenlabs.io. media-src blocked both, so every google voice — the
96
+ // entire Hebrew set — rendered a player stuck at 0:00 / 0:00 with no error
97
+ // anywhere. The Spaces-hosted previews come free via HOST_MAP above; these
98
+ // two do not, because they are not ours.
99
+ 'https://storage.googleapis.com',
100
+ 'https://api.us.elevenlabs.io',
101
+
91
102
  // Default SYNCI catalog project. Any production override must be reviewed
92
103
  // and added here as an exact hostname before deployment.
93
104
  'https://gfbpxdkripkbbrcvoyeh.supabase.co',
@@ -48,17 +48,17 @@ function boot(sc) {
48
48
  var audioItems = sc.items.filter(function (i) { return i.media_type === 'audio'; });
49
49
  var visualItems = sc.items.filter(function (i) { return i.media_type !== 'audio'; });
50
50
  var h = '';
51
+ // Render everything currently in state — each PAGE is already capped
52
+ // server-side (GRID_CAP), so this grows one page per Load more instead of
53
+ // being pinned forever. The old hard slices (24 visual / 12 audio) meant an
54
+ // appended page could never actually appear, and the count below tallied all
55
+ // items while only 36 were drawn, so the button also lied about the total.
51
56
  if (visualItems.length) {
52
- h += '<div class="k-grid">' + visualItems.slice(0, 24).map(cellHTML).join('') + '</div>';
57
+ h += '<div class="k-grid">' + visualItems.map(cellHTML).join('') + '</div>';
53
58
  }
54
59
  if (audioItems.length) {
55
- h += audioItems.slice(0, 12).map(audioRowHTML).join('');
60
+ h += audioItems.map(audioRowHTML).join('');
56
61
  }
57
- // Only a fixed page ever renders here (24 visual + 12 audio) — with a library
58
- // in the thousands there was no way to reach the rest short of asking in
59
- // chat. A visible "Load more" turns that into one click; it sends a message
60
- // rather than calling the tool directly, since the widget has no host API
61
- // to invoke a tool itself — same mechanism every other "Use" action here uses.
62
62
  var shown = visualItems.length + audioItems.length;
63
63
  if (sc.total != null && sc.total > shown) {
64
64
  h += '<button class="k-btn" id="load-more" style="width:100%;margin-top:10px">Load more (' +
@@ -145,13 +145,50 @@ function wire() {
145
145
  });
146
146
  var loadMore = el('load-more');
147
147
  if (loadMore) {
148
- loadMore.onclick = function () {
149
- window.kolbo.sendMessage('Show me the next page of this same media search (already saw ' +
150
- (state.items.length) + ' of ' + state.total + ' results).');
151
- };
148
+ loadMore.onclick = function () { fetchNextPage(loadMore); };
152
149
  }
153
150
  }
154
151
 
152
+ // Fetch the next page IN the widget and append it.
153
+ //
154
+ // This used to sendMessage() a request for "the next page of this same media
155
+ // search", on the stated belief that a widget has no way to invoke a tool. It
156
+ // does — window.kolbo.callTool, the same bridge call every generation card
157
+ // polls status with. And the message could not have worked anyway: the payload
158
+ // carried no page number and none of the filters, so the model had nothing to
159
+ // reconstruct the query from and would re-run page 1 or something else. The
160
+ // button appeared to do nothing.
161
+ function fetchNextPage(btn) {
162
+ if (!state || !state.page_tool || btn.disabled) return;
163
+ var next = (state.page || 1) + 1;
164
+ btn.disabled = true;
165
+ var label = btn.textContent;
166
+ btn.innerHTML = '<span class="k-spin"></span> Loading';
167
+ var args = {};
168
+ var q = state.query || {};
169
+ for (var k in q) { if (q[k] !== undefined && q[k] !== null && q[k] !== '') args[k] = q[k]; }
170
+ args.page = next;
171
+ if (state.page_size) args.page_size = state.page_size;
172
+
173
+ window.kolbo.callTool(state.page_tool, args).then(function (res) {
174
+ var sc = structured(res);
175
+ var more = (sc && sc.items) || [];
176
+ if (!more.length) {
177
+ // Nothing came back: say so rather than restoring a button that still
178
+ // looks like it has pages behind it.
179
+ btn.textContent = 'No more results';
180
+ return;
181
+ }
182
+ state.items = (state.items || []).concat(more);
183
+ state.page = (sc && sc.page) || next;
184
+ if (sc && sc.total != null) state.total = sc.total;
185
+ boot(state); // re-renders the grid + a fresh Load more button
186
+ }).catch(function () {
187
+ btn.disabled = false;
188
+ btn.textContent = label;
189
+ });
190
+ }
191
+
155
192
  function useItem(i) {
156
193
  var item = state.items[i];
157
194
  if (!item || !item.id) return;
@@ -851,6 +851,18 @@ async function uiCompleted(p, textPayload, extraContent) {
851
851
  // above which assume everything finished together. Only set when the
852
852
  // caller actually has this shape; every existing caller is unaffected.
853
853
  ...(Array.isArray(p.items) ? { items: p.items } : {}),
854
+ // Transcription payload. get_generation_status is the ONLY way the live
855
+ // transcript widget learns its result, and it reads text/srt_url/txt_url off
856
+ // this object — but uiCompleted is shaped for the generation card and
857
+ // dropped every one, so a finished transcription rendered "(empty
858
+ // transcript)" with no SRT/TXT buttons while the text sat in the status
859
+ // response. structuredContent SHADOWS the text block on widget hosts, so
860
+ // omitting a field here is the same as deleting it.
861
+ ...(typeof p.text === 'string' ? { text: p.text } : {}),
862
+ ...(p.srt_url ? { srt_url: p.srt_url } : {}),
863
+ ...(p.word_by_word_srt_url ? { word_by_word_srt_url: p.word_by_word_srt_url } : {}),
864
+ ...(p.txt_url ? { txt_url: p.txt_url } : {}),
865
+ ...(p.audio_url ? { audio_url: p.audio_url } : {}),
854
866
  // The voice, by name and portrait. uiGenerating has carried this since the
855
867
  // chips were introduced; uiCompleted never did, so it silently dropped a
856
868
  // resolved voice its caller had already looked up — every FINISHED speech
@@ -1118,6 +1118,14 @@ function registerGenerateTools(server, client, options = {}) {
1118
1118
  state: single.state,
1119
1119
  urls: done ? urls : undefined,
1120
1120
  thumbnail_url: res.thumbnail_url,
1121
+ // Transcription results ride the same status tool as media
1122
+ // generations; without these the transcript widget merges a payload
1123
+ // with no transcript in it and renders "(empty transcript)".
1124
+ text: typeof res.text === 'string' ? res.text : undefined,
1125
+ srt_url: res.srt_url || undefined,
1126
+ word_by_word_srt_url: res.word_by_word_srt_url || undefined,
1127
+ txt_url: res.txt_url || undefined,
1128
+ audio_url: res.audio_url || undefined,
1121
1129
  // The refs the server actually conditioned on (reference_details) —
1122
1130
  // the live card merges this payload over its submit-time state, so
1123
1131
  // the finished card shows every reference, including server-side
@@ -303,7 +303,17 @@ function registerMediaTools(server, client, options = {}) {
303
303
  title: 'Media Library',
304
304
  items,
305
305
  total: totalItems != null ? totalItems : media.length,
306
- shown: Math.min(media.length, GRID_CAP)
306
+ shown: Math.min(media.length, GRID_CAP),
307
+ // Everything "Load more" needs to fetch page N+1 ITSELF. The button used
308
+ // to send a chat message asking the model to run the next page, on the
309
+ // belief that a widget cannot invoke a tool — it can
310
+ // (window.kolbo.callTool, the same call every generation card polls
311
+ // with). Worse, the payload carried no page and no filters, so the model
312
+ // could not reconstruct the query either and typically re-ran page 1.
313
+ page_tool: 'list_media',
314
+ page: page || 1,
315
+ page_size: page_size || 50,
316
+ query: { project_id, folder_id, type, category, source_type, sort, search }
307
317
  });
308
318
  }
309
319
  );