@kolbo/mcp 1.30.1 → 1.30.2
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 +6 -2
- package/src/apps/index.js +30 -3
- package/src/apps/widgets/catalog.js +11 -9
- package/src/apps/widgets/generation.js +5 -3
- package/src/tools/models.js +25 -3
package/package.json
CHANGED
package/src/apps/bridge.js
CHANGED
|
@@ -91,9 +91,13 @@ const BRIDGE_JS = `
|
|
|
91
91
|
}).catch(function () { /* host without apps support — widget stays static */ });
|
|
92
92
|
|
|
93
93
|
function notifySize() {
|
|
94
|
-
|
|
94
|
+
// Measure the widget card itself — documentElement.scrollHeight over-reports
|
|
95
|
+
// in some hosts and leaves a huge empty iframe below the card.
|
|
96
|
+
var card = document.querySelector('.k-card');
|
|
97
|
+
var rect = card ? card.getBoundingClientRect() : null;
|
|
98
|
+
var height = rect ? Math.ceil(rect.bottom + 8) : document.documentElement.scrollHeight;
|
|
95
99
|
notify('ui/notifications/size-changed', {
|
|
96
|
-
width:
|
|
100
|
+
width: document.documentElement.scrollWidth, height: height
|
|
97
101
|
});
|
|
98
102
|
}
|
|
99
103
|
|
package/src/apps/index.js
CHANGED
|
@@ -42,6 +42,26 @@ function widgetHtml(uri) {
|
|
|
42
42
|
return htmlCache.get(uri);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Hosts apply a deny-by-default CSP to widget iframes — without this
|
|
46
|
+
// declaration EVERY external asset (generated images/videos on the CDN, model
|
|
47
|
+
// icons, Google Fonts) is silently blocked. resourceDomains maps to
|
|
48
|
+
// img/script/style/font/media-src; connectDomains to connect-src.
|
|
49
|
+
const WIDGET_CSP = {
|
|
50
|
+
resourceDomains: [
|
|
51
|
+
'https://*.kolbo.ai', // media.kolbo.ai CDN + app.kolbo.ai model icons
|
|
52
|
+
'https://*.digitaloceanspaces.com', // DO Spaces buckets (all envs)
|
|
53
|
+
'https://*.cdn.digitaloceanspaces.com', // DO Spaces CDN endpoints
|
|
54
|
+
'https://fonts.googleapis.com', // Inter / JetBrains Mono stylesheet
|
|
55
|
+
'https://fonts.gstatic.com', // font files
|
|
56
|
+
'https://images.pexels.com', // stock thumbnails
|
|
57
|
+
'https://*.pexels.com',
|
|
58
|
+
'https://*.pixabay.com',
|
|
59
|
+
'https://*.sketchfab.com',
|
|
60
|
+
'https://*.cloudfront.net', // provider-hosted previews
|
|
61
|
+
],
|
|
62
|
+
connectDomains: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
45
65
|
/** Register all Kolbo widget resources on an McpServer. */
|
|
46
66
|
function registerApps(server) {
|
|
47
67
|
for (const [uri, name] of [
|
|
@@ -50,9 +70,16 @@ function registerApps(server) {
|
|
|
50
70
|
[UI.catalog, 'Kolbo Model Catalog Widget'],
|
|
51
71
|
[UI.transcript, 'Kolbo Transcription Widget'],
|
|
52
72
|
]) {
|
|
53
|
-
registerAppResource(
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
registerAppResource(
|
|
74
|
+
server, name, uri,
|
|
75
|
+
{ mimeType: RESOURCE_MIME_TYPE, _meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } } },
|
|
76
|
+
async () => ({
|
|
77
|
+
contents: [{
|
|
78
|
+
uri, mimeType: RESOURCE_MIME_TYPE, text: widgetHtml(uri),
|
|
79
|
+
_meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } },
|
|
80
|
+
}],
|
|
81
|
+
})
|
|
82
|
+
);
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
85
|
|
|
@@ -34,20 +34,22 @@ function boot(sc) {
|
|
|
34
34
|
if (!sc || !sc.groups) return;
|
|
35
35
|
state = sc;
|
|
36
36
|
el('title').textContent = sc.title || 'AI Models';
|
|
37
|
-
var
|
|
37
|
+
var shown = sc.groups.reduce(function (n, g) { return n + g.models.length; }, 0);
|
|
38
38
|
el('count-chip').style.display = '';
|
|
39
|
-
el('count-chip').textContent =
|
|
39
|
+
el('count-chip').textContent = sc.total_available && sc.total_available > shown
|
|
40
|
+
? 'top picks · ' + sc.total_available + ' total'
|
|
41
|
+
: shown + ' models';
|
|
40
42
|
el('stage').innerHTML = sc.groups.map(function (g, gi) {
|
|
41
|
-
return '<div style="margin-bottom:
|
|
42
|
-
'<div style="font-size:
|
|
43
|
+
return '<div style="margin-bottom:10px">' +
|
|
44
|
+
'<div style="font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-faint);margin-bottom:6px">' + esc(g.name) + '</div>' +
|
|
43
45
|
g.models.map(function (m, mi) {
|
|
44
|
-
return '<div class="k-audio-row" data-g="' + gi + '" data-m="' + mi + '" style="cursor:pointer">' +
|
|
45
|
-
(m.icon ? '<img class="k-audio-art" style="width:
|
|
46
|
+
return '<div class="k-audio-row" data-g="' + gi + '" data-m="' + mi + '" style="cursor:pointer;padding:6px 8px;margin-bottom:4px">' +
|
|
47
|
+
(m.icon ? '<img class="k-audio-art" style="width:24px;height:24px;border-radius:6px" src="' + esc(m.icon) + '" onerror="this.outerHTML=monogram(\\'' + esc(m.name).replace(/'/g, '') + '\\')">'
|
|
46
48
|
: '<span style="flex:none">' + monogram(m.name) + '</span>') +
|
|
47
|
-
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(m.name) + '</div>' +
|
|
48
|
-
(m.description ? '<div class="k-audio-sub">' + esc(m.description) + '</div>' : '') + '</div>' +
|
|
49
|
+
'<div class="k-audio-meta"><div class="k-audio-title" style="font-size:12px">' + esc(m.name) + '</div>' +
|
|
50
|
+
(m.description ? '<div class="k-audio-sub" style="font-size:10.5px">' + esc(m.description) + '</div>' : '') + '</div>' +
|
|
49
51
|
'<div style="display:flex;gap:4px;flex:none">' + (m.chips || []).slice(0, 3).map(function (c) {
|
|
50
|
-
return '<span class="k-chip">' + esc(c) + '</span>';
|
|
52
|
+
return '<span class="k-chip" style="padding:2px 7px;font-size:10px">' + esc(c) + '</span>';
|
|
51
53
|
}).join('') + '</div></div>';
|
|
52
54
|
}).join('') + '</div>';
|
|
53
55
|
}).join('');
|
|
@@ -98,7 +98,7 @@ function renderChips(sc) {
|
|
|
98
98
|
if (s.voice) h += chip('🎤 ' + esc(s.voice));
|
|
99
99
|
if (s.mode) h += chip(esc(s.mode));
|
|
100
100
|
if (sc.count > 1) h += chip('×' + sc.count);
|
|
101
|
-
if (sc.reference_image) h += '<img class="k-ref-thumb" src="' + esc(sc.reference_image) + '" alt="
|
|
101
|
+
if (sc.reference_image) h += '<img class="k-ref-thumb" src="' + esc(sc.reference_image) + '" alt="" title="Reference image" onerror="this.style.display=\'none\'">';
|
|
102
102
|
el('chips').innerHTML = h;
|
|
103
103
|
}
|
|
104
104
|
function chip(inner) { return '<span class="k-chip">' + inner + '</span>'; }
|
|
@@ -166,7 +166,6 @@ function poll(sc) {
|
|
|
166
166
|
} else if (stateName === 'failed' || stateName === 'error' || stateName === 'cancelled') {
|
|
167
167
|
renderError(st.error || 'Generation ' + stateName);
|
|
168
168
|
} else {
|
|
169
|
-
if (st.progress != null) el('status-text').textContent = 'Generating… ' + Math.round(st.progress) + '%';
|
|
170
169
|
schedulePoll(sc);
|
|
171
170
|
}
|
|
172
171
|
}).catch(function () { schedulePoll(sc); });
|
|
@@ -196,7 +195,10 @@ function renderResult(sc) {
|
|
|
196
195
|
|
|
197
196
|
function renderImages(sc, urls) {
|
|
198
197
|
selected = Math.min(selected, urls.length - 1);
|
|
199
|
-
|
|
198
|
+
// If the host CSP still blocks the image, degrade to open-in-browser rows
|
|
199
|
+
// instead of a broken empty viewer.
|
|
200
|
+
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt="" onerror="window.__imgFail && window.__imgFail()"></div>';
|
|
201
|
+
window.__imgFail = function () { renderLinks(urls); window.kolbo.notifySize(); };
|
|
200
202
|
var thumbs = '';
|
|
201
203
|
if (urls.length > 1) {
|
|
202
204
|
thumbs = '<div class="k-thumbs">' + urls.map(function (u, i) {
|
package/src/tools/models.js
CHANGED
|
@@ -43,14 +43,34 @@ function modelChips(m) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// structuredContent for ui://kolbo/catalog.html — see src/apps/widgets/catalog.js
|
|
46
|
+
// Deliberately CURATED, not exhaustive: the widget is a picker, not a database.
|
|
47
|
+
// Smart Select is pinned first, each group shows the recommended/new models
|
|
48
|
+
// (max 6), and the total count chip tells the user how many exist overall.
|
|
46
49
|
function buildCatalogStructured(models, type) {
|
|
47
50
|
const groups = [];
|
|
48
51
|
const byName = new Map();
|
|
49
|
-
|
|
52
|
+
const isAuto = (m) => /^auto$|smart.select/i.test(String(m.name || '')) || /smart-select|k_auto/i.test(String(m.identifier || ''));
|
|
53
|
+
|
|
54
|
+
// Pinned Smart Select entry (replaces the confusing "Other / Auto" row).
|
|
55
|
+
const smartSelect = {
|
|
56
|
+
name: 'Smart Select',
|
|
57
|
+
icon: null,
|
|
58
|
+
description: 'Recommended — automatically routes to the best model for your prompt, quality and cost.',
|
|
59
|
+
chips: ['AUTO'],
|
|
60
|
+
use_hint: 'Generate with Smart Select (omit the model) — ask me what I want to create first.',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Recommended + new models float to the top of each group.
|
|
64
|
+
const ranked = [...models].filter((m) => !isAuto(m)).sort((a, b) => {
|
|
65
|
+
const score = (m) => (m.recommended ? 2 : 0) + (m.new_model || m.newModel ? 1 : 0);
|
|
66
|
+
return score(b) - score(a);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
for (const m of ranked) {
|
|
50
70
|
const name = groupNameFor(m);
|
|
51
71
|
let g = byName.get(name);
|
|
52
72
|
if (!g) { g = { name, models: [] }; byName.set(name, g); groups.push(g); }
|
|
53
|
-
if (g.models.length >=
|
|
73
|
+
if (g.models.length >= 6) continue; // curated cap — full list lives in the text payload
|
|
54
74
|
g.models.push({
|
|
55
75
|
name: m.name,
|
|
56
76
|
icon: m.avatar
|
|
@@ -61,10 +81,12 @@ function buildCatalogStructured(models, type) {
|
|
|
61
81
|
use_hint: `Generate with the "${m.name}" model — ask me what I want to create first.`,
|
|
62
82
|
});
|
|
63
83
|
}
|
|
84
|
+
groups.sort((a, b) => (a.name === 'Other' ? 1 : b.name === 'Other' ? -1 : 0));
|
|
64
85
|
return {
|
|
65
86
|
widget: 'catalog',
|
|
66
87
|
title: 'Kolbo AI Models' + (type ? ' — ' + type : ''),
|
|
67
|
-
|
|
88
|
+
total_available: models.length,
|
|
89
|
+
groups: [{ name: 'Recommended', models: [smartSelect] }, ...groups],
|
|
68
90
|
};
|
|
69
91
|
}
|
|
70
92
|
|