@kolbo/mcp 1.31.0 → 1.31.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.31.0",
3
+ "version": "1.31.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": {
@@ -33,7 +33,7 @@ const BRIDGE_JS = `
33
33
  var initialized = false;
34
34
  var queue = []; // deferred host-bound sends until initialized
35
35
  var hostContext = null;
36
- var readyFns = [], toolResultFns = [], themeFns = [];
36
+ var readyFns = [], toolResultFns = [], themeFns = [], toolInputFns = [];
37
37
 
38
38
  function post(msg) { window.parent.postMessage(msg, '*'); }
39
39
 
@@ -66,6 +66,10 @@ const BRIDGE_JS = `
66
66
  }
67
67
  if (m.method === 'ui/notifications/tool-result') {
68
68
  toolResultFns.forEach(function (f) { try { f(m.params || {}); } catch (e) {} });
69
+ } else if (m.method === 'ui/notifications/tool-input' || m.method === 'ui/notifications/tool-input-partial') {
70
+ // Fires while the tool is still RUNNING — lets widgets show a real
71
+ // "preparing" state instead of a blank card until the result lands.
72
+ toolInputFns.forEach(function (f) { try { f((m.params && m.params.arguments) || {}); } catch (e) {} });
69
73
  } else if (m.method === 'ui/notifications/host-context-changed') {
70
74
  hostContext = (m.params && m.params.hostContext) || m.params || hostContext;
71
75
  themeFns.forEach(function (f) { try { f(hostContext); } catch (e) {} });
@@ -132,6 +136,7 @@ const BRIDGE_JS = `
132
136
  window.kolbo = {
133
137
  ready: function (f) { if (initialized) f(hostContext); else readyFns.push(f); },
134
138
  onToolResult: function (f) { toolResultFns.push(f); },
139
+ onToolInput: function (f) { toolInputFns.push(f); },
135
140
  onThemeChange: function (f) { themeFns.push(f); },
136
141
  callTool: function (name, args) { return request('tools/call', { name: name, arguments: args || {} }); },
137
142
  sendMessage: function (text) {
package/src/apps/html.js CHANGED
@@ -33,6 +33,12 @@ function esc(s) {
33
33
  });
34
34
  }
35
35
  function el(id) { return document.getElementById(id); }
36
+ // Force-download via the api.kolbo.ai proxy (CDN files open inline otherwise —
37
+ // browsers display images/videos instead of saving them).
38
+ function downloadUrl(u) {
39
+ if (!u) return u;
40
+ return 'https://api.kolbo.ai/mcp/download?url=' + encodeURIComponent(u);
41
+ }
36
42
  function fmtCredits(n) { return (n == null) ? '' : (Math.round(n * 100) / 100) + ' cr'; }
37
43
  function fmtDur(s) { if (s == null) return ''; s = Math.round(s); return s >= 60 ? Math.floor(s/60) + 'm ' + (s%60) + 's' : s + 's'; }
38
44
  function applyTheme(ctx) {
@@ -64,7 +64,12 @@ function boot(sc) {
64
64
 
65
65
  window.kolbo.onToolResult(function (result) {
66
66
  var sc = result.structuredContent || structured(result);
67
- if (sc) boot(sc);
67
+ if (sc && sc.groups) return boot(sc);
68
+ // Internal lookup (Claude checking constraints mid-task) — no catalog data
69
+ // was attached on purpose. Collapse instead of showing a dead card.
70
+ var card = document.querySelector('.k-card');
71
+ if (card) card.style.display = 'none';
72
+ window.kolbo.notifySize();
68
73
  });
69
74
  `;
70
75
 
@@ -224,7 +224,7 @@ function render3d(sc, urls) {
224
224
  '<button class="k-btn" data-url="' + esc(u) + '">Download</button></div>';
225
225
  }).join('');
226
226
  Array.prototype.forEach.call(el('stage').querySelectorAll('.k-btn[data-url]'), function (b) {
227
- b.onclick = function () { window.kolbo.openLink(b.getAttribute('data-url')); };
227
+ b.onclick = function () { window.kolbo.openLink(downloadUrl(b.getAttribute('data-url'))); };
228
228
  });
