@kolbo/mcp 1.82.6 → 1.83.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.82.6",
3
+ "version": "1.83.0",
4
4
  "description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/skill/SKILL.md CHANGED
@@ -105,10 +105,12 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
105
105
  |------|-------------|
106
106
  | `generate_image` | Single image from a text prompt. Supports Visual DNA, moodboards, image presets (custom instructions live here), reference images, web-search grounding. Named sheets/styles: `list_presets({ type: "image", search: "headless" })` then `preset_id`. |
107
107
  | `generate_image_edit` | Edit/transform an existing image. Pass `source_images` + edit prompt. Image-editing presets are supported through `preset_id` from `list_presets({ type: "image_edit" })`. |
108
+ | `edit_image` | Operation-routed image tools such as upscale, reframe, outpaint, background work, inpaint and enhance. Before choosing `model`, call `list_models` with the operation family (`image_upscale`, `image_reframe`, `image_zoom_out`, `background_remove`, `graphics_enhance`, etc.) and pass a concrete returned identifier. `multi_shot` and `split` use pinned processing, so omit `model` for those. |
108
109
  | `generate_creative_director` | **2–8 related images or videos as one coherent set.** Use INSTEAD of multiple `generate_image` calls for any related multi-output. |
109
110
  | `generate_video` | Text-to-video. Accepts `visual_dna_ids` and `sound_enabled`; `generate_elements` is still the primary reference-driven route for a DNA-anchored film. |
110
111
  | `generate_video_from_image` | Animate a still. Prompt describes motion, not subject. |
111
112
  | `generate_video_from_video` | Restyle/transform an existing video. Keeps original motion. |
113
+ | `edit_video` | Operation-routed video tools such as upscale, reframe, audio generation, watermark/background removal, face swap, extend, inpaint and retake. Discover the real engines through the operation family (`video_upscale`, `video_reframe`, `video_to_sound`, `video_extend`, etc.), compare caps/cost/`params`, and pass a concrete identifier — never a `kolbo_gateway_*` navigation row. |
112
114
  | `generate_elements` | Reference-driven video. **Primary route for DNA → video.** Prompt = Seedance Locked Intro (`Total` + `[GLOBAL LOOK]` / `[CAST]` / `[LOCATION]` + `SHOT N`). Every DNA in `visual_dna_ids` must also be `@Name` in that prompt. |
113
115
  | `generate_first_last_frame` | Keyframe interpolation between two frames. |
114
116
  | `generate_lipsync` | Lipsync an existing waveform onto a face. **Not the route for dialogue in a film you are generating** — write the line in the Seedance prompt instead. |
