@kolbo/mcp 1.110.1 → 1.110.3
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 +1 -1
- package/src/apps/index.js +11 -4
- package/src/apps/widgets/generation.js +19 -4
- package/src/tools/agents.js +19 -8
- package/src/tools/audio_stems.js +18 -16
- package/src/tools/chat.js +5 -5
- package/src/tools/docs.js +5 -3
- package/src/tools/generate.js +1 -1
- package/src/tools/projects.js +1 -1
- package/src/tools/stock_library.js +11 -9
package/package.json
CHANGED
package/src/apps/index.js
CHANGED
|
@@ -362,7 +362,7 @@ async function modelInfoMap(client) {
|
|
|
362
362
|
// model the user actually knows. draft_enhance finalizes a Seedance 2.5 Draft;
|
|
363
363
|
// its engine id is internal, so the card showed "Smart Select" / a raw id.
|
|
364
364
|
const DISPLAY_ALIASES = {
|
|
365
|
-
'flux-3-draft-enhance': { base: 'seedance-2-5', name: 'Seedance 2.5 Draft Enhance' },
|
|
365
|
+
'flux-3-draft-enhance': { base: 'seedance-2-5', name: 'Seedance 2.5 Draft Enhance' },
|
|
366
366
|
'video-draft-enhance': { base: 'seedance-2-5', name: 'Seedance 2.5 Draft Enhance' },
|
|
367
367
|
};
|
|
368
368
|
// Operations whose engine is picked server-side when no model is passed.
|
|
@@ -573,11 +573,17 @@ async function modelIcon(client, modelName) {
|
|
|
573
573
|
// `key.replace(/\s+/g, '-')`) is why "flux-2-flash" never found "flux-2/flash".
|
|
574
574
|
const normId = (s) => String(s || '').toLowerCase().replace(/[\s._/-]+/g, '');
|
|
575
575
|
|
|
576
|
-
//
|
|
577
|
-
//
|
|
576
|
+
// "Let Kolbo pick" spellings. They are never typos, so they must never be
|
|
577
|
+
// "corrected" or reported as unknown. The API only accepts the exact spellings
|
|
578
|
+
// in API_AUTO_ALIASES; it answers any other explicit model with 400
|
|
579
|
+
// MODEL_NOT_FOUND, so the rest ("Smart Select", "default", "none") are sent
|
|
580
|
+
// as 'auto'.
|
|
578
581
|
const AUTO_ALIASES = new Set([
|
|
579
582
|
'auto', 'autoselect', 'smartselect', 'kolbosmartselectrouter', 'default', 'none',
|
|
580
583
|
]);
|
|
584
|
+
const API_AUTO_ALIASES = new Set([
|
|
585
|
+
'smart-select', 'smart_select', 'smartselect', 'auto', 'auto-select', 'kolbo_smart_select_router',
|
|
586
|
+
]);
|
|
581
587
|
|
|
582
588
|
/**
|
|
583
589
|
* Narrow several models that answer to the same string down to one identifier.
|
|
@@ -649,7 +655,8 @@ async function canonicalModelId(client, input, type) {
|
|
|
649
655
|
if (!input || typeof input !== 'string') return input;
|
|
650
656
|
const key = input.toLowerCase().trim();
|
|
651
657
|
const want = normId(key);
|
|
652
|
-
if (!want
|
|
658
|
+
if (!want) return input;
|
|
659
|
+
if (AUTO_ALIASES.has(want)) return API_AUTO_ALIASES.has(key) ? input : 'auto';
|
|
653
660
|
// Exact native-workflow ids pass through: the near-miss check below would reject an
|
|
654
661
|
// unpublished or cached-out 'kolbo-*' id as unknown.
|
|
655
662
|
if (NATIVE_WORKFLOW_MODEL_IDS.has(key)) return input;
|
|
@@ -393,6 +393,14 @@ function referenceHTML(sc) {
|
|
|
393
393
|
+ peek + ' onerror="this.style.display=\\'none\\'">';
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
|
+
// Local files the caller passed by path: the iframe cannot load them and they
|
|
397
|
+
// are only uploaded during the run, so name them instead of showing nothing.
|
|
398
|
+
// The finished card carries the uploaded thumbnails and drops these.
|
|
399
|
+
if (sc.phase !== 'completed' && Array.isArray(sc.local_references)) {
|
|
400
|
+
sc.local_references.slice(0, 6).forEach(function (name) {
|
|
401
|
+
h += '<span class="k-chip" title="' + esc(name) + '">' + ICONS.image + ' ' + esc(String(name).slice(0, 24)) + '</span>';
|
|
402
|
+
});
|
|
403
|
+
}
|
|
396
404
|
return h;
|
|
397
405
|
}
|
|
398
406
|
// Which characters/looks are locked into this generation — by face and name,
|
|
@@ -1304,7 +1312,8 @@ function openPromptRow(placeholder, onSend) {
|
|
|
1304
1312
|
// like the result path, so a .mp4 handed to the files arg renders as video.
|
|
1305
1313
|
var PRE_IMAGE_KEYS = ['source_images', 'reference_images', 'image_url', 'mask_image_url',
|
|
1306
1314
|
'additional_images', 'first_frame', 'last_frame', 'seed_reference_image_url',
|
|
1307
|
-
'elements', 'files', 'keyframes', 'source'
|
|
1315
|
+
'elements', 'files', 'keyframes', 'source', 'first_frame_url', 'last_frame_url',
|
|
1316
|
+
'texture_image_url', 'background_reference_image'];
|
|
1308
1317
|
var PRE_VIDEO_KEYS = ['source_video', 'video_url', 'mask_video_url', 'reference_videos'];
|
|
1309
1318
|
var PRE_AUDIO_KEYS = ['audio', 'audio_url', 'reference_audio_urls', 'seed_reference_audio_urls'];
|
|
1310
1319
|
|
|
@@ -1319,11 +1328,16 @@ var PRE_AUDIO_KEYS = ['audio', 'audio_url', 'reference_audio_urls', 'seed_refere
|
|
|
1319
1328
|
// this) — rendering the raw identifier the caller passed would put an id on the
|
|
1320
1329
|
// card, which is never allowed.
|
|
1321
1330
|
function preRefSc(toolName, a) {
|
|
1322
|
-
var img = [], vid = [], aud = [];
|
|
1331
|
+
var img = [], vid = [], aud = [], local = [];
|
|
1323
1332
|
var take = function (v, bucket) {
|
|
1324
1333
|
if (typeof v === 'string') {
|
|
1325
|
-
// http(s) only — an absolute local path is not loadable from the iframe
|
|
1326
|
-
|
|
1334
|
+
// http(s) only — an absolute local path is not loadable from the iframe,
|
|
1335
|
+
// so it is shown by file name instead (see referenceHTML).
|
|
1336
|
+
if (/^https?:/i.test(v)) { if (bucket.indexOf(v) < 0) bucket.push(v); }
|
|
1337
|
+
else if (/^(?:[a-z]:[\\\\/]|\\/|~[\\\\/]|file:)/i.test(v)) {
|
|
1338
|
+
var name = v.split(/[\\\\/]/).pop() || v;
|
|
1339
|
+
if (local.indexOf(name) < 0) local.push(name);
|
|
1340
|
+
}
|
|
1327
1341
|
return;
|
|
1328
1342
|
}
|
|
1329
1343
|
if (Array.isArray(v)) {
|
|
@@ -1343,6 +1357,7 @@ function preRefSc(toolName, a) {
|
|
|
1343
1357
|
reference_images: img,
|
|
1344
1358
|
reference_videos: vid,
|
|
1345
1359
|
reference_audio: aud,
|
|
1360
|
+
local_references: local,
|
|
1346
1361
|
settings: {
|
|
1347
1362
|
duration: a.duration,
|
|
1348
1363
|
resolution: a.draft === true ? '480p-draft' : a.draft === false ? String(a.resolution || '').replace(/-draft$/i, '') : a.resolution,
|
package/src/tools/agents.js
CHANGED
|
@@ -35,14 +35,23 @@ function registerSkillCrud(server, client, { noun, idArg, suffix, plural }) {
|
|
|
35
35
|
server.tool(
|
|
36
36
|
`list_${plural}`,
|
|
37
37
|
`List the user's custom ${plural} (personal + any platform/org preset ${plural} visible to them). A ${noun} is a reusable, named persona for the chat tool — its \`description\` is the system instruction the model adopts. Use this to resolve a ${noun} NAME the user mentioned into its id, or to show what ${plural} exist. Returns id, name, description, emoji, is_global. Personal ${plural} (is_global:false) are editable/deletable; platform presets are not.${legacyNote}`,
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
{
|
|
39
|
+
search: z.string().optional().describe('Optional case-insensitive name filter.'),
|
|
40
|
+
page: z.number().int().min(1).optional().describe('Page number, 1-indexed. Default: 1. The response `pagination.has_more` says whether another page exists.'),
|
|
41
|
+
limit: z.number().int().min(1).max(100).optional().describe('Results per page, max 100. Default: 50.')
|
|
42
|
+
},
|
|
43
|
+
async ({ search, page, limit }) => {
|
|
44
|
+
const params = new URLSearchParams();
|
|
45
|
+
if (search) params.set('search', search);
|
|
46
|
+
if (page) params.set('page', String(page));
|
|
47
|
+
if (limit) params.set('limit', String(limit));
|
|
48
|
+
const qs = params.toString();
|
|
49
|
+
const result = await client.get(`/v1/skills${qs ? '?' + qs : ''}`);
|
|
42
50
|
const agents = result.agents || [];
|
|
43
51
|
const text = JSON.stringify({
|
|
44
52
|
agents,
|
|
45
|
-
count: result.count || agents.length
|
|
53
|
+
count: result.count || agents.length,
|
|
54
|
+
...(result.pagination ? { pagination: result.pagination } : {})
|
|
46
55
|
}, null, 2);
|
|
47
56
|
|
|
48
57
|
return listResult(text, {
|
|
@@ -55,7 +64,9 @@ function registerSkillCrud(server, client, { noun, idArg, suffix, plural }) {
|
|
|
55
64
|
badge: a.is_global ? 'preset' : null,
|
|
56
65
|
use_hint: `Use my "{TITLE}" ${noun} (${idArg}: {ID}) for this conversation.`
|
|
57
66
|
})),
|
|
58
|
-
total: agents.length
|
|
67
|
+
total: agents.length,
|
|
68
|
+
page_tool: `list_${plural}`,
|
|
69
|
+
next_args: result.pagination?.has_more ? { search, page: (page || 1) + 1, limit } : undefined
|
|
59
70
|
});
|
|
60
71
|
}
|
|
61
72
|
);
|
|
@@ -66,7 +77,7 @@ function registerSkillCrud(server, client, { noun, idArg, suffix, plural }) {
|
|
|
66
77
|
`Create a reusable custom ${noun} (a named persona for the chat tool). The \`description\` IS the ${noun}'s system instruction — write it as the persona + behavior you want ("You are a senior creative director. Turn any brief into a structured shot list…"). Use when the user wants a persistent, reusable assistant ("make me a creative-director ${noun}", "set up a support-triage bot"). For a ONE-OFF persona on a single conversation, pass \`system_prompt\` to chat_send_message instead — no need to create a ${noun}. Plan limits apply (server rejects when the ${noun} cap is reached).${legacyNote}`,
|
|
67
78
|
{
|
|
68
79
|
name: z.string().optional().describe(`${noun[0].toUpperCase() + noun.slice(1)} name. If omitted, a name is generated from the description.`),
|
|
69
|
-
description: z.string().describe(`The ${noun} persona + instructions (
|
|
80
|
+
description: z.string().describe(`The ${noun} persona + instructions (no API length cap; keep it under 8000 characters if it should stay editable in the Kolbo app). This becomes the system prompt the model adopts in every conversation that uses the ${noun}.`),
|
|
70
81
|
emoji: z.string().optional().describe('Optional emoji avatar (auto-picked if omitted).'),
|
|
71
82
|
thumbnail: z.string().optional().describe('Optional thumbnail image URL.')
|
|
72
83
|
},
|
|
@@ -87,7 +98,7 @@ function registerSkillCrud(server, client, { noun, idArg, suffix, plural }) {
|
|
|
87
98
|
{
|
|
88
99
|
[idArg]: z.string().describe(idDesc),
|
|
89
100
|
name: z.string().optional().describe('New name.'),
|
|
90
|
-
description: z.string().optional().describe('New persona/instructions (replaces the old description;
|
|
101
|
+
description: z.string().optional().describe('New persona/instructions (replaces the old description; no API length cap, keep it under 8000 characters if it should stay editable in the Kolbo app).'),
|
|
91
102
|
emoji: z.string().optional().describe('New emoji avatar.'),
|
|
92
103
|
thumbnail: z.string().optional().describe('New thumbnail image URL.')
|
|
93
104
|
},
|
package/src/tools/audio_stems.js
CHANGED
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Audio stem separation — Kolbo's own pipeline, not a passthrough to one vendor.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* "
|
|
9
|
-
* ambience land there at full volume. When no words are actually spoken, that mask is folded
|
|
10
|
-
* back into Effects rather than handed over mislabeled as Dialogue. That gate is why these
|
|
11
|
-
* tools exist as their own surface instead of a `separate` operation on edit_video.
|
|
6
|
+
* The backend runs prompted source separation in sequence (speech, then music, then
|
|
7
|
+
* ambience), each pass working on the previous residual, so the layers stay disjoint.
|
|
8
|
+
* mode="dialogue" stops after the speech pass (Dialogue + M&E only).
|
|
12
9
|
*
|
|
13
10
|
* These three routes answer INLINE — there is no job id and nothing to poll. Each call holds
|
|
14
11
|
* its connection for the whole run, so they carry an explicit long request timeout rather
|
|
@@ -57,13 +54,13 @@ function registerAudioStemTools(server, client) {
|
|
|
57
54
|
// ─── separate_audio_stems ─────────────────────────────────────
|
|
58
55
|
server.tool(
|
|
59
56
|
'separate_audio_stems',
|
|
60
|
-
'Split a soundtrack into separate audio layers: Dialogue, Music, Effects, and a '
|
|
57
|
+
'Split a soundtrack into separate audio layers: Dialogue, Music, Ambience, Effects, and a '
|
|
61
58
|
+ '"without dialogue" (M&E) track — Kolbo\'s own separation pipeline, built for dubbing, '
|
|
62
59
|
+ 'localisation, podcast cleanup and re-scoring. Use this whenever someone wants to '
|
|
63
60
|
+ 'remove/isolate speech, strip narration, mute the music, get a clean instrumental bed, '
|
|
64
|
-
+ 'or hand an editor stems.
|
|
65
|
-
+ '
|
|
66
|
-
+ '
|
|
61
|
+
+ 'or hand an editor stems. Speech, music and ambience are pulled out one after another, '
|
|
62
|
+
+ 'each from what the previous pass left, so the layers do not overlap. Pass '
|
|
63
|
+
+ 'mode="dialogue" when only the voice/background split is needed. Costs 5 credits. Runs inline and '
|
|
67
64
|
+ 'returns the finished layer URLs (usually 20–90 seconds); there is nothing to poll. '
|
|
68
65
|
+ 'Sources up to 15 minutes long — trim longer files first. NOTE: there is no cache on the '
|
|
69
66
|
+ 'URL form, so calling it twice on the same file separates twice and bills twice.',
|
|
@@ -73,9 +70,12 @@ function registerAudioStemTools(server, client) {
|
|
|
73
70
|
generation_id: z.string().optional()
|
|
74
71
|
.describe('ID of a Kolbo video generation to separate instead of a URL. Layers are also saved onto that generation, so a repeat call is free.'),
|
|
75
72
|
project_id: projectIdField,
|
|
73
|
+
mode: z.enum(['full', 'dialogue']).optional()
|
|
74
|
+
.describe('"full" (default): Dialogue, Music, Ambience, Effects and M&E. "dialogue": one speech pass returning only Dialogue and the without-dialogue (M&E) track.'),
|
|
76
75
|
},
|
|
77
|
-
async ({ audio_url, generation_id, project_id }) => {
|
|
76
|
+
async ({ audio_url, generation_id, project_id, mode }) => {
|
|
78
77
|
const body = buildBody({ audio_url, generation_id, project_id }, 'separate_audio_stems');
|
|
78
|
+
if (mode) body.mode = mode;
|
|
79
79
|
const result = await client.post('/v1/audio/separate', body, { timeoutMs: STEMS_TIMEOUT_MS });
|
|
80
80
|
return textResult({
|
|
81
81
|
...summarize(result),
|
|
@@ -86,11 +86,12 @@ function registerAudioStemTools(server, client) {
|
|
|
86
86
|
layer_guide: {
|
|
87
87
|
dialogue: 'Speech only.',
|
|
88
88
|
music: 'Score / song bed. Absent when the clip has no music.',
|
|
89
|
+
ambience: 'Room tone / atmosphere. Present in mode="full".',
|
|
89
90
|
sfx: 'Effects and Foley.',
|
|
90
91
|
me: 'Everything except dialogue (M&E) — the track to dub over.',
|
|
91
92
|
original: 'The untouched mix, for reference.',
|
|
92
93
|
},
|
|
93
|
-
next_steps: 'If voices are still faintly audible in the "me" layer, run clean_dialogue_leftovers on its URL.
|
|
94
|
+
next_steps: 'If voices are still faintly audible in the "me" layer, run clean_dialogue_leftovers on its URL. Ambience comes back as its own layer in mode="full"; for a dialogue-mode split, run separate_ambience on the "me" layer with source_type="me".',
|
|
94
95
|
});
|
|
95
96
|
},
|
|
96
97
|
);
|
|
@@ -101,8 +102,8 @@ function registerAudioStemTools(server, client) {
|
|
|
101
102
|
'Strip voices that are still faintly audible in a "without dialogue" (M&E) track produced '
|
|
102
103
|
+ 'by separate_audio_stems. Use it only when the user actually hears speech bleeding '
|
|
103
104
|
+ 'through — it is not part of the normal flow, and it trades fidelity to do its job '
|
|
104
|
-
+ '(
|
|
105
|
-
+ '
|
|
105
|
+
+ '(each cleanup pass re-separates the bed, so the M&E comes back less faithful than the '
|
|
106
|
+
+ 'original split; it runs up to 3 passes until the voices are gone). Typical on dense crowd scenes; unnecessary on clean '
|
|
106
107
|
+ 'dialogue. Costs 17 credits, charged even when the track turns out to be already clean '
|
|
107
108
|
+ '(the analysis pass still runs) — the response says which happened. Runs inline.',
|
|
108
109
|
{
|
|
@@ -128,8 +129,9 @@ function registerAudioStemTools(server, client) {
|
|
|
128
129
|
server.tool(
|
|
129
130
|
'separate_ambience',
|
|
130
131
|
'Pull room tone / atmosphere out of an Effects or Music bed, leaving the hard effects '
|
|
131
|
-
+ 'behind
|
|
132
|
-
+ '
|
|
132
|
+
+ 'behind. separate_audio_stems in its default "full" mode already returns an Ambience '
|
|
133
|
+
+ 'layer, so this is only needed for a dialogue-mode split, an older split, or a bed that '
|
|
134
|
+
+ 'still carries room tone. Run separate_audio_stems first '
|
|
133
135
|
+ 'and pass the resulting "sfx" (or "music") layer URL. Costs 17 credits, charged even '
|
|
134
136
|
+ 'when the bed turns out to have no separable ambience — the response reports '
|
|
135
137
|
+ 'skipped: true in that case. Runs inline.',
|
package/src/tools/chat.js
CHANGED
|
@@ -26,11 +26,11 @@ function registerChatTools(server, client) {
|
|
|
26
26
|
project_id: projectIdField
|
|
27
27
|
},
|
|
28
28
|
async ({ message, model, session_id, system_prompt, web_search, deep_think, thinking_level, routing_mode, enhance_prompt = false, media_urls, project_id }) => {
|
|
29
|
-
// Every generate_* tool resolves its model this way
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
29
|
+
// Every generate_* tool resolves its model this way. The API answers an
|
|
30
|
+
// explicit model it cannot match with 400 MODEL_NOT_FOUND (plus "did you
|
|
31
|
+
// mean" suggestions) instead of silently falling back to Smart Select, so
|
|
32
|
+
// resolve display names list_models hands back ("Claude Fable 5") to their
|
|
33
|
+
// catalog identifier here, scoped to the text catalog, before sending.
|
|
34
34
|
model = await canonicalModelId(client, model, 'text'); // lenient id resolution ("Grok 4.5" → its identifier)
|
|
35
35
|
|
|
36
36
|
const gen = await client.post('/v1/chat', {
|
package/src/tools/docs.js
CHANGED
|
@@ -7,7 +7,7 @@ const { z } = require('zod');
|
|
|
7
7
|
const { projectIdField } = require('./_shared');
|
|
8
8
|
const { listResult } = require('../apps');
|
|
9
9
|
|
|
10
|
-
const CONTENT_GUIDE = 'HTML body content. Use clean semantic HTML the in-app editor understands: <h1>-<h3>, <p>, <ul>/<ol>/<li>, <table>, <blockquote>, <strong>/<em>, <a
|
|
10
|
+
const CONTENT_GUIDE = 'HTML body content. Use clean semantic HTML the in-app editor understands: <h1>-<h3>, <p>, <ul>/<ol>/<li>, <table>, <blockquote>, <strong>/<em>, <a>, <img>, <video>/<audio> (http(s) src, e.g. Kolbo CDN URLs from list_media), and <iframe> embeds from YouTube, Vimeo, Spotify, SoundCloud, CodePen, CodeSandbox, Google Maps, Figma or Loom (https only; other iframe hosts are stripped). No <script>/<style> (stripped server-side). Write the FULL document yourself — this is where you author the doc.';
|
|
11
11
|
|
|
12
12
|
function registerDocTools(server, client) {
|
|
13
13
|
// ─── create_doc ────────────────────────────────────────────
|
|
@@ -90,12 +90,14 @@ function registerDocTools(server, client) {
|
|
|
90
90
|
{
|
|
91
91
|
doc_id: z.string().describe('The doc ObjectId to update.'),
|
|
92
92
|
title: z.string().optional().describe('New title. Omit to keep the current one.'),
|
|
93
|
-
content: z.string().optional().describe('Full replacement ' + CONTENT_GUIDE)
|
|
93
|
+
content: z.string().optional().describe('Full replacement ' + CONTENT_GUIDE),
|
|
94
|
+
destructive_intent: z.enum(['replace-document', 'clear-document']).optional().describe('Only when the update intentionally removes most of the document. The server rejects with 409 DESTRUCTIVE_DOCUMENT_REPLACEMENT when new content drops most of the text or every table/image/video, and with 409 DOCUMENT_CLEAR_REQUIRES_INTENT when it empties a populated doc. "replace-document" = the user asked for a rewrite/replacement; "clear-document" = the user asked to empty the doc (send empty content). Never pass it to get past a 409 the user did not ask for — re-read with get_doc and merge instead.')
|
|
94
95
|
},
|
|
95
|
-
async ({ doc_id, title, content }) => {
|
|
96
|
+
async ({ doc_id, title, content, destructive_intent }) => {
|
|
96
97
|
const body = {};
|
|
97
98
|
if (title !== undefined) body.title = title;
|
|
98
99
|
if (content !== undefined) body.content = content;
|
|
100
|
+
if (destructive_intent !== undefined) body.destructive_intent = { kind: destructive_intent };
|
|
99
101
|
const result = await client.put(`/v1/docs/${encodeURIComponent(doc_id)}`, body);
|
|
100
102
|
return { content: [{ type: 'text', text: JSON.stringify(result.doc, null, 2) }] };
|
|
101
103
|
}
|
package/src/tools/generate.js
CHANGED
|
@@ -486,7 +486,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
486
486
|
aspect_ratio: z.string().optional().describe(aspectRatioDescribe('1:1')),
|
|
487
487
|
workflow_type: z.string().optional().describe('"image" (default) or "video"'),
|
|
488
488
|
duration: z.number().optional().describe('Duration in seconds per scene (video mode only). Must be a value in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. E.g., 5 or 10.'),
|
|
489
|
-
enhance_prompt: z.boolean().optional().describe('
|
|
489
|
+
enhance_prompt: z.boolean().optional().describe('Ignored by the server: Creative Director always writes and enhances each scene prompt itself, so true and false behave the same. Kept for backward compatibility.'),
|
|
490
490
|
reference_images: z.array(z.string()).optional().describe('Array of reference images (URLs or absolute local paths) to guide style/composition of every scene. **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model.**'),
|
|
491
491
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply consistently across every scene. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model.** This is the ideal way to keep a character or product looking the same in all scenes of a campaign.'),
|
|
492
492
|
moodboard_id: z.string().optional().describe('A single moodboard ID whose master_prompt and style_guide should shape every scene.'),
|
package/src/tools/projects.js
CHANGED
|
@@ -70,7 +70,7 @@ function registerProjectTools(server, client) {
|
|
|
70
70
|
// ─── move_session ──────────────────────────────────────────
|
|
71
71
|
server.tool(
|
|
72
72
|
'move_session',
|
|
73
|
-
'Move ONE session — and ALL of its generations and media library items — to another project. Works for
|
|
73
|
+
'Move ONE session — and ALL of its generations and media library items — to another project. Works for image, image_edit, video, video_from_image, elements, first_last_frame, lipsync, video_from_video, global_video_edit, music, speech, sound and chat sessions (the `session_id` returned by generate_* tools, or a chat conversation). Creative Director, transcription, global_image_edit and shorts sessions cannot be moved through the API and return SESSION_TYPE_NOT_MOVABLE. Use this when work landed in the wrong project (e.g. the default "API Generations" bucket) and the user wants it in a named project — moving is always better than regenerating. For SEVERAL sessions use `bulk_move_sessions` instead: one call, up to 100 sessions, and it reports per-session failures. Caller needs edit/full/owner permission on BOTH the source and target projects (a shared-project member can move a teammate\'s session). Resolve the target project id with `list_projects` first.',
|
|
74
74
|
{
|
|
75
75
|
session_id: z.string().describe('The session ObjectId to move (from a generation submit response, chat_list_conversations, or an "Open in Kolbo" link).'),
|
|
76
76
|
project_id: z.string().describe('Target project ObjectId. Call `list_projects` to resolve a project name to its id.'),
|
|
@@ -49,7 +49,7 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
49
49
|
'Covers external providers (Pexels photos/videos, Pixabay, Sketchfab 3D, licensed Music) AND Kolbo\'s OWN AI-generated library: thousands of SOUND EFFECTS (mediaType="sfx") and MUSIC tracks (source="kolbo-ai"). Use this to FIND ready-made photos, videos, 3D models, music, or sound effects as b-roll/references/project assets — distinct from generate_* tools which create new content.\n\nFor SOUND EFFECTS or MUSIC, Kolbo supports SEMANTIC "VIBE" SEARCH: pass a natural-language description of the feeling/use ("tense ominous build-up for a horror reveal", "uplifting hopeful corporate background", "retro arcade coin pickup") with source="kolbo-ai" and mediaType="sfx" (or "music") — it matches by meaning, not just keywords. For external visual providers, use concrete keywords.\n\nsource="all" interleaves providers for the requested media type; or pick a single source. Returns assets with source, sourceId, mediaType, dimensions, author, attribution, thumbnail, and downloadable variants. To turn a script into queries first, call analyze_script_for_stock.',
|
|
50
50
|
{
|
|
51
51
|
query: z.string().max(200).optional().describe('For visual providers: concrete keywords ("city skyline sunset"). For Kolbo SFX/music (source="kolbo-ai"): a natural-language VIBE works great ("eerie suspenseful drone", "emotional sad piano"). Omit to browse.'),
|
|
52
|
-
source: z.enum(['all', 'kolbo-ai', 'pexels', 'pixabay', 'sketchfab', 'music', 'freesound']).optional().describe('Provider. "all" (default) interleaves. "kolbo-ai" = Kolbo\'s own AI SFX + music (best for vibe search). "freesound" = external CC sound effects.'),
|
|
52
|
+
source: z.enum(['all', 'kolbo-ai', 'pexels', 'unsplash', 'pixabay', 'coverr', 'sketchfab', 'music', 'freesound']).optional().describe('Provider. "all" (default) interleaves. "kolbo-ai" = Kolbo\'s own AI SFX + music (best for vibe search). "unsplash" = photos. "coverr" = stock video + music. "freesound" = external CC sound effects. "music" is not available over the API (returns not found) — use "kolbo-ai" or "coverr" for music.'),
|
|
53
53
|
mediaType: z.enum(['image', 'illustration', 'vector', 'video', '3d', 'music', 'sfx']).optional().describe('Asset type (default "image"). "sfx" = sound effects, "music" = music tracks. Not every source supports every type — call get_stock_sources.'),
|
|
54
54
|
category: z.string().optional().describe('Category/group chip value (providerParam) from get_stock_categories. For Kolbo SFX these are 77 Soundly-style top-level groups (e.g. Ambience, Animals, Vehicles, Weapons, Water, Designed, Magic, UI) — call get_stock_categories to list them all.'),
|
|
55
55
|
subcategory: z.string().optional().describe('Kolbo SFX sub-filter within a group (providerParam from get_stock_categories, e.g. Weapons>sword, Water>splash, Footsteps>concrete, Designed>riser). 623 sub-filters across the 77 groups.'),
|
|
@@ -57,10 +57,11 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
57
57
|
collectionId: z.string().optional().describe('Filter to one Kolbo collection id (from get_stock_collections).'),
|
|
58
58
|
orientation: z.enum(['horizontal', 'vertical', 'landscape', 'portrait', 'square']).optional().describe('Orientation filter (provider-dependent).'),
|
|
59
59
|
color: z.string().optional().describe('Color filter (Pixabay named color, or Pexels named/hex color).'),
|
|
60
|
-
order: z.enum(['popular', 'latest']).optional().describe('Sort order (
|
|
60
|
+
order: z.enum(['popular', 'latest', 'trending', 'newest', 'title', 'duration_asc', 'duration_desc', 'bpm_asc', 'bpm_desc']).optional().describe('Sort order, provider-dependent. "popular"/"latest": Pixabay, Freesound, Coverr. "trending": Coverr. Music (mediaType="music"): "popular", "newest", "title", "duration_asc", "duration_desc", "bpm_asc", "bpm_desc". A provider ignores values it does not support.'),
|
|
61
61
|
cursor: z.string().optional().describe('Opaque pagination cursor for Sketchfab single-source browse (from a previous response).'),
|
|
62
62
|
page: z.number().int().min(1).optional().describe('1-based page number (default 1).'),
|
|
63
|
-
perPage: z.number().int().min(1).max(80).optional().describe('Results per page (default 24, max 80).')
|
|
63
|
+
perPage: z.number().int().min(1).max(80).optional().describe('Results per page (default 24, max 80).'),
|
|
64
|
+
projectId: z.string().optional().describe('Optional project id. Prices Kolbo music/SFX for that project (e.g. org-project access), so meta.priceCredits matches what importing into that project would cost.')
|
|
64
65
|
},
|
|
65
66
|
async (args) => {
|
|
66
67
|
const q = buildQuery(args);
|
|
@@ -136,7 +137,7 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
136
137
|
'get_stock_categories',
|
|
137
138
|
'List the dynamic category chips for stock sources. For external providers: Pixabay/Sketchfab categories + curated Pexels topics. For Kolbo SFX (source="kolbo-ai", mediaType="sfx"): the 77 Soundly-style top-level groups (group=null) AND their 623 sub-filters (each has a `group` pointing to its parent). Pass a row\'s `providerParam` as `category` (groups) or `subcategory` (sub-filters) to search_stock_media.',
|
|
138
139
|
{
|
|
139
|
-
source: z.enum(['kolbo-ai', 'pexels', 'pixabay', 'sketchfab']).optional().describe('Restrict to one source. Use "kolbo-ai" to list the SFX groups + sub-filters.'),
|
|
140
|
+
source: z.enum(['kolbo-ai', 'pexels', 'unsplash', 'pixabay', 'coverr', 'sketchfab', 'freesound']).optional().describe('Restrict to one source. Use "kolbo-ai" to list the SFX groups + sub-filters.'),
|
|
140
141
|
mediaType: z.string().optional().describe('Restrict to one media type (e.g. "image", "video", "3d", "sfx").')
|
|
141
142
|
},
|
|
142
143
|
async (args) => {
|
|
@@ -204,12 +205,13 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
204
205
|
'get_stock_asset',
|
|
205
206
|
'Get a single normalized stock asset with all downloadable variants, author, license, and attribution, by source + id. Call after search_stock_media to resolve the exact download URLs (incl. WAV master + MP3 for Kolbo SFX/music).',
|
|
206
207
|
{
|
|
207
|
-
source: z.enum(['kolbo-ai', 'pexels', 'pixabay', 'sketchfab', 'music', 'freesound']).describe('The asset source.'),
|
|
208
|
+
source: z.enum(['kolbo-ai', 'pexels', 'unsplash', 'pixabay', 'coverr', 'sketchfab', 'music', 'freesound']).describe('The asset source. "music" is not available over the API.'),
|
|
208
209
|
id: z.string().describe('The provider asset id (sourceId).'),
|
|
209
|
-
mediaType: z.string().optional().describe('Media type hint (e.g. "video") — needed for sources that share ids across types.')
|
|
210
|
+
mediaType: z.string().optional().describe('Media type hint (e.g. "video") — needed for sources that share ids across types.'),
|
|
211
|
+
projectId: z.string().optional().describe('Optional project id used to price Kolbo music/SFX for that project (same as search_stock_media).')
|
|
210
212
|
},
|
|
211
|
-
async ({ source, id, mediaType }) => {
|
|
212
|
-
const q = buildQuery({ mediaType });
|
|
213
|
+
async ({ source, id, mediaType, projectId }) => {
|
|
214
|
+
const q = buildQuery({ mediaType, projectId });
|
|
213
215
|
const result = await client.get(`/v1/stock/asset/${encodeURIComponent(source)}/${encodeURIComponent(id)}${q ? '?' + q : ''}`);
|
|
214
216
|
return { content: [{ type: 'text', text: JSON.stringify(result.asset, null, 2) }] };
|
|
215
217
|
}
|
|
@@ -233,7 +235,7 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
233
235
|
'import_stock_asset',
|
|
234
236
|
"Copy a stock asset into the account's Kolbo media library (downloaded to Kolbo's CDN with a stable URL) so it can be used in projects/generations. FREE for images, video and 3D. Kolbo's own MUSIC and SFX are free for subscribers, org members and anyone who already bought the track; for everyone else they COST CREDITS (search results carry the exact price in meta.priceCredits; 0/absent means free for this caller). When a price is shown, tell the user the cost and get agreement BEFORE calling this. Returns the created media library item. Works for Kolbo SFX (source='kolbo-ai', mediaType='sfx') and supported external visual/audio sources. For SYNCI music use import_music_track_to_library; that paid action acquires a clean licensed file.",
|
|
235
237
|
{
|
|
236
|
-
source: z.enum(['kolbo-ai', 'pexels', 'pixabay', 'sketchfab', 'freesound']).describe('The asset source.'),
|
|
238
|
+
source: z.enum(['kolbo-ai', 'pexels', 'unsplash', 'pixabay', 'coverr', 'sketchfab', 'freesound']).describe('The asset source.'),
|
|
237
239
|
id: z.string().describe('The provider asset id (sourceId).'),
|
|
238
240
|
mediaType: z.string().optional().describe('Media type hint (e.g. "video", "image", "vector", "3d").'),
|
|
239
241
|
variant: z.string().optional().describe('Which download variant label to import (from get_stock_asset). Defaults to the best/largest available.'),
|