229
229
  }
230
230
 
@@ -259,7 +259,8 @@ function renderError(msg) {
259
259
  el('stage').innerHTML = '<div class="k-error">⚠ ' + esc(msg) + '</div>';
260
260
  el('actions').innerHTML = '<button class="k-btn" id="retry-btn">↻ Try Again</button>';
261
261
  el('retry-btn').onclick = function () {
262
- window.kolbo.sendMessage('Please retry that ' + (TOOL_TITLES[state.tool] || 'generation').toLowerCase() + ' — it failed with: ' + msg);
262
+ var what = (state && TOOL_TITLES[state.tool]) || 'generation';
263
+ window.kolbo.sendMessage('Please retry that ' + what.toLowerCase() + ' — it failed with: ' + msg);
263
264
  };
264
265
  window.kolbo.notifySize();
265
266
  }
@@ -324,7 +325,7 @@ function renderActions(sc) {
324
325
  a.push('<button class="k-btn ghost" id="btn-open">Open in Kolbo ↗</button>');
325
326
  el('actions').innerHTML = a.join('');
326
327
 
327
- bind('btn-download', function () { window.kolbo.openLink(currentUrl()); });
328
+ bind('btn-download', function () { window.kolbo.openLink(downloadUrl(currentUrl())); });
328
329
  bind('btn-open', function () { window.kolbo.openLink(state.open_url || 'https://app.kolbo.ai'); });
329
330
  bind('btn-recreate', function () {
330
331
  window.kolbo.sendMessage('Recreate this with the same settings' +
@@ -367,17 +368,41 @@ function openPromptRow(placeholder, onSend) {
367
368
  window.kolbo.notifySize();
368
369
  }
369
370
 
371
+ /* ---------- pre-result "Preparing" state ----------
372
+ The host mounts this iframe as soon as the tool is CALLED; the result can
373
+ take many seconds (model resolution, file upload, submit). Show a live
374
+ shell immediately instead of a blank card. */
375
+ function bootPre(toolName, args) {
376
+ if (state) return; // real data already arrived
377
+ el('tool-title').textContent = TOOL_TITLES[toolName] || 'Generation';
378
+ if (args && (args.prompt || args.text)) {
379
+ el('prompt').textContent = args.prompt || args.text;
380
+ el('prompt').style.display = '';
381
+ }
382
+ setPhaseChip('Preparing', true);
383
+ if (!el('stage').innerHTML) {
384
+ el('stage').innerHTML = '<div class="k-gen-grid n1"><div class="k-skel video" style="min-height:100px;max-height:140px"></div></div>';
385
+ }
386
+ window.kolbo.notifySize();
387
+ }
388
+
370
389
  /* ---------- wire host events ---------- */
371
390
  window.kolbo.onToolResult(function (result) {
372
391
  var sc = result.structuredContent || structured(result);
373
- if (sc) boot(sc);
392
+ if (sc && (sc.phase || sc.widget)) return boot(sc);
393
+ // Tool errored (or returned plain text): show it instead of a dead blank card.
394
+ var txt = '';
395
+ try { txt = (result.content || []).filter(function (c) { return c.type === 'text'; }).map(function (c) { return c.text; }).join(' '); } catch (e) {}
396
+ if (result.isError || /error|failed/i.test(txt)) {
397
+ renderError((txt || 'The request failed.').slice(0, 300));
398
+ }
374
399
  });
400
+ window.kolbo.onToolInput(function (args) { bootPre(null, args); });
375
401
  window.kolbo.ready(function (ctx) {
376
- // Some hosts deliver the initial result via hostContext.toolInfo; the
377
- // tool-result notification is the primary path.
378
- if (!state && ctx && ctx.toolInfo && ctx.toolInfo.result) {
379
- var sc = ctx.toolInfo.result.structuredContent;
380
- if (sc) boot(sc);
402
+ var info = ctx && ctx.toolInfo;
403
+ if (!state && info) {
404
+ if (info.result && info.result.structuredContent) return boot(info.result.structuredContent);
405
+ bootPre(info.tool && info.tool.name, null);
381
406
  }
382
407
  });
383
408
  `;
@@ -72,7 +72,7 @@ function boot(sc) {
72
72
  a.push('<button class="k-btn ghost" id="btn-copy">Copy text</button>');
73
73
  el('actions').innerHTML = a.join('');
74
74
  Array.prototype.forEach.call(el('actions').querySelectorAll('[data-url]'), function (b) {
75
- b.onclick = function () { window.kolbo.openLink(b.getAttribute('data-url')); };
75
+ b.onclick = function () { window.kolbo.openLink(downloadUrl(b.getAttribute('data-url'))); };
76
76
  });
77
77
  var copyBtn = el('btn-copy');
78
78
  if (copyBtn) copyBtn.onclick = function () {
@@ -96,9 +96,13 @@ function registerModelTools(server, client, options = {}) {
96
96
  'List available AI models on Kolbo. Filter by `type` to narrow to a generation type, and pass `format: "json"` to get the raw model documents (every constraint field, useful for programmatic comparison / cap validation before submitting a generation). Default `format: "text"` returns the human-readable summary.',
97
97
  {
98
98
  type: z.string().optional().describe('Filter by DB type name: "text_to_img", "image_editing", "text_to_video", "img_to_video", "draw_to_video", "video_to_video", "elements", "firstlastgenerations", "lipsync-image", "lipsync-video", "music_gen", "text_to_speech", "text_to_sound", "stt", "text". Legacy aliases also accepted: "image", "image_edit", "video", "video_from_image", "video_from_video", "music", "speech", "sound", "chat", "lipsync" (both lipsync types), "three_d" (all 3D types), "first_last_frame", "transcription". Omit for all models.'),
99
- format: z.enum(['text', 'json']).optional().describe('Output format. "text" (default) returns a human-readable summary with the most-used caps. "json" returns the raw model documents from the API — use this when you need to programmatically verify caps (max_reference_images, max_visual_dna, max_video_duration, supported_aspect_ratios, etc.) before passing an array/value that might exceed a model-specific limit. The JSON form is the source of truth; the text form is a convenience preview.')
99
+ format: z.enum(['text', 'json']).optional().describe('Output format. "text" (default) returns a human-readable summary with the most-used caps. "json" returns the raw model documents from the API — use this when you need to programmatically verify caps (max_reference_images, max_visual_dna, max_video_duration, supported_aspect_ratios, etc.) before passing an array/value that might exceed a model-specific limit. The JSON form is the source of truth; the text form is a convenience preview.'),
100
+ display_catalog: z.boolean().optional().describe('Set true ONLY when the USER explicitly asked to see/browse the available models — it renders a visual model catalog to them. Leave unset for internal lookups (verifying a model name, checking caps before a generation): those should stay invisible to the user.')
100
101
  },
101
- async ({ type, format }) => {
102
+ async ({ type, format, display_catalog }) => {
103
+ // Widget only when the user asked to browse — internal validation
104
+ // lookups were spamming the chat with a full catalog card.
105
+ const showCatalog = display_catalog === true;
102
106
  const path = type ? `/v1/models?type=${encodeURIComponent(type)}` : '/v1/models';
103
107
  const result = await client.get(path);
104
108
 
@@ -108,7 +112,7 @@ function registerModelTools(server, client, options = {}) {
108
112
  // resolution multipliers, supports_* flags, prompt-length limits, etc.).
109
113
  if (format === 'json') {
110
114
  const text = JSON.stringify({ count: result.count, models: result.models }, null, 2);
111
- if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
115
+ if (ui() && showCatalog) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
112
116
  return { content: [{ type: 'text', text }] };
113
117
  }
114
118
 
@@ -279,7 +283,7 @@ function registerModelTools(server, client, options = {}) {
279
283
  }
280
284
 
281
285
  const text = `Available models (${result.count}):\n\n${sections.join('\n\n')}\n\nUse the "identifier" value as the "model" parameter in generate tools. For programmatic cap validation, re-call with format: "json".`;
282
- if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
286
+ if (ui() && showCatalog) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
283
287
  return { content: [{ type: 'text', text }] };
284
288
  }
285
289
  );