package/src/apps/index.js CHANGED
@@ -562,6 +562,30 @@ async function canonicalModelId(client, input, type) {
562
562
  const ids = new Set((narrowed.length ? narrowed : prefixed).map((i) => i.id));
563
563
  if (ids.size === 1) return [...ids][0];
564
564
 
565
+ // The general catalog is cached for widget performance, while list_models
566
+ // is intentionally live. On a just-published model, refresh only the typed
567
+ // family before reporting an unknown identifier so a model discovered one
568
+ // moment ago is immediately usable.
569
+ if (types.length && typeof client.get === 'function') {
570
+ try {
571
+ const freshRows = [];
572
+ for (const expectedType of types) {
573
+ const response = await client.get(`/v1/models?type=${encodeURIComponent(expectedType)}`);
574
+ freshRows.push(...(response?.models || response?.data?.models || []));
575
+ }
576
+ const freshMatches = freshRows.filter((row) => {
577
+ const id = row?.identifier;
578
+ const name = row?.name;
579
+ return (id && (id.toLowerCase() === key || normId(id) === want))
580
+ || (name && (name.toLowerCase() === key || normId(name) === want));
581
+ });
582
+ const freshIds = [...new Set(freshMatches.map((row) => row.identifier).filter(Boolean))];
583
+ if (freshIds.length === 1) return freshIds[0];
584
+ } catch (_) {
585
+ // Keep the existing actionable near-miss error when the refresh fails.
586
+ }
587
+ }
588
+
565
589
  // 4. unknown — name the near misses instead of dead-ending at the API.
566
590
  const stem = normId(key.split(/[\s._/-]+/).filter(Boolean)[0] || key);
567
591
  const near = [...new Set(
@@ -577,6 +601,31 @@ async function canonicalModelId(client, input, type) {
577
601
  );
578
602
  }
579
603
 
604
+ /**
605
+ * Reject a published model that belongs to a different operation family.
606
+ * Hidden/unpublished identifiers still fail open so existing pinned engines
607
+ * remain usable; the API remains authoritative for those.
608
+ */
609
+ async function assertModelSupportsType(client, modelId, type) {
610
+ if (!modelId || !type) return modelId;
611
+ let all;
612
+ try {
613
+ all = (await modelCatalog(client)).all;
614
+ } catch (_) {
615
+ return modelId;
616
+ }
617
+
618
+ const row = (all || []).find((item) => item.id === modelId);
619
+ if (!row) return modelId;
620
+ const expected = (Array.isArray(type) ? type : [type]).filter(Boolean);
621
+ if (!expected.length || row.types.some((value) => expected.includes(value))) return modelId;
622
+
623
+ throw new Error(
624
+ `Model "${modelId}" cannot be used for this operation (expected type: ${expected.join(' or ')}). `
625
+ + `Call list_models with type="${expected[0]}" and pass a concrete identifier it returns.`
626
+ );
627
+ }
628
+
580
629
  /* ------------------------------------------------------------------ */
581
630
  /* Declaration-level widget metadata */
582
631
  /* ------------------------------------------------------------------ */
@@ -668,6 +717,7 @@ module.exports = {
668
717
  modelInfoMap,
669
718
  voiceInfo,
670
719
  canonicalModelId,
720
+ assertModelSupportsType,
671
721
  normalizeAspectRatio,
672
722
  closestAspectRatio,
673
723
  resolveCatalogAspectRatio,
@@ -0,0 +1,64 @@
1
+ /*
2
+ * Operation-specific model catalogs for edit_image / edit_video.
3
+ *
4
+ * The `kolbo_gateway_*` rows in video_to_video are navigation aliases used by
5
+ * Kolbo's web picker. They are not provider engines. MCP callers must discover
6
+ * and submit a concrete model from the operation's real DB type instead.
7
+ */
8
+
9
+ const VIDEO_EDIT_MODEL_TYPES = Object.freeze({
10
+ upscale: 'video_upscale',
11
+ reframe: 'video_reframe',
12
+ generate_audio: 'video_to_sound',
13
+ remove_watermark: 'video_watermark_removal',
14
+ face_swap: 'video_face_swap',
15
+ extend: 'video_extend',
16
+ magic_edit: 'video_to_video',
17
+ lipsync: 'lipsync-video',
18
+ remove_background: 'video_background_removal',
19
+ inpaint: 'video_inpaint',
20
+ retake: 'video_retake',
21
+ });
22
+
23
+ const IMAGE_EDIT_MODEL_TYPES = Object.freeze({
24
+ upscale: 'image_upscale',
25
+ clarity_upscale: 'image_upscale',
26
+ reframe: 'image_reframe',
27
+ zoom_out: 'image_zoom_out',
28
+ inpaint: 'inpaint',
29
+ erase: 'erase',
30
+ face_swap: 'face_swap',
31
+ background_remove: 'background_remove',
32
+ removebg: 'background_remove',
33
+ background_replace: 'background_replace',
34
+ magic_edit: 'image_editing',
35
+ camera_angle: 'image_editing',
36
+ enhance_skin: 'skin_enhancer',
37
+ enhance: 'graphics_enhance',
38
+ // The API intentionally pins multi_shot to its dedicated engine.
39
+ multi_shot: null,
40
+ split_upscale: 'image_upscale',
41
+ split: null,
42
+ });
43
+
44
+ function modelTypeForEditOperation(kind, operation) {
45
+ const map = kind === 'image' ? IMAGE_EDIT_MODEL_TYPES : VIDEO_EDIT_MODEL_TYPES;
46
+ return map[operation] || null;
47
+ }
48
+
49
+ function assertExecutableEditModel(model, kind, operation) {
50
+ if (!model || !/^kolbo_gateway_/i.test(String(model))) return;
51
+ const type = modelTypeForEditOperation(kind, operation);
52
+ throw new Error(
53
+ `"${model}" is a Kolbo navigation alias, not an executable AI model. ` +
54
+ `Call list_models with type="${type || (kind === 'image' ? 'image_editing' : 'video_to_video')}" ` +
55
+ `and pass one of the concrete model identifiers it returns.`
56
+ );
57
+ }
58
+
59
+ module.exports = {
60
+ VIDEO_EDIT_MODEL_TYPES,
61
+ IMAGE_EDIT_MODEL_TYPES,
62
+ modelTypeForEditOperation,
63
+ assertExecutableEditModel,
64
+ };
@@ -8,7 +8,8 @@ const FormData = require('form-data');
8
8
  const { pollUntilDone, waitWindowMs } = require('../polling');
9
9
  const { resolveToBuffer, pollOrTimedOut, creditFields, projectIdField, sessionIdField, inlineImageBlocks, linkFields, uiGenerating, uiCompleted, appsEnabled } = require('./_shared');
10
10
  const { ownedUrl } = require('./owned-url');
11
- const { UI, uiResult, canonicalModelId, modelInfo, voiceInfo, resolveCatalogAspectRatio } = require('../apps');
11
+ const { UI, uiResult, canonicalModelId, assertModelSupportsType, modelInfo, voiceInfo, resolveCatalogAspectRatio } = require('../apps');
12
+ const { modelTypeForEditOperation, assertExecutableEditModel } = require('./editModelCatalog');
12
13
 
13
14
  // ─── Cinematic Dimensions schema (shared by generate_image + generate_image_edit) ───
14
15
  // Kolbo's "Cinema mode": eight independent photographic dimensions, each an OPTIONAL
@@ -1979,7 +1980,7 @@ function registerGenerateTools(server, client, options = {}) {
1979
1980
  ].join(' ')),
1980
1981
 
1981
1982
  model: z.string().optional()
1982
- .describe('Model identifier override. Omit to use the platform default for the operation.'),
1983
+ .describe('Concrete model identifier for this operation. Dynamically discover it with list_models using the operation-specific type: upscale/clarity_upscale/split_upscale → "image_upscale"; reframe → "image_reframe"; zoom_out → "image_zoom_out"; inpaint → "inpaint"; erase → "erase"; face_swap → "face_swap"; removebg → "background_remove"; background_replace → "background_replace"; enhance_skin → "skin_enhancer"; enhance → "graphics_enhance"; magic_edit/camera_angle → "image_editing". multi_shot and split use pinned/non-selectable processing, so omit model for those operations. Omit elsewhere to use the platform default. Never pass a kolbo_gateway_* navigation alias.'),
1983
1984
 
1984
1985
  // ── upscale ────────────────────────────────────────────
1985
1986
  scale: z.number().optional()
@@ -2049,11 +2050,15 @@ function registerGenerateTools(server, client, options = {}) {
2049
2050
  enhancement_model, output_format,
2050
2051
  project_id, session_id
2051
2052
  }) => {
2052
- // No `type` argument: these are operation-routed tools (upscale / reframe /
2053
- // removebg / …), each operation with its own model family — there is no single
2054
- // catalog type to disambiguate against.
2055
- model = await canonicalModelId(client, model);
2056
- aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio);
2053
+ const editModelType = modelTypeForEditOperation('image', operation);
2054
+ if ((operation === 'multi_shot' || operation === 'split') && model) {
2055
+ throw new Error(`model is not configurable for ${operation}; omit it to use the operation's pinned processing path`);
2056
+ }
2057
+ assertExecutableEditModel(model, 'image', operation);
2058
+ model = await canonicalModelId(client, model, editModelType || undefined);
2059
+ assertExecutableEditModel(model, 'image', operation);
2060
+ await assertModelSupportsType(client, model, editModelType || undefined);
2061
+ aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, editModelType || undefined);
2057
2062
 
