@kolbo/mcp 1.28.1 → 1.30.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/README.md +14 -1
- package/package.json +2 -1
- package/skill/GENERATED.md +1 -1
- package/skill/SKILL.md +23 -6
- package/skill/VERSION +1 -1
- package/skill/references/models/seedance.md +7 -7
- package/skill/references/workflows/marketing-studio.md +62 -0
- package/src/apps/bridge.js +124 -0
- package/src/apps/html.js +73 -0
- package/src/apps/index.js +142 -0
- package/src/apps/theme.js +247 -0
- package/src/apps/widgets/catalog.js +73 -0
- package/src/apps/widgets/generation.js +374 -0
- package/src/apps/widgets/mediaGrid.js +129 -0
- package/src/apps/widgets/transcript.js +108 -0
- package/src/index.js +28 -14
- package/src/tools/_shared.js +75 -0
- package/src/tools/generate.js +123 -13
- package/src/tools/media.js +26 -5
- package/src/tools/models.js +71 -13
- package/src/tools/moodboards.js +26 -10
- package/src/tools/music_library.js +44 -3
- package/src/tools/presets.js +30 -11
- package/src/tools/shorts_creator.js +405 -0
- package/src/tools/stock_library.js +60 -3
- package/src/tools/visual_dna.js +27 -10
- package/src/tools/voices.js +23 -7
- package/skill/references/workflows/music-library.md +0 -32
package/src/index.js
CHANGED
|
@@ -72,6 +72,8 @@ const { registerProjectTools } = require('./tools/projects');
|
|
|
72
72
|
const { registerVoiceTools } = require('./tools/voices');
|
|
73
73
|
const { registerMusicLibraryTools } = require('./tools/music_library');
|
|
74
74
|
const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
75
|
+
const { registerShortsCreatorTools } = require('./tools/shorts_creator');
|
|
76
|
+
const { registerApps } = require('./apps');
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
79
|
* Build a fully-configured Kolbo MCP server (all tool groups registered)
|
|
@@ -83,6 +85,11 @@ const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
|
83
85
|
* @param {object} [opts]
|
|
84
86
|
* @param {string} [opts.apiKey] Per-instance Kolbo API key (overrides env).
|
|
85
87
|
* @param {string} [opts.apiBase] API base URL override.
|
|
88
|
+
* @param {boolean} [opts.apps] Force-enable MCP Apps widget results. Set by
|
|
89
|
+
* the kolbo-api remote connector (claude.ai),
|
|
90
|
+
* whose stateless transport hides client
|
|
91
|
+
* capabilities. stdio hosts are auto-detected
|
|
92
|
+
* from the initialize handshake instead.
|
|
86
93
|
* @returns {McpServer} a server ready to `.connect(transport)`.
|
|
87
94
|
*/
|
|
88
95
|
function createServer(opts = {}) {
|
|
@@ -95,20 +102,27 @@ function createServer(opts = {}) {
|
|
|
95
102
|
|
|
96
103
|
// Register all tools. `inlineImages` (off by default) is opt-in: only the
|
|
97
104
|
// remote HTTP host enables it, so stdio clients (Kolbo Code / Desktop / Cursor)
|
|
98
|
-
// keep identical text-URL output.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
// keep identical text-URL output. `apps` gates interactive widget results
|
|
106
|
+
// (MCP Apps) the same way — see src/apps/index.js.
|
|
107
|
+
const toolOptions = { inlineImages: !!opts.inlineImages, apps: !!opts.apps };
|
|
108
|
+
registerGenerateTools(server, client, toolOptions);
|
|
109
|
+
registerModelTools(server, client, toolOptions);
|
|
110
|
+
registerVoiceTools(server, client, toolOptions);
|
|
111
|
+
registerChatTools(server, client, toolOptions);
|
|
112
|
+
registerVisualDnaTools(server, client, toolOptions);
|
|
113
|
+
registerMoodboardTools(server, client, toolOptions);
|
|
114
|
+
registerMediaTools(server, client, toolOptions);
|
|
115
|
+
registerPresetTools(server, client, toolOptions);
|
|
116
|
+
registerAppBuilderTools(server, client, toolOptions);
|
|
117
|
+
registerArtifactTools(server, client, toolOptions);
|
|
118
|
+
registerProjectTools(server, client, toolOptions);
|
|
119
|
+
registerMusicLibraryTools(server, client, toolOptions);
|
|
120
|
+
registerStockLibraryTools(server, client, toolOptions);
|
|
121
|
+
registerShortsCreatorTools(server, client, toolOptions);
|
|
122
|
+
|
|
123
|
+
// MCP Apps widget resources (ui://kolbo/*). Registering resources is inert
|
|
124
|
+
// for text-only hosts — they never fetch them.
|
|
125
|
+
registerApps(server);
|
|
112
126
|
|
|
113
127
|
return server;
|
|
114
128
|
}
|
package/src/tools/_shared.js
CHANGED
|
@@ -291,6 +291,78 @@ async function inlineImageBlocks(urls, opts = {}) {
|
|
|
291
291
|
return blocks.filter(Boolean);
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// ─── MCP Apps generation widget helpers ──────────────────────────────────────
|
|
295
|
+
// When the host renders MCP Apps (claude.ai via the remote connector, Claude
|
|
296
|
+
// Desktop over stdio), generation tools return IMMEDIATELY after submit and the
|
|
297
|
+
// ui://kolbo/generation.html widget takes over: live progress, inline result,
|
|
298
|
+
// action buttons. Text-only hosts never enter this path — their blocking
|
|
299
|
+
// behavior and response bytes are UNCHANGED.
|
|
300
|
+
const { UI, uiResult, appsEnabled, modelIcon } = require('../apps');
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Build the "submitted — widget is live" tool result for a UI host.
|
|
304
|
+
* @param {object} p
|
|
305
|
+
* tool MCP tool name (e.g. 'generate_image')
|
|
306
|
+
* kind 'image' | 'video' | 'audio' | '3d' | 'scenes'
|
|
307
|
+
* gen the submit response ({ generation_id, poll_interval_hint })
|
|
308
|
+
* client KolboClient (for model icon lookup)
|
|
309
|
+
* model, prompt, count, settings, reference_image, estimated_seconds
|
|
310
|
+
* poll_tool widget-side status tool (default 'get_generation_status')
|
|
311
|
+
* status_args args for poll_tool (default { generation_id })
|
|
312
|
+
*/
|
|
313
|
+
async function uiGenerating(p) {
|
|
314
|
+
const icon = await modelIcon(p.client, p.model).catch(() => null);
|
|
315
|
+
const structured = {
|
|
316
|
+
phase: 'generating',
|
|
317
|
+
widget: 'generation',
|
|
318
|
+
kind: p.kind,
|
|
319
|
+
tool: p.tool,
|
|
320
|
+
generation_id: p.gen.generation_id,
|
|
321
|
+
poll_tool: p.poll_tool || 'get_generation_status',
|
|
322
|
+
status_args: p.status_args,
|
|
323
|
+
model: p.model || 'Smart Select',
|
|
324
|
+
model_icon: icon,
|
|
325
|
+
prompt: p.prompt,
|
|
326
|
+
count: p.count || 1,
|
|
327
|
+
settings: p.settings || {},
|
|
328
|
+
reference_image: p.reference_image,
|
|
329
|
+
estimated_seconds: p.estimated_seconds,
|
|
330
|
+
};
|
|
331
|
+
const text = JSON.stringify({
|
|
332
|
+
status: 'submitted',
|
|
333
|
+
generation_id: p.gen.generation_id,
|
|
334
|
+
_widget_note: 'A live Kolbo widget is rendering this generation for the user (progress + final result + action buttons). Tell the user it is generating and the card above will update — do NOT poll in a loop. If you later need the output URLs (e.g. for a follow-up edit), call get_generation_status once with this generation_id.',
|
|
335
|
+
}, null, 2);
|
|
336
|
+
return uiResult(UI.generation, text, structured);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Wrap an already-completed generation result with the widget (used by tools
|
|
341
|
+
* that stay blocking even on UI hosts, e.g. creative director).
|
|
342
|
+
*/
|
|
343
|
+
async function uiCompleted(p, textPayload) {
|
|
344
|
+
const icon = await modelIcon(p.client, p.model).catch(() => null);
|
|
345
|
+
const structured = {
|
|
346
|
+
phase: 'completed',
|
|
347
|
+
widget: 'generation',
|
|
348
|
+
kind: p.kind,
|
|
349
|
+
tool: p.tool,
|
|
350
|
+
model: p.model || 'Smart Select',
|
|
351
|
+
model_icon: icon,
|
|
352
|
+
prompt: p.prompt,
|
|
353
|
+
count: p.count || 1,
|
|
354
|
+
settings: p.settings || {},
|
|
355
|
+
reference_image: p.reference_image,
|
|
356
|
+
urls: p.urls,
|
|
357
|
+
thumbnail_url: p.thumbnail_url,
|
|
358
|
+
title: p.title,
|
|
359
|
+
duration: p.duration,
|
|
360
|
+
scenes: p.scenes,
|
|
361
|
+
credits_used: p.credits_used,
|
|
362
|
+
};
|
|
363
|
+
return uiResult(UI.generation, textPayload, structured);
|
|
364
|
+
}
|
|
365
|
+
|
|
294
366
|
module.exports = {
|
|
295
367
|
MAX_FILE_BYTES,
|
|
296
368
|
VISUAL_DNA_MAX_BYTES,
|
|
@@ -303,4 +375,7 @@ module.exports = {
|
|
|
303
375
|
creditFields,
|
|
304
376
|
projectIdField,
|
|
305
377
|
inlineImageBlocks,
|
|
378
|
+
uiGenerating,
|
|
379
|
+
uiCompleted,
|
|
380
|
+
appsEnabled,
|
|
306
381
|
};
|
package/src/tools/generate.js
CHANGED
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
7
|
const FormData = require('form-data');
|
|
8
8
|
const { pollUntilDone } = require('../polling');
|
|
9
|
-
const { resolveToBuffer, creditFields, projectIdField, inlineImageBlocks } = require('./_shared');
|
|
9
|
+
const { resolveToBuffer, creditFields, projectIdField, inlineImageBlocks, uiGenerating, uiCompleted, appsEnabled } = require('./_shared');
|
|
10
|
+
const { UI, uiResult } = require('../apps');
|
|
10
11
|
|
|
11
12
|
function registerGenerateTools(server, client, options = {}) {
|
|
12
13
|
// Only enabled by hosts that explicitly opt in (the remote HTTP connector).
|
|
13
14
|
// stdio hosts (Kolbo Code, Claude Desktop, Cursor) leave this false, so their
|
|
14
15
|
// tool output is unchanged: a text block with the image URL.
|
|
15
16
|
const inlineImages = !!options.inlineImages;
|
|
17
|
+
// MCP Apps hosts (claude.ai remote connector, Claude Desktop) get an instant
|
|
18
|
+
// "submitted" response + a live ui://kolbo/generation.html widget that polls
|
|
19
|
+
// get_generation_status itself. Text-only hosts never take this branch.
|
|
20
|
+
const ui = () => appsEnabled(server, options);
|
|
16
21
|
// ─── generate_image ────────────────────────────────────────
|
|
17
22
|
server.tool(
|
|
18
23
|
'generate_image',
|
|
@@ -37,6 +42,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
37
42
|
reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, preset_id, project_id
|
|
38
43
|
});
|
|
39
44
|
|
|
45
|
+
if (ui()) return uiGenerating({
|
|
46
|
+
tool: 'generate_image', kind: 'image', gen, client, model, prompt,
|
|
47
|
+
count: num_images, settings: { resolution, aspect_ratio },
|
|
48
|
+
reference_image: reference_images?.[0], estimated_seconds: 25
|
|
49
|
+
});
|
|
50
|
+
|
|
40
51
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
41
52
|
interval: (gen.poll_interval_hint || 3) * 1000,
|
|
42
53
|
timeout: 120000
|
|
@@ -81,6 +92,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
81
92
|
visual_dna_ids, moodboard_id, enable_web_search, resolution, project_id
|
|
82
93
|
});
|
|
83
94
|
|
|
95
|
+
if (ui()) return uiGenerating({
|
|
96
|
+
tool: 'generate_image_edit', kind: 'image', gen, client, model, prompt,
|
|
97
|
+
count: num_images, settings: { resolution, aspect_ratio },
|
|
98
|
+
reference_image: source_images?.[0], estimated_seconds: 40
|
|
99
|
+
});
|
|
100
|
+
|
|
84
101
|
// Multi-source compositing or DNA-anchored edits routinely exceed 120s
|
|
85
102
|
// server-side. Extend the polling window in those cases to avoid forcing
|
|
86
103
|
// every call into the timeout-and-recover path via get_generation_status.
|
|
@@ -146,18 +163,24 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
146
163
|
video_urls: s.video_urls
|
|
147
164
|
}));
|
|
148
165
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
const cdText = JSON.stringify({
|
|
167
|
+
...creditFields(result),
|
|
168
|
+
scenes,
|
|
169
|
+
total_scenes: result.scenes?.length || 0,
|
|
170
|
+
completed_scenes: scenes.length,
|
|
171
|
+
_followup_hint: 'Each scene is a separate asset. If the user asks to edit one scene, find that scene by scene_number/title and pass its image_urls[0] (or video_urls[0]) to generate_image_edit / edit_image / edit_video / generate_video_from_video. Do NOT re-run generate_creative_director unless the user explicitly wants a brand-new set.'
|
|
172
|
+
}, null, 2);
|
|
173
|
+
|
|
174
|
+
// Creative Director polls a dedicated status route the widget can't reach
|
|
175
|
+
// through get_generation_status, so it stays blocking on UI hosts too and
|
|
176
|
+
// renders the completed scene gallery.
|
|
177
|
+
if (ui()) return uiCompleted({
|
|
178
|
+
tool: 'generate_creative_director', kind: 'scenes', client, model, prompt,
|
|
179
|
+
settings: { duration, resolution, mode: workflow_type }, scenes,
|
|
180
|
+
credits_used: creditFields(result).credits_used
|
|
181
|
+
}, cdText);
|
|
182
|
+
|
|
183
|
+
return { content: [{ type: 'text', text: cdText }] };
|
|
161
184
|
}
|
|
162
185
|
);
|
|
163
186
|
|
|
@@ -185,6 +208,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
185
208
|
prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, preset_id, project_id
|
|
186
209
|
});
|
|
187
210
|
|
|
211
|
+
if (ui()) return uiGenerating({
|
|
212
|
+
tool: 'generate_video', kind: 'video', gen, client, model, prompt,
|
|
213
|
+
settings: { duration, resolution, aspect_ratio },
|
|
214
|
+
reference_image: reference_images?.[0], estimated_seconds: 120
|
|
215
|
+
});
|
|
216
|
+
|
|
188
217
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
189
218
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
190
219
|
timeout: 300000
|
|
@@ -227,6 +256,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
227
256
|
image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, project_id
|
|
228
257
|
});
|
|
229
258
|
|
|
259
|
+
if (ui()) return uiGenerating({
|
|
260
|
+
tool: 'generate_video_from_image', kind: 'video', gen, client, model, prompt,
|
|
261
|
+
settings: { duration, resolution, aspect_ratio },
|
|
262
|
+
reference_image: image_url, estimated_seconds: 120
|
|
263
|
+
});
|
|
264
|
+
|
|
230
265
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
231
266
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
232
267
|
timeout: 300000
|
|
@@ -268,6 +303,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
268
303
|
prompt, model, style, instrumental, lyrics, vocal_gender, enhance_prompt, preset_id, project_id
|
|
269
304
|
});
|
|
270
305
|
|
|
306
|
+
if (ui()) return uiGenerating({
|
|
307
|
+
tool: 'generate_music', kind: 'audio', gen, client, model: model || 'Suno', prompt,
|
|
308
|
+
settings: { mode: instrumental ? 'instrumental' : (style || undefined) },
|
|
309
|
+
estimated_seconds: 90
|
|
310
|
+
});
|
|
311
|
+
|
|
271
312
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
272
313
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
273
314
|
timeout: 300000
|
|
@@ -304,6 +345,11 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
304
345
|
text, voice, model, language, project_id
|
|
305
346
|
});
|
|
306
347
|
|
|
348
|
+
if (ui()) return uiGenerating({
|
|
349
|
+
tool: 'generate_speech', kind: 'audio', gen, client, model, prompt: text,
|
|
350
|
+
settings: { voice: voice || 'Rachel' }, estimated_seconds: 20
|
|
351
|
+
});
|
|
352
|
+
|
|
307
353
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
308
354
|
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
309
355
|
timeout: 120000
|
|
@@ -339,6 +385,11 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
339
385
|
prompt, model, duration, prompt_influence, project_id
|
|
340
386
|
});
|
|
341
387
|
|
|
388
|
+
if (ui()) return uiGenerating({
|
|
389
|
+
tool: 'generate_sound', kind: 'audio', gen, client, model, prompt,
|
|
390
|
+
settings: { duration }, estimated_seconds: 20
|
|
391
|
+
});
|
|
392
|
+
|
|
342
393
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
343
394
|
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
344
395
|
timeout: 120000
|
|
@@ -432,6 +483,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
432
483
|
});
|
|
433
484
|
}
|
|
434
485
|
|
|
486
|
+
if (ui()) return uiGenerating({
|
|
487
|
+
tool: 'generate_elements', kind: 'video', gen: startResponse, client, model, prompt,
|
|
488
|
+
settings: { duration, resolution, aspect_ratio },
|
|
489
|
+
reference_image: reference_images?.[0], estimated_seconds: 180
|
|
490
|
+
});
|
|
491
|
+
|
|
435
492
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
436
493
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
437
494
|
timeout: 600000
|
|
@@ -504,6 +561,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
504
561
|
});
|
|
505
562
|
}
|
|
506
563
|
|
|
564
|
+
if (ui()) return uiGenerating({
|
|
565
|
+
tool: 'generate_first_last_frame', kind: 'video', gen: startResponse, client, model, prompt,
|
|
566
|
+
settings: { duration, resolution, aspect_ratio },
|
|
567
|
+
reference_image: first_frame_url || undefined, estimated_seconds: 120
|
|
568
|
+
});
|
|
569
|
+
|
|
507
570
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
508
571
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
509
572
|
timeout: 300000
|
|
@@ -607,6 +670,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
607
670
|
startResponse = await client.postMultipart('/v1/generate/lipsync', form);
|
|
608
671
|
}
|
|
609
672
|
|
|
673
|
+
if (ui()) return uiGenerating({
|
|
674
|
+
tool: 'generate_lipsync', kind: 'video', gen: startResponse, client, model,
|
|
675
|
+
prompt: text_prompt, settings: { mode: 'lipsync' },
|
|
676
|
+
reference_image: sourceIsUrl && !/\.(mp4|mov|webm|mkv|avi|m4v)(\?|$)/i.test(source) ? source : undefined,
|
|
677
|
+
estimated_seconds: 180
|
|
678
|
+
});
|
|
679
|
+
|
|
610
680
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
611
681
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
612
682
|
timeout: 600000
|
|
@@ -699,6 +769,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
699
769
|
startResponse = await client.postMultipart('/v1/generate/video-from-video', form);
|
|
700
770
|
}
|
|
701
771
|
|
|
772
|
+
if (ui()) return uiGenerating({
|
|
773
|
+
tool: 'generate_video_from_video', kind: 'video', gen: startResponse, client, model,
|
|
774
|
+
prompt: prompt || (preset ? `Subtitles preset: ${preset}` : undefined),
|
|
775
|
+
settings: { duration, resolution, aspect_ratio, mode: preset ? 'subtitles' : 'restyle' },
|
|
776
|
+
reference_image: reference_images?.[0], estimated_seconds: 240
|
|
777
|
+
});
|
|
778
|
+
|
|
702
779
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
703
780
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
704
781
|
timeout: 600000
|
|
@@ -742,6 +819,19 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
742
819
|
startResponse = await client.postMultipart('/v1/transcribe', form);
|
|
743
820
|
}
|
|
744
821
|
|
|
822
|
+
if (ui()) {
|
|
823
|
+
return uiResult(UI.transcript, JSON.stringify({
|
|
824
|
+
status: 'submitted',
|
|
825
|
+
generation_id: startResponse.generation_id,
|
|
826
|
+
_widget_note: 'A live Kolbo transcription widget is rendering above — it shows progress, the transcript text, and SRT/TXT download buttons. Tell the user it is transcribing. If you need the transcript text for a follow-up step, call get_generation_status with this generation_id once done.',
|
|
827
|
+
}, null, 2), {
|
|
828
|
+
widget: 'transcript', phase: 'generating',
|
|
829
|
+
generation_id: startResponse.generation_id,
|
|
830
|
+
poll_tool: 'get_generation_status',
|
|
831
|
+
audio_url: isUrl ? source : undefined,
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
745
835
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
746
836
|
interval: (startResponse.poll_interval_hint || 5) * 1000,
|
|
747
837
|
timeout: 1800000 // 30 minutes — long podcasts are a thing
|
|
@@ -797,6 +887,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
797
887
|
project_id
|
|
798
888
|
});
|
|
799
889
|
|
|
890
|
+
if (ui()) return uiGenerating({
|
|
891
|
+
tool: 'generate_3d', kind: '3d', gen: startResponse, client, model, prompt,
|
|
892
|
+
settings: { mode: mode || (reference_images?.length > 1 ? 'multi' : reference_images?.length === 1 ? 'single' : 'text') },
|
|
893
|
+
reference_image: reference_images?.[0], estimated_seconds: 300
|
|
894
|
+
});
|
|
895
|
+
|
|
800
896
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
801
897
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
802
898
|
timeout: 900000 // 15 minutes — 3D generation is slow
|
|
@@ -840,6 +936,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
840
936
|
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt, project_id
|
|
841
937
|
});
|
|
842
938
|
|
|
939
|
+
if (ui()) return uiGenerating({
|
|
940
|
+
tool: 'edit_image', kind: 'image', gen, client, model,
|
|
941
|
+
prompt: prompt || operation,
|
|
942
|
+
settings: { mode: operation, aspect_ratio },
|
|
943
|
+
reference_image: image_url, estimated_seconds: 40
|
|
944
|
+
});
|
|
945
|
+
|
|
843
946
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
844
947
|
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
845
948
|
timeout: 180000
|
|
@@ -890,6 +993,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
890
993
|
image_url, audio_url, duration, mode, project_id
|
|
891
994
|
});
|
|
892
995
|
|
|
996
|
+
if (ui()) return uiGenerating({
|
|
997
|
+
tool: 'edit_video', kind: 'video', gen, client, model,
|
|
998
|
+
prompt: prompt || operation,
|
|
999
|
+
settings: { mode: operation, duration, aspect_ratio },
|
|
1000
|
+
reference_image: image_url, estimated_seconds: 180
|
|
1001
|
+
});
|
|
1002
|
+
|
|
893
1003
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
894
1004
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
895
1005
|
timeout: 600000
|
package/src/tools/media.js
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
7
|
const FormData = require('form-data');
|
|
8
8
|
const { resolveToBuffer } = require('./_shared');
|
|
9
|
+
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
9
10
|
|
|
10
|
-
function registerMediaTools(server, client) {
|
|
11
|
+
function registerMediaTools(server, client, options = {}) {
|
|
12
|
+
const ui = () => appsEnabled(server, options);
|
|
11
13
|
// ─── upload_media ──────────────────────────────────────────
|
|
12
14
|
server.tool(
|
|
13
15
|
'upload_media',
|
|
@@ -72,13 +74,32 @@ function registerMediaTools(server, client) {
|
|
|
72
74
|
const qs = params.toString();
|
|
73
75
|
const result = await client.get(`/v1/media${qs ? '?' + qs : ''}`);
|
|
74
76
|
|
|
77
|
+
const media = result.media || [];
|
|
78
|
+
const pagination = result.pagination || null;
|
|
79
|
+
const text = JSON.stringify({ media, pagination }, null, 2);
|
|
80
|
+
|
|
81
|
+
if (ui()) {
|
|
82
|
+
const items = media.slice(0, 24).map((m) => ({
|
|
83
|
+
id: m.id,
|
|
84
|
+
title: m.filename,
|
|
85
|
+
subtitle: m.media_type + (m.size ? ' · ' + Math.round(m.size / 1024) + 'KB' : ''),
|
|
86
|
+
thumbnail: m.media_type === 'image' ? m.url : (m.thumbnail_url || null),
|
|
87
|
+
media_type: m.media_type,
|
|
88
|
+
url: m.url,
|
|
89
|
+
use_hint: 'Use this media library asset in my next step:\nURL: {URL}\n(id: {ID})'
|
|
90
|
+
}));
|
|
91
|
+
return uiResult(UI.mediaGrid, text, {
|
|
92
|
+
widget: 'media-grid',
|
|
93
|
+
title: 'Media Library',
|
|
94
|
+
items,
|
|
95
|
+
total: pagination && pagination.total != null ? pagination.total : media.length
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
return {
|
|
76
100
|
content: [{
|
|
77
101
|
type: 'text',
|
|
78
|
-
text
|
|
79
|
-
media: result.media || [],
|
|
80
|
-
pagination: result.pagination || null
|
|
81
|
-
}, null, 2)
|
|
102
|
+
text
|
|
82
103
|
}]
|
|
83
104
|
};
|
|
84
105
|
}
|
package/src/tools/models.js
CHANGED
|
@@ -4,8 +4,72 @@
|
|
|
4
4
|
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
5
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
|
+
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// type name → human group label for the catalog widget
|
|
10
|
+
const TYPE_GROUPS = {
|
|
11
|
+
text_to_img: 'Image Generation',
|
|
12
|
+
text_to_video: 'Video Generation',
|
|
13
|
+
img_to_video: 'Video Generation',
|
|
14
|
+
music_gen: 'Music',
|
|
15
|
+
text_to_speech: 'Voice',
|
|
16
|
+
image_editing: 'Image Editing',
|
|
17
|
+
video_to_video: 'Video to Video',
|
|
18
|
+
elements: 'Elements',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function groupNameFor(m) {
|
|
22
|
+
const t = (Array.isArray(m.types) && m.types[0]) || m.type || '';
|
|
23
|
+
if (TYPE_GROUPS[t]) return TYPE_GROUPS[t];
|
|
24
|
+
if (t === 'three_d' || String(t).startsWith('3d_')) return '3D';
|
|
25
|
+
return 'Other';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function modelChips(m) {
|
|
29
|
+
const chips = [];
|
|
30
|
+
if (Array.isArray(m.supported_resolutions) && m.supported_resolutions.length) {
|
|
31
|
+
const highest = [...m.supported_resolutions]
|
|
32
|
+
.sort((a, b) => (parseInt(a, 10) || 0) - (parseInt(b, 10) || 0))
|
|
33
|
+
.pop();
|
|
34
|
+
if (highest) chips.push(String(highest));
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(m.supported_durations) && m.supported_durations.length) {
|
|
37
|
+
const ds = [...m.supported_durations].sort((a, b) => a - b);
|
|
38
|
+
chips.push(ds.length > 1 ? `${ds[0]}-${ds[ds.length - 1]}s` : `${ds[0]}s`);
|
|
39
|
+
}
|
|
40
|
+
if (m.supports_visual_dna) chips.push('DNA');
|
|
41
|
+
if (m.new_model || m.newModel) chips.push('NEW');
|
|
42
|
+
return chips.slice(0, 3);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// structuredContent for ui://kolbo/catalog.html — see src/apps/widgets/catalog.js
|
|
46
|
+
function buildCatalogStructured(models, type) {
|
|
47
|
+
const groups = [];
|
|
48
|
+
const byName = new Map();
|
|
49
|
+
for (const m of models) {
|
|
50
|
+
const name = groupNameFor(m);
|
|
51
|
+
let g = byName.get(name);
|
|
52
|
+
if (!g) { g = { name, models: [] }; byName.set(name, g); groups.push(g); }
|
|
53
|
+
if (g.models.length >= 12) continue; // cap per group (≤60 total across groups)
|
|
54
|
+
g.models.push({
|
|
55
|
+
name: m.name,
|
|
56
|
+
icon: m.avatar
|
|
57
|
+
? (/^https?:\/\//i.test(m.avatar) ? m.avatar : `https://app.kolbo.ai/models_icons/${m.avatar}`)
|
|
58
|
+
: null,
|
|
59
|
+
description: String(m.smartSelect_StrengthsSummary || m.summary || m.description || '').slice(0, 90),
|
|
60
|
+
chips: modelChips(m),
|
|
61
|
+
use_hint: `Generate with the "${m.name}" model — ask me what I want to create first.`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
widget: 'catalog',
|
|
66
|
+
title: 'Kolbo AI Models' + (type ? ' — ' + type : ''),
|
|
67
|
+
groups,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function registerModelTools(server, client, options = {}) {
|
|
72
|
+
const ui = () => appsEnabled(server, options);
|
|
9
73
|
// ─── list_models ───────────────────────────────────────────
|
|
10
74
|
server.tool(
|
|
11
75
|
'list_models',
|
|
@@ -23,12 +87,9 @@ function registerModelTools(server, client) {
|
|
|
23
87
|
// a request lives here (durations, reference caps, audio/video min/max,
|
|
24
88
|
// resolution multipliers, supports_* flags, prompt-length limits, etc.).
|
|
25
89
|
if (format === 'json') {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
text: JSON.stringify({ count: result.count, models: result.models }, null, 2)
|
|
30
|
-
}]
|
|
31
|
-
};
|
|
90
|
+
const text = JSON.stringify({ count: result.count, models: result.models }, null, 2);
|
|
91
|
+
if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
|
|
92
|
+
return { content: [{ type: 'text', text }] };
|
|
32
93
|
}
|
|
33
94
|
|
|
34
95
|
// Split into auto-selectable (has summary) and named-only (no summary)
|
|
@@ -197,12 +258,9 @@ function registerModelTools(server, client) {
|
|
|
197
258
|
sections.push(`Named-only models (${withoutSummary.length}) — only use if the user explicitly requests by name:\n${withoutSummary.map(formatModel).join('\n')}`);
|
|
198
259
|
}
|
|
199
260
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
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".`
|
|
204
|
-
}]
|
|
205
|
-
};
|
|
261
|
+
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".`;
|
|
262
|
+
if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
|
|
263
|
+
return { content: [{ type: 'text', text }] };
|
|
206
264
|
}
|
|
207
265
|
);
|
|
208
266
|
|
package/src/tools/moodboards.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
5
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
|
+
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
7
8
|
|
|
8
|
-
function registerMoodboardTools(server, client) {
|
|
9
|
+
function registerMoodboardTools(server, client, options = {}) {
|
|
10
|
+
const ui = () => appsEnabled(server, options);
|
|
9
11
|
// ─── list_moodboards ───────────────────────────────────────
|
|
10
12
|
server.tool(
|
|
11
13
|
'list_moodboards',
|
|
@@ -18,15 +20,29 @@ function registerMoodboardTools(server, client) {
|
|
|
18
20
|
if (scope && scope !== 'all') params.set('scope', scope);
|
|
19
21
|
const qs = params.toString();
|
|
20
22
|
const result = await client.get(`/v1/moodboards${qs ? '?' + qs : ''}`);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
const moodboards = result.moodboards || [];
|
|
24
|
+
const text = JSON.stringify({
|
|
25
|
+
moodboards,
|
|
26
|
+
count: result.count || 0
|
|
27
|
+
}, null, 2);
|
|
28
|
+
|
|
29
|
+
if (ui()) {
|
|
30
|
+
return uiResult(UI.mediaGrid, text, {
|
|
31
|
+
widget: 'media-grid',
|
|
32
|
+
title: 'Moodboards',
|
|
33
|
+
items: moodboards.slice(0, 24).map(mb => ({
|
|
34
|
+
id: mb.id,
|
|
35
|
+
title: mb.name,
|
|
36
|
+
thumbnail: mb.thumbnail || (Array.isArray(mb.image_urls) ? mb.image_urls[0] : undefined),
|
|
37
|
+
media_type: 'image',
|
|
38
|
+
use_hint: 'Apply moodboard "{TITLE}" (moodboard_id: {ID}) to my next generation.'
|
|
39
|
+
})),
|
|
40
|
+
total: result.count || moodboards.length,
|
|
41
|
+
has_more: moodboards.length > 24
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { content: [{ type: 'text', text }] };
|
|
30
46
|
}
|
|
31
47
|
);
|
|
32
48
|
|