@kolbo/mcp 1.87.5 → 1.87.7
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 +3 -2
- package/src/apps/index.js +7 -1
- package/src/apps/widgets/generation.js +105 -8
- package/src/tools/generate.js +14 -6
- package/src/tools/presets.js +8 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolbo/mcp",
|
|
3
|
-
"version": "1.87.
|
|
3
|
+
"version": "1.87.7",
|
|
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": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"test:blender": "node --test test/blender-tools.test.js",
|
|
21
21
|
"generate-chatgpt-submission": "node scripts/generate-chatgpt-submission.js",
|
|
22
22
|
"check-install": "node scripts/check-install.js",
|
|
23
|
-
"check-doctrine-parity": "node scripts/check-doctrine-parity.js"
|
|
23
|
+
"check-doctrine-parity": "node scripts/check-doctrine-parity.js",
|
|
24
|
+
"gallery": "node scripts/widget-gallery.js --serve"
|
|
24
25
|
},
|
|
25
26
|
"keywords": [
|
|
26
27
|
"kolbo",
|
package/src/apps/index.js
CHANGED
|
@@ -71,7 +71,13 @@ const WIDGET_CSP = {
|
|
|
71
71
|
// Public hosts owned by Kolbo.
|
|
72
72
|
'https://api.kolbo.ai',
|
|
73
73
|
'https://app.kolbo.ai',
|
|
74
|
-
'https://cdn.kolbo.ai',
|
|
74
|
+
'https://cdn.kolbo.ai',
|
|
75
|
+
// Preset thumbnails and library media live on the media-* hosts (prod
|
|
76
|
+
// presets still reference media-dev); without these every preset tile
|
|
77
|
+
// rendered as a black box. All three are on kolbo-api's download allowlist.
|
|
78
|
+
'https://media.kolbo.ai',
|
|
79
|
+
'https://media-staging.kolbo.ai',
|
|
80
|
+
'https://media-dev.kolbo.ai',
|
|
75
81
|
...KOLBO_MEDIA_DOMAINS,
|
|
76
82
|
'https://kolbo-general-media.fra1.digitaloceanspaces.com',
|
|
77
83
|
'https://kolbo-general-media.fra1.cdn.digitaloceanspaces.com',
|
|
@@ -195,6 +195,7 @@ function isListPayload(sc, toolName) {
|
|
|
195
195
|
function boot(sc) {
|
|
196
196
|
if (!sc) return;
|
|
197
197
|
if (isListPayload(sc, sc.tool)) return renderList(sc);
|
|
198
|
+
sc = liveFromStatus(sc);
|
|
198
199
|
state = sc;
|
|
199
200
|
el('tool-title').textContent = TOOL_TITLES[sc.tool] || 'Generation';
|
|
200
201
|
setPrompt(sc.prompt ? promptHTML(sc.prompt) : '', sc.prompt);
|
|
@@ -265,7 +266,13 @@ function displayKind(sc) {
|
|
|
265
266
|
if (/\\.(mp3|wav|m4a|aac|ogg|flac)$/.test(first)) return 'audio';
|
|
266
267
|
var kind = sc.kind;
|
|
267
268
|
if (kind === 'video' || kind === 'scenes' || kind === 'audio' || kind === '3d' || kind === 'model3d') {
|
|
268
|
-
|
|
269
|
+
if (kind !== 'scenes') return kind;
|
|
270
|
+
// A finished prompts[] batch is kind:'scenes' whatever it generated, so an
|
|
271
|
+
// image batch wore a "video" chip. Read the media the scenes actually hold.
|
|
272
|
+
var s0 = sc.scenes && sc.scenes[0];
|
|
273
|
+
if (s0 && s0.video_urls && s0.video_urls.length) return 'video';
|
|
274
|
+
if (s0 && s0.image_urls && s0.image_urls.length) return 'image';
|
|
275
|
+
return 'video';
|
|
269
276
|
}
|
|
270
277
|
var tool = sc.tool || '';
|
|
271
278
|
if (/video|elements|lipsync|first_last_frame/.test(tool)) return 'video';
|
|
@@ -486,6 +493,51 @@ function capAt(sc, i) {
|
|
|
486
493
|
return sc._caps[i] || sc.prompts[i];
|
|
487
494
|
}
|
|
488
495
|
|
|
496
|
+
// A completed TOOL CALL is not a completed GENERATION. When a generate_* call
|
|
497
|
+
// outlives its poll window on a text host (Claude desktop over stdio), the
|
|
498
|
+
// server answers with a kind:'status' grid whose items are still processing,
|
|
499
|
+
// and that used to render as a dead card of "processing" badges that never
|
|
500
|
+
// polled again: the user watched skeletons that would never fill while the
|
|
501
|
+
// images landed in the library. Turn any such payload back into a LIVE card,
|
|
502
|
+
// the same generating/polling path a fresh submit takes, cells filled per id.
|
|
503
|
+
function isTerminal(s) { return s === 'completed' || s === 'failed' || s === 'cancelled'; }
|
|
504
|
+
function liveFromStatus(sc) {
|
|
505
|
+
if (!sc || sc.phase !== 'completed' || !Array.isArray(sc.items) || !sc.items.length) return sc;
|
|
506
|
+
var ids = sc.items.map(function (it) { return it && it.id; });
|
|
507
|
+
if (ids.some(function (id) { return !id; })) return sc;
|
|
508
|
+
if (!sc.items.some(function (it) { return !isTerminal(it.state); })) return sc;
|
|
509
|
+
var kind = sc.kind === 'status' || !sc.kind ? kindFromTool(sc.tool, {}) : sc.kind;
|
|
510
|
+
var live = Object.assign({}, sc, {
|
|
511
|
+
phase: 'generating', kind: kind, items: undefined,
|
|
512
|
+
count: ids.length, poll_tool: 'get_generation_status', generation_id: ids[0]
|
|
513
|
+
});
|
|
514
|
+
if (ids.length > 1) {
|
|
515
|
+
live.generation_ids = ids;
|
|
516
|
+
live.prompts = sc.items.map(function (it) { return it.title || ''; });
|
|
517
|
+
live.status_args = { generation_ids: ids, wait: true };
|
|
518
|
+
} else {
|
|
519
|
+
live.status_args = { generation_id: ids[0], wait: true };
|
|
520
|
+
}
|
|
521
|
+
return live;
|
|
522
|
+
}
|
|
523
|
+
// Multi-id status responses carry their per-generation results as items[] in
|
|
524
|
+
// structuredContent (the shape the status grid renders) and as generations[]
|
|
525
|
+
// only in the text, and structured() reads structuredContent first, so a live
|
|
526
|
+
// batch card never saw generations[] on a host that ships structuredContent.
|
|
527
|
+
function itemsToGens(items) {
|
|
528
|
+
if (!Array.isArray(items)) return null;
|
|
529
|
+
return items.map(function (it) {
|
|
530
|
+
return {
|
|
531
|
+
generation_id: it.id, state: it.state, credits_used: it.credits_used,
|
|
532
|
+
result: {
|
|
533
|
+
urls: Array.isArray(it.urls) ? it.urls : (it.url ? [it.url] : []),
|
|
534
|
+
model: it.model, model_name: it.model_name, model_icon: it.model_icon,
|
|
535
|
+
reference_images: it.reference_images
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
489
541
|
function renderGenerating(sc) {
|
|
490
542
|
setPhaseChip('Generating', true);
|
|
491
543
|
var n = Math.min(sc.count || 1, isBatch(sc) ? 8 : 4);
|
|
@@ -670,9 +722,14 @@ function poll(sc) {
|
|
|
670
722
|
if (++pollErrors >= MAX_POLL_ERRORS) return renderTrackingIssue(st.error || 'Tracking paused. The generation may still be running.');
|
|
671
723
|
return schedulePoll(sc);
|
|
672
724
|
}
|
|
673
|
-
// Batch (prompts[] fan-out): multi-id status shape { all_done, generations[] }
|
|
674
|
-
|
|
675
|
-
|
|
725
|
+
// Batch (prompts[] fan-out): multi-id status shape { all_done, generations[] },
|
|
726
|
+
// or the same data as items[] when it came via structuredContent.
|
|
727
|
+
var gens = Array.isArray(st.generations) ? st.generations : itemsToGens(st.items);
|
|
728
|
+
if (isBatch(sc) && gens) {
|
|
729
|
+
return handleBatchStatus(sc, {
|
|
730
|
+
all_done: st.all_done != null ? st.all_done : gens.every(function (g) { return isTerminal(g.state); }),
|
|
731
|
+
generations: gens
|
|
732
|
+
});
|
|
676
733
|
}
|
|
677
734
|
if (stateName === 'completed') {
|
|
678
735
|
pollErrors = 0;
|
|
@@ -724,8 +781,13 @@ function poll(sc) {
|
|
|
724
781
|
function handleBatchStatus(sc, st) {
|
|
725
782
|
pollErrors = 0;
|
|
726
783
|
var gens = st.generations || [];
|
|
784
|
+
// Cells are laid out in submit order; match results by id, not position.
|
|
785
|
+
var cellIndex = function (g, i) {
|
|
786
|
+
var idx = sc.generation_ids ? sc.generation_ids.indexOf(g.generation_id) : -1;
|
|
787
|
+
return idx < 0 ? i : idx;
|
|
788
|
+
};
|
|
727
789
|
gens.forEach(function (g, i) {
|
|
728
|
-
if (g.state === 'completed') fillBatchCell(sc, i, g);
|
|
790
|
+
if (g.state === 'completed') fillBatchCell(sc, cellIndex(g, i), g);
|
|
729
791
|
});
|
|
730
792
|
if (!st.all_done) return schedulePoll(sc);
|
|
731
793
|
|
|
@@ -753,8 +815,8 @@ function handleBatchStatus(sc, st) {
|
|
|
753
815
|
resolved.reference_images = r.reference_images;
|
|
754
816
|
}
|
|
755
817
|
scenes.push({
|
|
756
|
-
scene_number: i + 1,
|
|
757
|
-
title: capAt(sc, i),
|
|
818
|
+
scene_number: cellIndex(g, i) + 1,
|
|
819
|
+
title: capAt(sc, cellIndex(g, i)),
|
|
758
820
|
image_urls: sc.kind === 'video' ? [] : urls,
|
|
759
821
|
video_urls: sc.kind === 'video' ? urls : []
|
|
760
822
|
});
|
|
@@ -1305,6 +1367,41 @@ function kindFromTool(tool, sc) {
|
|
|
1305
1367
|
|
|
1306
1368
|
// Recover a successful legacy/text result when a host mounted the iframe from
|
|
1307
1369
|
// declaration metadata but the server did not recognize its Apps capability.
|
|
1370
|
+
// Every generate_* tool on a text host answers a poll-window timeout with the
|
|
1371
|
+
// same plain shape from pollOrTimedOut(): { state:'processing', generation_id,
|
|
1372
|
+
// _timed_out }. No phase, no widget, no urls, so nothing above recognised it
|
|
1373
|
+
// and the card stayed on its pre-render skeleton forever while the generation
|
|
1374
|
+
// finished in the library. Rebuild the live card from the tool INPUT (same
|
|
1375
|
+
// source completedFromPlain uses) and let it poll to completion.
|
|
1376
|
+
function liveFromTimedOut(sc) {
|
|
1377
|
+
if (!sc || !sc.generation_id || isTerminal(sc.state) || Array.isArray(sc.urls)) return null;
|
|
1378
|
+
var ids = Array.isArray(sc.generation_ids) && sc.generation_ids.length > 1 ? sc.generation_ids : null;
|
|
1379
|
+
var live = {
|
|
1380
|
+
widget: 'generation',
|
|
1381
|
+
phase: 'generating',
|
|
1382
|
+
tool: originTool,
|
|
1383
|
+
kind: kindFromTool(originTool, sc),
|
|
1384
|
+
prompt: originArgs.prompt || originArgs.text || '',
|
|
1385
|
+
model: sc.model || originArgs.model,
|
|
1386
|
+
settings: {
|
|
1387
|
+
duration: originArgs.duration,
|
|
1388
|
+
resolution: originArgs.resolution,
|
|
1389
|
+
aspect_ratio: originArgs.aspect_ratio,
|
|
1390
|
+
quality: originArgs.quality,
|
|
1391
|
+
visual_dna_ids: originArgs.visual_dna_ids,
|
|
1392
|
+
moodboard_id: originArgs.moodboard_id
|
|
1393
|
+
},
|
|
1394
|
+
count: ids ? ids.length : (originArgs.num_images || 1),
|
|
1395
|
+
generation_id: sc.generation_id,
|
|
1396
|
+
poll_tool: 'get_generation_status',
|
|
1397
|
+
status_args: ids ? { generation_ids: ids, wait: true } : { generation_id: sc.generation_id, wait: true },
|
|
1398
|
+
session_id: sc.session_id || originArgs.session_id,
|
|
1399
|
+
project_id: sc.project_id || originArgs.project_id
|
|
1400
|
+
};
|
|
1401
|
+
if (ids) { live.generation_ids = ids; live.prompts = originArgs.prompts || []; }
|
|
1402
|
+
return live;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1308
1405
|
function completedFromPlain(sc) {
|
|
1309
1406
|
if (!sc || (!Array.isArray(sc.urls) && !Array.isArray(sc.scenes))) return null;
|
|
1310
1407
|
return Object.assign({}, sc, {
|
|
@@ -1333,7 +1430,7 @@ window.kolbo.onToolResult(function (result) {
|
|
|
1333
1430
|
var list = listPayload(sc);
|
|
1334
1431
|
if (list) return renderList(list);
|
|
1335
1432
|
if (sc && (sc.phase || sc.widget)) return boot(sc);
|
|
1336
|
-
var recovered = completedFromPlain(sc);
|
|
1433
|
+
var recovered = completedFromPlain(sc) || liveFromTimedOut(sc);
|
|
1337
1434
|
if (recovered) return boot(recovered);
|
|
1338
1435
|
// Tool errored (or returned plain text): show it instead of a dead blank card.
|
|
1339
1436
|
var txt = '';
|
package/src/tools/generate.js
CHANGED
|
@@ -88,7 +88,7 @@ async function submitBatch(rawItems, submitOne) {
|
|
|
88
88
|
// multi-id branch, same fix: always ship structuredContent via the shared
|
|
89
89
|
// kind:'status' grid, which already renders any mix of completed/processing
|
|
90
90
|
// items correctly.
|
|
91
|
-
async function pollBatch(client, batch, { interval, timeout }, toolName) {
|
|
91
|
+
async function pollBatch(client, batch, { interval, timeout }, toolName, submittedModel) {
|
|
92
92
|
const polls = await Promise.all(batch.ids.map((id) => pollOrTimedOut(client, id, { interval, timeout })));
|
|
93
93
|
const generations = polls.map((p, i) => p.timedOut
|
|
94
94
|
? { 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.' }
|
|
@@ -103,7 +103,7 @@ async function pollBatch(client, batch, { interval, timeout }, toolName) {
|
|
|
103
103
|
const modelsRan = [...new Set(polls.filter((p) => !p.timedOut).map((p) => p.result.result && p.result.result.model).filter(Boolean))];
|
|
104
104
|
return uiCompleted({
|
|
105
105
|
tool: toolName, kind: 'status', client,
|
|
106
|
-
model: modelsRan.length === 1 ? modelsRan[0] : 'Generations',
|
|
106
|
+
model: modelsRan.length === 1 ? modelsRan[0] : (submittedModel || 'Generations'),
|
|
107
107
|
gen: { generation_id: batch.ids[0], session_id: batch.ok[0].gen.session_id },
|
|
108
108
|
settings: {},
|
|
109
109
|
items: generations.map(g => ({
|
|
@@ -111,6 +111,8 @@ async function pollBatch(client, batch, { interval, timeout }, toolName) {
|
|
|
111
111
|
state: g.status,
|
|
112
112
|
title: g.prompt,
|
|
113
113
|
url: Array.isArray(g.urls) ? g.urls[0] : undefined,
|
|
114
|
+
urls: Array.isArray(g.urls) ? g.urls : undefined,
|
|
115
|
+
credits_used: g.credits_used,
|
|
114
116
|
})),
|
|
115
117
|
}, text);
|
|
116
118
|
}
|
|
@@ -292,7 +294,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
292
294
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
293
295
|
reference_images
|
|
294
296
|
});
|
|
295
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image');
|
|
297
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image', model);
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
const gen = await client.post('/v1/generate/image', { ...shared, prompt, num_images });
|
|
@@ -381,7 +383,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
381
383
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
382
384
|
reference_images: [...(source_images || []), ...(reference_images || [])]
|
|
383
385
|
});
|
|
384
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image_edit');
|
|
386
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image_edit', model);
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
const gen = await client.post('/v1/generate/image-edit', { ...shared, prompt, num_images });
|
|
@@ -634,7 +636,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
634
636
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
635
637
|
reference_images
|
|
636
638
|
});
|
|
637
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 150000 }, 'generate_video');
|
|
639
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 150000 }, 'generate_video', model);
|
|
638
640
|
}
|
|
639
641
|
|
|
640
642
|
const gen = await client.post('/v1/generate/video', { ...shared, prompt });
|
|
@@ -724,7 +726,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
724
726
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
725
727
|
reference_images: items.map((item) => item.image_url)
|
|
726
728
|
});
|
|
727
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 }, 'generate_video_from_image');
|
|
729
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 }, 'generate_video_from_image', model);
|
|
728
730
|
}
|
|
729
731
|
|
|
730
732
|
const gen = await client.post('/v1/generate/video/from-image', { ...shared, image_url, prompt });
|
|
@@ -1249,6 +1251,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1249
1251
|
state: r.state,
|
|
1250
1252
|
title: res.prompt_used || res.prompt || undefined,
|
|
1251
1253
|
url: Array.isArray(res.urls) ? res.urls[0] : undefined,
|
|
1254
|
+
// A live batch card reads these from structuredContent (its only
|
|
1255
|
+
// view of this response) to fill each cell and repaint its chips.
|
|
1256
|
+
urls: Array.isArray(res.urls) ? res.urls : undefined,
|
|
1257
|
+
credits_used: creditFields(r).credits_used,
|
|
1258
|
+
model: res.model, model_name: res.model_name, model_icon: res.model_icon,
|
|
1259
|
+
reference_images: res.reference_images,
|
|
1252
1260
|
};
|
|
1253
1261
|
}),
|
|
1254
1262
|
}, text, extraContent);
|
package/src/tools/presets.js
CHANGED
|
@@ -46,10 +46,14 @@ function registerPresetTools(server, client, options = {}) {
|
|
|
46
46
|
|
|
47
47
|
if (lookup) return { content: [{ type: 'text', text }] };
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// 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.
|
|
53
|
+
return uiResult(UI.mediaGrid, text, {
|
|
54
|
+
widget: 'mediaGrid',
|
|
51
55
|
title: 'Presets' + (type ? ' — ' + type : ''),
|
|
52
|
-
items: presets.slice(0,
|
|
56
|
+
items: presets.slice(0, 300).map(p => ({
|
|
53
57
|
id: p.id,
|
|
54
58
|
title: p.name,
|
|
55
59
|
subtitle: p.category,
|
|
@@ -60,7 +64,7 @@ function registerPresetTools(server, client, options = {}) {
|
|
|
60
64
|
use_hint: 'Use preset "{TITLE}" (preset_id: {ID}) for my next generation — ask me for the prompt.'
|
|
61
65
|
})),
|
|
62
66
|
total: result.count || presets.length,
|
|
63
|
-
has_more: presets.length >
|
|
67
|
+
has_more: presets.length > 300
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
);
|