2058
2063
  // Basic validation
2059
2064
  if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe');
@@ -2133,7 +2138,7 @@ function registerGenerateTools(server, client, options = {}) {
2133
2138
  ].join(' ')),
2134
2139
 
2135
2140
  model: z.string().optional()
2136
- .describe('Model identifier override. Omit to use the platform default for the operation.'),
2141
+ .describe('Concrete model identifier for this operation. Dynamically discover it with list_models using the operation-specific type: upscale → "video_upscale"; reframe → "video_reframe"; generate_audio → "video_to_sound"; remove_watermark → "video_watermark_removal"; face_swap → "video_face_swap"; extend → "video_extend"; magic_edit → "video_to_video"; lipsync → "lipsync-video"; remove_background → "video_background_removal"; inpaint → "video_inpaint"; retake → "video_retake". Compare the returned credit, supported resolutions/aspect ratios, duration limits, resolution multipliers, and params, then pass a concrete identifier. Omit to use the platform default. Never pass a kolbo_gateway_* navigation alias.'),
2137
2142
 
2138
2143
  // ── upscale ────────────────────────────────────────────
2139
2144
  scale: z.number().optional()
@@ -2164,6 +2169,14 @@ function registerGenerateTools(server, client, options = {}) {
2164
2169
  .describe('When true, keeps the original video audio and mixes in the generated audio. Used with "generate_audio". Default: false.'),
2165
2170
  cfg_strength: z.number().optional()
2166
2171
  .describe('Guidance strength for audio generation (higher = follows prompt more strictly). Used with "generate_audio".'),
2172
+ audio_format: z.enum(['wav', 'mp3', 'aac', 'flac']).optional()
2173
+ .describe('Separate generated-audio format for Sonilo sound-effects models. Read output_audio_formats/default_output_audio_format from list_models; default is "aac".'),
2174
+ segments: z.array(z.object({
2175
+ start: z.number().nonnegative(),
2176
+ end: z.number().positive(),
2177
+ prompt: z.string()
2178
+ })).optional()
2179
+ .describe('Optional contiguous Sonilo sound-design ranges. The first start must be 0, each end must equal the next start, and the final end must not exceed the video duration. Omit to let Sonilo detect scenes automatically.'),
2167
2180
 
2168
2181
  // ── face_swap ──────────────────────────────────────────
2169
2182
  image_url: z.string().optional()
@@ -2210,21 +2223,26 @@ function registerGenerateTools(server, client, options = {}) {
2210
2223
  target_fps, resolution, enhancement_model,
2211
2224
  grid_position_x, grid_position_y,
2212
2225
  sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
2226
+ audio_format, segments,
2213
2227
  refine_edges, subject_is_person,
2214
2228
  text_prompt, context,
2215
2229
  mask_video_url, object_prompt, video_strength,
2216
2230
  start_time,
2217
2231
  project_id, session_id
2218
2232
  }) => {
2219
- // No `type` argument: these are operation-routed tools (upscale / reframe /
2220
- // removebg / …), each operation with its own model family — there is no single
2221
- // catalog type to disambiguate against.
2222
- model = await canonicalModelId(client, model);
2223
- aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio);
2233
+ const editModelType = modelTypeForEditOperation('video', operation);
2234
+ assertExecutableEditModel(model, 'video', operation);
2235
+ model = await canonicalModelId(client, model, editModelType || undefined);
2236
+ assertExecutableEditModel(model, 'video', operation);
2237
+ await assertModelSupportsType(client, model, editModelType || undefined);
2238
+ aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, editModelType || undefined);
2224
2239
 
