@kolbo/mcp 1.30.10 → 1.31.1
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/bridge.js +10 -1
- package/src/apps/theme.js +11 -1
- package/src/apps/widgets/catalog.js +6 -1
- package/src/apps/widgets/generation.js +68 -9
- package/src/tools/models.js +8 -4
package/package.json
CHANGED
package/src/apps/bridge.js
CHANGED
|
@@ -33,7 +33,7 @@ const BRIDGE_JS = `
|
|
|
33
33
|
var initialized = false;
|
|
34
34
|
var queue = []; // deferred host-bound sends until initialized
|
|
35
35
|
var hostContext = null;
|
|
36
|
-
var readyFns = [], toolResultFns = [], themeFns = [];
|
|
36
|
+
var readyFns = [], toolResultFns = [], themeFns = [], toolInputFns = [];
|
|
37
37
|
|
|
38
38
|
function post(msg) { window.parent.postMessage(msg, '*'); }
|
|
39
39
|
|
|
@@ -66,6 +66,10 @@ const BRIDGE_JS = `
|
|
|
66
66
|
}
|
|
67
67
|
if (m.method === 'ui/notifications/tool-result') {
|
|
68
68
|
toolResultFns.forEach(function (f) { try { f(m.params || {}); } catch (e) {} });
|
|
69
|
+
} else if (m.method === 'ui/notifications/tool-input' || m.method === 'ui/notifications/tool-input-partial') {
|
|
70
|
+
// Fires while the tool is still RUNNING — lets widgets show a real
|
|
71
|
+
// "preparing" state instead of a blank card until the result lands.
|
|
72
|
+
toolInputFns.forEach(function (f) { try { f((m.params && m.params.arguments) || {}); } catch (e) {} });
|
|
69
73
|
} else if (m.method === 'ui/notifications/host-context-changed') {
|
|
70
74
|
hostContext = (m.params && m.params.hostContext) || m.params || hostContext;
|
|
71
75
|
themeFns.forEach(function (f) { try { f(hostContext); } catch (e) {} });
|
|
@@ -132,6 +136,7 @@ const BRIDGE_JS = `
|
|
|
132
136
|
window.kolbo = {
|
|
133
137
|
ready: function (f) { if (initialized) f(hostContext); else readyFns.push(f); },
|
|
134
138
|
onToolResult: function (f) { toolResultFns.push(f); },
|
|
139
|
+
onToolInput: function (f) { toolInputFns.push(f); },
|
|
135
140
|
onThemeChange: function (f) { themeFns.push(f); },
|
|
136
141
|
callTool: function (name, args) { return request('tools/call', { name: name, arguments: args || {} }); },
|
|
137
142
|
sendMessage: function (text) {
|
|
@@ -141,6 +146,10 @@ const BRIDGE_JS = `
|
|
|
141
146
|
updateModelContext: function (text) {
|
|
142
147
|
return request('ui/update-model-context', { content: [{ type: 'text', text: text }] });
|
|
143
148
|
},
|
|
149
|
+
// Resolves with the mode the host ACTUALLY granted ('inline'|'fullscreen'|'pip').
|
|
150
|
+
requestDisplayMode: function (mode) {
|
|
151
|
+
return request('ui/request-display-mode', { mode: mode });
|
|
152
|
+
},
|
|
144
153
|
notifySize: notifySize,
|
|
145
154
|
hostContext: function () { return hostContext; }
|
|
146
155
|
};
|
package/src/apps/theme.js
CHANGED
|
@@ -161,11 +161,21 @@ body {
|
|
|
161
161
|
|
|
162
162
|
/* Keep the whole completed card under the host's iframe height cap (~800px):
|
|
163
163
|
header + prompt + chips + viewer + thumbs + actions + footer must all fit,
|
|
164
|
-
or claude.ai adds an inner scrollbar. Click the image to
|
|
164
|
+
or claude.ai adds an inner scrollbar. Click the image to expand in-Claude. */
|
|
165
165
|
.k-viewer { margin-bottom: 10px; }
|
|
166
166
|
.k-viewer img, .k-viewer video { display: block; width: 100%; max-height: 340px; object-fit: contain;
|
|
167
167
|
border-radius: 12px; background: #000; border: 1px solid var(--border); cursor: zoom-in; }
|
|
168
168
|
.k-viewer video { cursor: default; }
|
|
169
|
+
|
|
170
|
+
/* ---- Fullscreen (ui/request-display-mode granted) ---- */
|
|
171
|
+
html.k-fullscreen, html.k-fullscreen body { height: 100%; }
|
|
172
|
+
html.k-fullscreen .k-card { height: 100%; display: flex; flex-direction: column; border-radius: 0; }
|
|
173
|
+
html.k-fullscreen .k-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
|
174
|
+
html.k-fullscreen .k-viewer { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
|
|
175
|
+
html.k-fullscreen .k-viewer img, html.k-fullscreen .k-viewer video {
|
|
176
|
+
max-height: 100%; max-width: 100%; width: auto; margin: 0 auto; cursor: zoom-out; }
|
|
177
|
+
html.k-fullscreen .k-thumbs .k-thumb { width: 64px; height: 64px; }
|
|
178
|
+
.k-expand-hint { display: none; }
|
|
169
179
|
.k-thumbs { display: flex; gap: 6px; margin: 10px 0 2px; }
|
|
170
180
|
.k-thumbs .k-thumb { width: 48px; height: 48px; border-radius: 8px; overflow: hidden; cursor: pointer;
|
|
171
181
|
border: 2px solid transparent; opacity: 0.75; transition: all 150ms var(--smooth); flex: none; }
|
|
@@ -64,7 +64,12 @@ function boot(sc) {
|
|
|
64
64
|
|
|
65
65
|
window.kolbo.onToolResult(function (result) {
|
|
66
66
|
var sc = result.structuredContent || structured(result);
|
|
67
|
-
if (sc) boot(sc);
|
|
67
|
+
if (sc && sc.groups) return boot(sc);
|
|
68
|
+
// Internal lookup (Claude checking constraints mid-task) — no catalog data
|
|
69
|
+
// was attached on purpose. Collapse instead of showing a dead card.
|
|
70
|
+
var card = document.querySelector('.k-card');
|
|
71
|
+
if (card) card.style.display = 'none';
|
|
72
|
+
window.kolbo.notifySize();
|
|
68
73
|
});
|
|
69
74
|
`;
|
|
70
75
|
|
|
@@ -173,10 +173,12 @@ function renderImages(sc, urls) {
|
|
|
173
173
|
// instead of a broken empty viewer.
|
|
174
174
|
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt="" onerror="window.__imgFail && window.__imgFail()"></div>';
|
|
175
175
|
window.__imgFail = function () { renderLinks(urls); window.kolbo.notifySize(); };
|
|
176
|
-
//
|
|
176
|
+
// Click → expand into an in-Claude fullscreen viewer (all actions stay
|
|
177
|
+
// available); click again (or Exit) collapses back. Hosts that refuse
|
|
178
|
+
// fullscreen fall back to opening the original in a new tab.
|
|
177
179
|
setTimeout(function () {
|
|
178
180
|
var img = el('main-img');
|
|
179
|
-
if (img) img.onclick =
|
|
181
|
+
if (img) img.onclick = toggleFullscreen;
|
|
180
182
|
}, 0);
|
|
181
183
|
var thumbs = '';
|
|
182
184
|
if (urls.length > 1) {
|
|
@@ -257,11 +259,44 @@ function renderError(msg) {
|
|
|
257
259
|
el('stage').innerHTML = '<div class="k-error">⚠ ' + esc(msg) + '</div>';
|
|
258
260
|
el('actions').innerHTML = '<button class="k-btn" id="retry-btn">↻ Try Again</button>';
|
|
259
261
|
el('retry-btn').onclick = function () {
|
|
260
|
-
|
|
262
|
+
var what = (state && TOOL_TITLES[state.tool]) || 'generation';
|
|
263
|
+
window.kolbo.sendMessage('Please retry that ' + what.toLowerCase() + ' — it failed with: ' + msg);
|
|
261
264
|
};
|
|
262
265
|
window.kolbo.notifySize();
|
|
263
266
|
}
|
|
264
267
|
|
|
268
|
+
/* ---------- fullscreen viewer ---------- */
|
|
269
|
+
var isFullscreen = false;
|
|
270
|
+
function toggleFullscreen() {
|
|
271
|
+
var want = isFullscreen ? 'inline' : 'fullscreen';
|
|
272
|
+
window.kolbo.requestDisplayMode(want).then(function (res) {
|
|
273
|
+
var granted = res && res.mode;
|
|
274
|
+
if (granted === 'fullscreen') { isFullscreen = true; applyFullscreen(true); }
|
|
275
|
+
else if (granted === 'inline' || isFullscreen) { isFullscreen = false; applyFullscreen(false); }
|
|
276
|
+
else if (!isFullscreen) {
|
|
277
|
+
// Host refused fullscreen — degrade to opening the original file.
|
|
278
|
+
window.kolbo.openLink(state.urls && state.urls[selected]);
|
|
279
|
+
}
|
|
280
|
+
}).catch(function () {
|
|
281
|
+
if (!isFullscreen) window.kolbo.openLink(state.urls && state.urls[selected]);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function applyFullscreen(on) {
|
|
285
|
+
document.documentElement.classList.toggle('k-fullscreen', on);
|
|
286
|
+
var c = el('phase-chip');
|
|
287
|
+
if (on) {
|
|
288
|
+
c.style.display = '';
|
|
289
|
+
c.innerHTML = '✕ ' + esc('Exit');
|
|
290
|
+
c.style.cursor = 'pointer';
|
|
291
|
+
c.onclick = toggleFullscreen;
|
|
292
|
+
} else {
|
|
293
|
+
c.style.display = 'none';
|
|
294
|
+
c.onclick = null;
|
|
295
|
+
c.style.cursor = '';
|
|
296
|
+
}
|
|
297
|
+
window.kolbo.notifySize();
|
|
298
|
+
}
|
|
299
|
+
|
|
265
300
|
function setPhaseChip(text, spinning) {
|
|
266
301
|
var c = el('phase-chip');
|
|
267
302
|
if (!text) { c.style.display = 'none'; return; }
|
|
@@ -333,17 +368,41 @@ function openPromptRow(placeholder, onSend) {
|
|
|
333
368
|
window.kolbo.notifySize();
|
|
334
369
|
}
|
|
335
370
|
|
|
371
|
+
/* ---------- pre-result "Preparing" state ----------
|
|
372
|
+
The host mounts this iframe as soon as the tool is CALLED; the result can
|
|
373
|
+
take many seconds (model resolution, file upload, submit). Show a live
|
|
374
|
+
shell immediately instead of a blank card. */
|
|
375
|
+
function bootPre(toolName, args) {
|
|
376
|
+
if (state) return; // real data already arrived
|
|
377
|
+
el('tool-title').textContent = TOOL_TITLES[toolName] || 'Generation';
|
|
378
|
+
if (args && (args.prompt || args.text)) {
|
|
379
|
+
el('prompt').textContent = args.prompt || args.text;
|
|
380
|
+
el('prompt').style.display = '';
|
|
381
|
+
}
|
|
382
|
+
setPhaseChip('Preparing', true);
|
|
383
|
+
if (!el('stage').innerHTML) {
|
|
384
|
+
el('stage').innerHTML = '<div class="k-gen-grid n1"><div class="k-skel video" style="min-height:100px;max-height:140px"></div></div>';
|
|
385
|
+
}
|
|
386
|
+
window.kolbo.notifySize();
|
|
387
|
+
}
|
|
388
|
+
|
|
336
389
|
/* ---------- wire host events ---------- */
|
|
337
390
|
window.kolbo.onToolResult(function (result) {
|
|
338
391
|
var sc = result.structuredContent || structured(result);
|
|
339
|
-
if (sc) boot(sc);
|
|
392
|
+
if (sc && (sc.phase || sc.widget)) return boot(sc);
|
|
393
|
+
// Tool errored (or returned plain text): show it instead of a dead blank card.
|
|
394
|
+
var txt = '';
|
|
395
|
+
try { txt = (result.content || []).filter(function (c) { return c.type === 'text'; }).map(function (c) { return c.text; }).join(' '); } catch (e) {}
|
|
396
|
+
if (result.isError || /error|failed/i.test(txt)) {
|
|
397
|
+
renderError((txt || 'The request failed.').slice(0, 300));
|
|
398
|
+
}
|
|
340
399
|
});
|
|
400
|
+
window.kolbo.onToolInput(function (args) { bootPre(null, args); });
|
|
341
401
|
window.kolbo.ready(function (ctx) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (sc) boot(sc);
|
|
402
|
+
var info = ctx && ctx.toolInfo;
|
|
403
|
+
if (!state && info) {
|
|
404
|
+
if (info.result && info.result.structuredContent) return boot(info.result.structuredContent);
|
|
405
|
+
bootPre(info.tool && info.tool.name, null);
|
|
347
406
|
}
|
|
348
407
|
});
|
|
349
408
|
`;
|
package/src/tools/models.js
CHANGED
|
@@ -96,9 +96,13 @@ function registerModelTools(server, client, options = {}) {
|
|
|
96
96
|
'List available AI models on Kolbo. Filter by `type` to narrow to a generation type, and pass `format: "json"` to get the raw model documents (every constraint field, useful for programmatic comparison / cap validation before submitting a generation). Default `format: "text"` returns the human-readable summary.',
|
|
97
97
|
{
|
|
98
98
|
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.'),
|
|
99
|
-
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.')
|
|
99
|
+
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.'),
|
|
100
|
+
display_catalog: z.boolean().optional().describe('Set true ONLY when the USER explicitly asked to see/browse the available models — it renders a visual model catalog to them. Leave unset for internal lookups (verifying a model name, checking caps before a generation): those should stay invisible to the user.')
|
|
100
101
|
},
|
|
101
|
-
async ({ type, format }) => {
|
|
102
|
+
async ({ type, format, display_catalog }) => {
|
|
103
|
+
// Widget only when the user asked to browse — internal validation
|
|
104
|
+
// lookups were spamming the chat with a full catalog card.
|
|
105
|
+
const showCatalog = display_catalog === true;
|
|
102
106
|
const path = type ? `/v1/models?type=${encodeURIComponent(type)}` : '/v1/models';
|
|
103
107
|
const result = await client.get(path);
|
|
104
108
|
|
|
@@ -108,7 +112,7 @@ function registerModelTools(server, client, options = {}) {
|
|
|
108
112
|
// resolution multipliers, supports_* flags, prompt-length limits, etc.).
|
|
109
113
|
if (format === 'json') {
|
|
110
114
|
const text = JSON.stringify({ count: result.count, models: result.models }, null, 2);
|
|
111
|
-
if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
|
|
115
|
+
if (ui() && showCatalog) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
|
|
112
116
|
return { content: [{ type: 'text', text }] };
|
|
113
117
|
}
|
|
114
118
|
|
|
@@ -279,7 +283,7 @@ function registerModelTools(server, client, options = {}) {
|
|
|
279
283
|
}
|
|
280
284
|
|
|
281
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".`;
|
|
282
|
-
if (ui()) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
|
|
286
|
+
if (ui() && showCatalog) return uiResult(UI.catalog, text, buildCatalogStructured(result.models, type));
|
|
283
287
|
return { content: [{ type: 'text', text }] };
|
|
284
288
|
}
|
|
285
289
|
);
|