@kolbo/mcp 1.53.0 → 1.54.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 +1 -1
- package/src/apps/widgets/catalog.js +16 -5
- package/src/index.js +1 -1
- package/src/tools/generate.js +23 -5
- package/src/tools/models.js +12 -6
package/package.json
CHANGED
|
@@ -31,9 +31,20 @@ el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openL
|
|
|
31
31
|
var state = null;
|
|
32
32
|
|
|
33
33
|
function boot(sc) {
|
|
34
|
-
if (!sc || !sc.groups) return;
|
|
34
|
+
if (!sc || !sc.groups || !sc.groups.length) return false;
|
|
35
35
|
state = sc;
|
|
36
36
|
el('title').textContent = sc.title || 'AI Models';
|
|
37
|
+
// Internal lookup (Claude checking caps mid-task): a full catalog card would
|
|
38
|
+
// be chat spam, an empty card would look broken. One expandable row instead.
|
|
39
|
+
if (sc.compact) {
|
|
40
|
+
el('stage').innerHTML = '<div class="k-audio-row" id="expand" style="cursor:pointer;padding:8px">' +
|
|
41
|
+
'<div class="k-audio-meta"><div class="k-audio-title" style="font-size:12px">Kolbo AI models</div>' +
|
|
42
|
+
'<div class="k-audio-sub" style="font-size:10.5px">Tap to browse what you can generate</div></div>' +
|
|
43
|
+
'<span class="k-chip" style="flex:none;padding:2px 7px;font-size:10px">Browse</span></div>';
|
|
44
|
+
el('expand').onclick = function () { sc.compact = false; boot(sc); };
|
|
45
|
+
window.kolbo.notifySize();
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
37
48
|
var shown = sc.groups.reduce(function (n, g) { return n + g.models.length; }, 0);
|
|
38
49
|
el('count-chip').style.display = '';
|
|
39
50
|
el('count-chip').textContent = sc.total_available && sc.total_available > shown
|
|
@@ -60,13 +71,13 @@ function boot(sc) {
|
|
|
60
71
|
};
|
|
61
72
|
});
|
|
62
73
|
window.kolbo.notifySize();
|
|
74
|
+
return true;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
window.kolbo.onToolResult(function (result) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
// was attached on purpose. Collapse instead of showing a dead card.
|
|
78
|
+
if (boot(result.structuredContent || structured(result))) return;
|
|
79
|
+
// No catalog data reached the iframe (host/version mismatch, or a filter
|
|
80
|
+
// that matched nothing) — collapse instead of showing a dead card.
|
|
70
81
|
var card = document.querySelector('.k-card');
|
|
71
82
|
if (card) card.style.display = 'none';
|
|
72
83
|
window.kolbo.notifySize();
|
package/src/index.js
CHANGED
|
@@ -124,7 +124,7 @@ function createServer(opts = {}) {
|
|
|
124
124
|
'7. SESSION CONTINUITY — one task, one session, always: every generation tool returns a `session_id`. For ANY follow-up, refinement, retry, or next step on the SAME task, pass that session_id back — never start fresh. BATCH RULE (critical): when a single user request produces multiple parallel generations (e.g. "animate these 5 images", "generate 3 variants"), do NOT launch them all at once without a session_id. Instead: (1) run the FIRST generation without session_id to create the session, (2) capture the session_id from its response, (3) pass that session_id to ALL remaining generations in the batch. This keeps the entire batch in one session. Exception: only omit session_id and start fresh when the user explicitly starts an unrelated new task.',
|
|
125
125
|
'8. LOCAL FILES / REFERENCE MEDIA — HOW TO HANDLE EVERY CASE: (A) User has a LOCAL file (audio, video, image, document) on their machine: if you have filesystem access (Claude Desktop / Code / IDE / any stdio MCP client) → call `upload_media` with the absolute local path OR pass the path directly to tools like `transcribe_audio` which accept local paths natively. If you have NO filesystem access (claude.ai browser/mobile) → call `media_upload_widget` IMMEDIATELY, an upload card appears, the user uploads, and a `media.kolbo.ai` CDN URL comes back — use that URL for any follow-up tool call. (B) You already have a public URL (media.kolbo.ai, any CDN, any direct link) → pass it directly to the tool. All Kolbo tools accept public URLs. NEVER search for DO Spaces keys, DigitalOcean credentials, or server-side upload credentials. NEVER ask the user to put the file on Google Drive, Dropbox, or Loom. NEVER invent or guess a URL. NEVER base64 a large file — use upload_media instead.',
|
|
126
126
|
'9. MODEL SELECTION: ALWAYS pass a specific `model` on every generation tool — do NOT omit it. Omitting falls back to "Smart Select" auto-routing, which we deliberately avoid because it hides the model choice from the user and often picks a generic default. Choose the model that best fits the task and the user\'s intent (quality, speed, style, capability). If you are unsure which model to use for a given type, call `list_models` with the matching `type` and pick the recommended/flagship one, then pass its `identifier`. Only use Smart Select (omit `model`) if the user EXPLICITLY asks you to auto-pick.',
|
|
127
|
-
'10. IMAGE EDITING: for ANY prompt-driven / content edit of an existing image — "make it night", changing scene/lighting/colors, adding/removing/replacing objects, restyling — use `generate_image_edit` (it runs on strong dedicated editing models, same as image generation). Do NOT use `edit_image` for content edits — `edit_image` is ONLY for mechanical enhancements (upscale,
|
|
127
|
+
'10. IMAGE EDITING: for ANY prompt-driven / content edit of an existing image — "make it night", changing scene/lighting/colors, adding/removing/replacing objects, restyling — use `generate_image_edit` (it runs on strong dedicated editing models, same as image generation). Do NOT use `edit_image` for content edits — `edit_image` is ONLY for mechanical enhancements (upscale, expand/outpaint, remove-background, skin retouch). Its `magic_edit` operation is deprecated in favor of `generate_image_edit`. EXPANDING AN IMAGE: to widen/extend/uncrop an image or fit it into a wider frame while KEEPING the existing artwork, use `edit_image` with operation="zoom_out" (outpainting — original pixels preserved; size it with `zoom_out_percentage` or the `expand_left/right/top/bottom` pixel args). The "reframe" operation is NOT this: it re-generates the whole picture at a new aspect ratio and the subject comes back re-imagined. Only pick "reframe" when the user wants the shot re-taken, never when they want their image extended.'
|
|
128
128
|
].join('\n')
|
|
129
129
|
});
|
|
130
130
|
|
package/src/tools/generate.js
CHANGED
|
@@ -1266,7 +1266,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1266
1266
|
// ─── edit_image ────────────────────────────────────────────
|
|
1267
1267
|
server.tool(
|
|
1268
1268
|
'edit_image',
|
|
1269
|
-
'Apply a targeted AI edit to an existing image. Covers mechanical enhancements (upscale,
|
|
1269
|
+
'Apply a targeted AI edit to an existing image. Covers mechanical enhancements (upscale, remove background, skin retouching) AND creative operations (expand/outpaint, reframe, inpaint, erase, face swap, background replace, camera angle, multi-shot grid, split/upscale). ⚠️ To EXPAND an image / add space / widen it to a new aspect ratio while keeping the existing artwork intact, use operation="zoom_out" — NOT "reframe" (reframe re-generates the whole picture). ⚠️ For open-ended PROMPT-DRIVEN content edits — "make it night", restyling, adding/removing objects — use `generate_image_edit` instead; it runs on stronger dedicated editing models and produces better results.',
|
|
1270
1270
|
{
|
|
1271
1271
|
image_url: z.string().describe('URL of the primary source image to edit.'),
|
|
1272
1272
|
|
|
@@ -1284,8 +1284,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1284
1284
|
]).describe([
|
|
1285
1285
|
'Edit operation:',
|
|
1286
1286
|
'"upscale" — increase resolution by 2×, 3×, or 4× (use `scale`). "clarity_upscale" — AI-powered clarity upscale with detail enhancement (use `resolution`).',
|
|
1287
|
-
'"
|
|
1288
|
-
'"
|
|
1287
|
+
'"zoom_out" — THE default way to expand / extend / outpaint / uncrop / widen an image, or to grow it into a wider or taller frame. The original pixels are PRESERVED and only the new area around them is generated. Expand uniformly with `zoom_out_percentage`, or in one direction with `expand_left` / `expand_right` / `expand_top` / `expand_bottom` (pixels — this is how you hit a target aspect ratio: read the source width/height, then add the missing pixels on the side you want). `prompt` describes what should appear in the NEW space only. Note: `aspect_ratio` is ignored by this operation.',
|
|
1288
|
+
'"reframe" — RE-GENERATES the entire picture at a new aspect ratio (Luma Photon). The subject is re-imagined, not preserved — expect a different-looking image. Use ONLY when the user explicitly wants the shot re-taken in another format. If they said "expand", "extend", "widen", "uncrop", "add space", "fill the sides", or "keep it the same but 16:9", they want "zoom_out" instead. Requires `aspect_ratio`.',
|
|
1289
1289
|
'"removebg" — remove the image background, output is transparent PNG.',
|
|
1290
1290
|
'"background_replace" — remove background and replace it with AI-generated content from `prompt`.',
|
|
1291
1291
|
'"enhance_skin" — portrait skin retouching (use `skin_strength`: "subtle" | "realistic" | "pimple" | "freckle").',
|
|
@@ -1310,7 +1310,23 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1310
1310
|
|
|
1311
1311
|
// ── reframe ────────────────────────────────────────────
|
|
1312
1312
|
aspect_ratio: z.string().optional()
|
|
1313
|
-
.describe('Target aspect ratio (e.g. "16:9", "9:16", "1:1", "4:3"). Required for operation="reframe".'),
|
|
1313
|
+
.describe('Target aspect ratio (e.g. "16:9", "9:16", "1:1", "4:3"). Required for operation="reframe". Ignored by "zoom_out" — size that expansion with `zoom_out_percentage` or the `expand_*` pixel args.'),
|
|
1314
|
+
|
|
1315
|
+
// ── zoom_out (outpaint / expand) ───────────────────────
|
|
1316
|
+
zoom_out_percentage: z.number().optional()
|
|
1317
|
+
.describe('How much to expand outward on ALL sides, as a percentage (0-90). Used with operation="zoom_out". Default: 20. Ignored when any `expand_*` arg is set.'),
|
|
1318
|
+
|
|
1319
|
+
expand_left: z.number().optional()
|
|
1320
|
+
.describe('Pixels of new AI-generated content to add on the LEFT. Used with operation="zoom_out" (0-700). This is how you widen an image asymmetrically, e.g. to turn a 16:9 frame into 21:9 without touching the subject.'),
|
|
1321
|
+
|
|
1322
|
+
expand_right: z.number().optional()
|
|
1323
|
+
.describe('Pixels of new AI-generated content to add on the RIGHT. Used with operation="zoom_out" (0-700).'),
|
|
1324
|
+
|
|
1325
|
+
expand_top: z.number().optional()
|
|
1326
|
+
.describe('Pixels of new AI-generated content to add on TOP. Used with operation="zoom_out" (0-700).'),
|
|
1327
|
+
|
|
1328
|
+
expand_bottom: z.number().optional()
|
|
1329
|
+
.describe('Pixels of new AI-generated content to add on the BOTTOM. Used with operation="zoom_out" (0-700).'),
|
|
1314
1330
|
|
|
1315
1331
|
// ── enhance_skin ───────────────────────────────────────
|
|
1316
1332
|
skin_strength: z.enum(['subtle', 'realistic', 'pimple', 'freckle']).optional()
|
|
@@ -1318,7 +1334,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1318
1334
|
|
|
1319
1335
|
// ── inpaint / erase / face_swap / background_replace / zoom_out / camera_angle / magic_edit ──
|
|
1320
1336
|
prompt: z.string().optional()
|
|
1321
|
-
.describe('Text instruction guiding the edit. Required for "background_replace". Used with "inpaint", "zoom_out", "camera_angle", and the deprecated "magic_edit".'),
|
|
1337
|
+
.describe('Text instruction guiding the edit. Required for "background_replace". Used with "inpaint", "zoom_out", "camera_angle", and the deprecated "magic_edit". For "zoom_out" it describes what fills the NEW space only — the original image is left as-is.'),
|
|
1322
1338
|
|
|
1323
1339
|
mask_image_url: z.string().optional()
|
|
1324
1340
|
.describe('URL of a mask image (black & white; white = affected area). Required for "inpaint" and "erase". For "face_swap", this is the face reference image.'),
|
|
@@ -1342,6 +1358,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1342
1358
|
async ({
|
|
1343
1359
|
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
|
|
1344
1360
|
mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize,
|
|
1361
|
+
zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom,
|
|
1345
1362
|
project_id
|
|
1346
1363
|
}) => {
|
|
1347
1364
|
model = await canonicalModelId(client, model);
|
|
@@ -1356,6 +1373,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1356
1373
|
const gen = await client.post('/v1/edit/image', {
|
|
1357
1374
|
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
|
|
1358
1375
|
mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize,
|
|
1376
|
+
zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom,
|
|
1359
1377
|
project_id
|
|
1360
1378
|
});
|
|
1361
1379
|
|
package/src/tools/models.js
CHANGED
|
@@ -49,7 +49,7 @@ function modelChips(m) {
|
|
|
49
49
|
// deliberately EXCLUDED — we always want a specific model chosen (server
|
|
50
50
|
// instruction #9): auto-routing hides the model choice and the generation
|
|
51
51
|
// metadata used to read just "Auto".
|
|
52
|
-
function buildCatalogStructured(models, type) {
|
|
52
|
+
function buildCatalogStructured(models, type, compact) {
|
|
53
53
|
const groups = [];
|
|
54
54
|
const byName = new Map();
|
|
55
55
|
const isAuto = (m) => /^auto$|smart.select/i.test(String(m.name || '')) || /smart-select|k_auto/i.test(String(m.identifier || ''));
|
|
@@ -78,6 +78,7 @@ function buildCatalogStructured(models, type) {
|
|
|
78
78
|
widget: 'catalog',
|
|
79
79
|
title: 'Kolbo AI Models' + (type ? ' — ' + type : ''),
|
|
80
80
|
total_available: models.length,
|
|
81
|
+
compact: compact === true,
|
|
81
82
|
groups,
|
|
82
83
|
};
|
|
83
84
|
}
|
|
@@ -91,11 +92,16 @@ function registerModelTools(server, client, options = {}) {
|
|
|
91
92
|
{
|
|
92
93
|
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.'),
|
|
93
94
|
format: z.enum(['text', 'json']).optional().describe('Output format. "text" (default) returns a human-readable summary with the most-used caps. "json" returns the raw model documents from the API — use this when you need to programmatically verify caps (max_reference_images, max_visual_dna, max_video_duration, supported_aspect_ratios, etc.) before passing an array/value that might exceed a model-specific limit. The JSON form is the source of truth; the text form is a convenience preview.'),
|
|
94
|
-
display_catalog: z.boolean().optional().describe('Set true
|
|
95
|
+
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.')
|
|
95
96
|
},
|
|
96
97
|
async ({ type, format, display_catalog }) => {
|
|
97
|
-
//
|
|
98
|
-
//
|
|
98
|
+
// The tool DECLARATION always carries widget meta, so hosts that mount
|
|
99
|
+
// from tools/list (Claude Code desktop) prepare an iframe on EVERY call.
|
|
100
|
+
// Returning plain text for internal lookups left that iframe with no
|
|
101
|
+
// data — a dead, empty "Widget from Kolbo list_models" shell. Always
|
|
102
|
+
// ship structuredContent; `compact` tells the widget to render a single
|
|
103
|
+
// "Browse models" row (expandable) instead of the full catalog, which is
|
|
104
|
+
// what display_catalog was really asking for.
|
|
99
105
|
const showCatalog = display_catalog === true;
|
|
100
106
|
const path = type ? `/v1/models?type=${encodeURIComponent(type)}` : '/v1/models';
|
|
101
107
|
const result = await client.get(path);
|
|
@@ -106,7 +112,7 @@ function registerModelTools(server, client, options = {}) {
|
|
|
106
112
|
// resolution multipliers, supports_* flags, prompt-length limits, etc.).
|
|
107
113
|
if (format === 'json') {
|
|
108
114
|
const text = JSON.stringify({ count: result.count, models: result.models }, null, 2);
|
|
109
|
-
if (ui()
|
|
115
|
+
if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type, !showCatalog));
|
|
110
116
|
return { content: [{ type: 'text', text }] };
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -277,7 +283,7 @@ function registerModelTools(server, client, options = {}) {
|
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
const text = `Available models (${result.count}):\n\n${sections.join('\n\n')}\n\nUse the "identifier" value as the "model" parameter in generate tools. For programmatic cap validation, re-call with format: "json".`;
|
|
280
|
-
if (ui()
|
|
286
|
+
if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type, !showCatalog));
|
|
281
287
|
return { content: [{ type: 'text', text }] };
|
|
282
288
|
}
|
|
283
289
|
);
|