2225
2240
  // Validation
2226
2241
  if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit');
2227
- if (operation === 'generate_audio'&& !prompt) throw new Error('prompt is required for generate_audio');
2242
+ if (operation === 'generate_audio' && !prompt && !sound_effect_prompt && !background_music_prompt
2243
+ && !(typeof model === 'string' && model.includes('sonilo'))) {
2244
+ throw new Error('prompt is required for generate_audio unless dedicated sound/music prompts are provided or the selected model supports automatic captioning');
2245
+ }
2228
2246
  if (operation === 'reframe' && !aspect_ratio)throw new Error('aspect_ratio is required for reframe');
2229
2247
  if (operation === 'face_swap' && !image_url) throw new Error('image_url (reference face) is required for face_swap');
2230
2248
  if (operation === 'lipsync' && !audio_url && !text_prompt) throw new Error('audio_url or text_prompt is required for lipsync');
@@ -2236,6 +2254,7 @@ function registerGenerateTools(server, client, options = {}) {
2236
2254
  target_fps, resolution,
2237
2255
  grid_position_x, grid_position_y,
2238
2256
  sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
2257
+ audio_format, segments,
2239
2258
  refine_edges, subject_is_person,
2240
2259
  text_prompt, context,
2241
2260
  mask_video_url, object_prompt, video_strength,
@@ -15,6 +15,18 @@ const TYPE_GROUPS = {
15
15
  text_to_speech: 'Voice',
16
16
  image_editing: 'Image Editing',
17
17
  video_to_video: 'Video to Video',
18
+ image_upscale: 'Image Upscale',
19
+ image_reframe: 'Image Reframe',
20
+ image_zoom_out: 'Image Expand',
21
+ video_upscale: 'Video Upscale',
22
+ video_reframe: 'Video Reframe',
23
+ video_background_removal: 'Video Background Removal',
24
+ video_to_sound: 'Video Audio Generation',
25
+ video_face_swap: 'Video Face Swap',
26
+ video_watermark_removal: 'Video Watermark Removal',
27
+ video_extend: 'Video Extend',
28
+ video_inpaint: 'Video Inpaint',
29
+ video_retake: 'Video Retake',
18
30
  elements: 'Elements',
19
31
  };
20
32
 
@@ -105,7 +117,7 @@ function registerModelTools(server, client, options = {}) {
105
117
  'list_models',
106
118
  'List available AI models on Kolbo. Filter by `type` to narrow to a generation type, and pass `format: "json"` to enumerate the catalog with exact identifiers — `format: "json"` + `type` returns the full raw model documents (every constraint field, for programmatic comparison / cap validation before submitting a generation); `format: "json"` alone returns a compact index of EVERY model and its identifier. Default `format: "text"` returns the human-readable summary. NEVER guess a model identifier: call this tool. ⚠️ COST: for any model whose type is video / firstlast / elements / motion_graphic / cast, `credit` is a PER-SECOND rate, not a per-clip price — multiply by the requested `duration` before quoting cost to the user (e.g. `credit: 9` at `duration: 8` is 72 credits, not 9). This is the universal rule, not a per-model exception. The one carve-out is a model with `flat_credit_by_resolution` set — those charge the flat rate regardless of duration. Every other model type (image, audio, 3D, per-token text) already bills flat per generation as `credit` states.',
107
119
  {
108
- 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.'),
120
+ type: z.string().optional().describe('Filter by DB type name. Generation: "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". Image-edit engines: "image_upscale", "image_reframe", "image_zoom_out", "inpaint", "erase", "face_swap", "background_remove", "background_replace", "skin_enhancer", "graphics_enhance". Video-edit engines: "video_upscale", "video_reframe", "video_background_removal", "video_to_sound", "video_face_swap", "video_watermark_removal", "video_extend", "video_inpaint", "video_retake". For edit_image/edit_video, query the operation-specific type and pass a CONCRETE returned identifier; never submit a kolbo_gateway_* row, because those are web-navigation aliases rather than AI engines. Legacy aliases also accepted: "image", "image_edit", "video", "video_from_image", "video_from_video", "music", "speech", "sound", "chat", "lipsync", "three_d", "first_last_frame", "transcription". Omit for all models.'),
109
121
  format: z.enum(['text', 'json']).optional().describe('Output format. "text" (default) returns a human-readable summary with the most-used caps. "json" is the source of truth for identifiers and caps: with `type` it returns the raw model documents from the API (identifier, credit, supported_durations, supported_resolutions, supported_aspect_ratios, max_reference_images, max_visual_dna, max_video_duration, …) for EVERY model of that type; without `type` it returns a compact index of every model in the catalog and its exact identifier. Use it whenever you need an identifier you have not seen listed, or must verify a cap before passing a value that might exceed a model-specific limit.'),
110
122
  display_catalog: z.boolean().optional().describe('Set true when the USER explicitly asked to see/browse the available models — the visual catalog opens expanded. Leave unset for internal lookups (verifying a model name, checking caps before a generation): the catalog stays collapsed to a single row the user can tap to browse.')
111
123
  },