@kolbo/mcp 1.82.3 → 1.82.6
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 +5 -0
- package/src/apps/theme.js +2 -0
- package/src/apps/widgets/generation.js +20 -2
- package/src/apps/widgets/plans.js +14 -104
- package/src/tools/generate.js +15 -1
package/package.json
CHANGED
package/src/apps/index.js
CHANGED
|
@@ -97,6 +97,11 @@ const WIDGET_CSP = {
|
|
|
97
97
|
connectDomains: [
|
|
98
98
|
'https://api.kolbo.ai',
|
|
99
99
|
],
|
|
100
|
+
// Nested iframes. Empty/omitted → frame-src 'none' and the live pricing
|
|
101
|
+
// embed inside the upgrade card is a blank box.
|
|
102
|
+
frameDomains: [
|
|
103
|
+
'https://app.kolbo.ai',
|
|
104
|
+
],
|
|
100
105
|
};
|
|
101
106
|
|
|
102
107
|
/** Register all Kolbo widget resources on an McpServer. */
|
package/src/apps/theme.js
CHANGED
|
@@ -183,6 +183,8 @@ html.k-peek-fs .k-peek img, html.k-peek-fs .k-peek video { max-height: calc(100v
|
|
|
183
183
|
.k-pack-head { margin: 14px 0 6px; font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
|
|
184
184
|
text-transform: uppercase; color: var(--text-faint); }
|
|
185
185
|
.k-pack-row { margin-bottom: 6px; }
|
|
186
|
+
.k-pricing-frame { display: block; width: 100%; height: 560px; border: 0; border-radius: 12px;
|
|
187
|
+
background: #0f0f0f; }
|
|
186
188
|
/* Visual DNA chips: the character's face, so you can see WHICH DNA is locked in. */
|
|
187
189
|
.k-dna-face { width: 18px; height: 18px; border-radius: 999px; object-fit: cover; margin-left: -3px; background: var(--border-strong); }
|
|
188
190
|
.k-dna-stack .k-dna-stack-item { display: inline-flex; }
|
|
@@ -794,7 +794,7 @@ function fillBatchCell(sc, i, g) {
|
|
|
794
794
|
// the user is meant to watch, and without it the tile was a muted poster with
|
|
795
795
|
// no way to play, seek or unmute until the whole batch finished and repainted.
|
|
796
796
|
cell.innerHTML = (sc.kind === 'video'
|
|
797
|
-
? '<video class="k-cell-fill" src="' + esc(
|
|
797
|
+
? '<video class="k-cell-fill" src="' + esc(playableAt(r, r.urls || [], 0)) + '"' + (r.thumbnail_url ? ' poster="' + esc(r.thumbnail_url) + '"' : '') + ' controls playsinline preload="metadata"></video>'
|
|
798
798
|
: '<img class="k-cell-fill" src="' + esc(u) + '" alt="">') + cap;
|
|
799
799
|
window.kolbo.notifySize();
|
|
800
800
|
}
|
|
@@ -862,10 +862,28 @@ function renderImages(sc, urls) {
|
|
|
862
862
|
});
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
+
/**
|
|
866
|
+
* The url to PLAY, which is not always the url to DOWNLOAD.
|
|
867
|
+
*
|
|
868
|
+
* Seedance 2.5 at 1080p returns HEVC Main 10 (10-bit). Chrome and Edge decode HEVC in
|
|
869
|
+
* hardware only — with no software fallback — and Firefox on Windows not at all, so the
|
|
870
|
+
* widget showed a black frame with sound. The API therefore returns playback_urls
|
|
871
|
+
* alongside urls whenever it had to build an H.264 proxy.
|
|
872
|
+
*
|
|
873
|
+
* urls stays the MASTER and is what every download button must keep using: handing
|
|
874
|
+
* someone the 8-bit proxy for a file they paid the 1080p multiplier for is a silent
|
|
875
|
+
* quality downgrade. Backend: kolbo-api/src/services/webPlaybackProxy.js
|
|
876
|
+
*/
|
|
877
|
+
function playableAt(src, urls, i) {
|
|
878
|
+
var proxies = (src && src.playback_urls) || [];
|
|
879
|
+
return proxies[i] || (urls || [])[i];
|
|
880
|
+
}
|
|
881
|
+
|
|
865
882
|
function renderVideo(sc, urls) {
|
|
866
883
|
// preload="none" behind a poster: the card shows the still until the user
|
|
867
884
|
// hits play, instead of pulling the video header on mount.
|
|
868
|
-
|
|
885
|
+
// PLAY the proxy when there is one; the download button below keeps urls[0] (master).
|
|
886
|
+
el('stage').innerHTML = '<div class="k-viewer"><video id="main-video" src="' + esc(playableAt(sc, urls, 0)) + '"' +
|
|
869
887
|
(sc.thumbnail_url ? ' poster="' + esc(sc.thumbnail_url) + '" preload="none"' : ' preload="metadata"') + ' controls playsinline></video>' +
|
|
870
888
|
dlBtnHTML(urls[0]) + '</div>';
|
|
871
889
|
wireDlButtons(el('stage'));
|
|
@@ -6,11 +6,11 @@ const { widgetPage } = require('../html');
|
|
|
6
6
|
* Plans / upgrade widget — shown by `show_plans`, and by any generation the
|
|
7
7
|
* server refused for credits.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* The live pricing UI lives on app.kolbo.ai. This card iframes
|
|
10
|
+
* `/pricing/embed` (same PlanList / PricingCard as the site) so a price or
|
|
11
|
+
* perk change does not require an MCP republish. Buy/Subscribe on that page
|
|
12
|
+
* open https://app.kolbo.ai/pricing in a new tab — Stripe and the user's
|
|
13
|
+
* session must not run inside a third-party host iframe.
|
|
14
14
|
*
|
|
15
15
|
* structuredContent contract:
|
|
16
16
|
* {
|
|
@@ -19,8 +19,8 @@ const { widgetPage } = require('../html');
|
|
|
19
19
|
* balance, required, shortfall, // when reason === 'insufficient_credits'
|
|
20
20
|
* current_plan: { key, name },
|
|
21
21
|
* plans: [{ key, name, interval, credits, price, original_price,
|
|
22
|
-
* discount_percent, promo_text, currency }],
|
|
23
|
-
* credit_packs: [
|
|
22
|
+
* discount_percent, promo_text, currency, top_up_discount }],
|
|
23
|
+
* credit_packs: [{ …, subscriber_price, is_subscriber }],
|
|
24
24
|
* pricing_url
|
|
25
25
|
* }
|
|
26
26
|
*/
|
|
@@ -35,10 +35,6 @@ const BODY = `
|
|
|
35
35
|
</div>
|
|
36
36
|
<div class="k-body">
|
|
37
37
|
<div class="k-error" id="notice" style="display:none"></div>
|
|
38
|
-
<div class="k-plan-toggle" id="toggle" style="display:none">
|
|
39
|
-
<button type="button" class="k-toggle-btn" data-interval="month">Monthly</button>
|
|
40
|
-
<button type="button" class="k-toggle-btn" data-interval="year">Annual</button>
|
|
41
|
-
</div>
|
|
42
38
|
<div id="stage"></div>
|
|
43
39
|
<div class="k-actions" id="actions"></div>
|
|
44
40
|
</div>
|
|
@@ -50,7 +46,7 @@ const BODY = `
|
|
|
50
46
|
|
|
51
47
|
const SCRIPT = `
|
|
52
48
|
var state = null;
|
|
53
|
-
var
|
|
49
|
+
var PRICING_EMBED = 'https://app.kolbo.ai/pricing/embed';
|
|
54
50
|
|
|
55
51
|
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
56
52
|
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
@@ -58,17 +54,6 @@ el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openL
|
|
|
58
54
|
function pricingUrl() {
|
|
59
55
|
return (state && state.pricing_url) || 'https://app.kolbo.ai/pricing';
|
|
60
56
|
}
|
|
61
|
-
function money(amount, currency) {
|
|
62
|
-
if (amount == null) return '';
|
|
63
|
-
var sym = String(currency || 'usd').toLowerCase() === 'usd' ? '$' : '';
|
|
64
|
-
var n = Math.round(Number(amount) * 100) / 100;
|
|
65
|
-
return sym + n + (sym ? '' : ' ' + String(currency || '').toUpperCase());
|
|
66
|
-
}
|
|
67
|
-
function perMonthNote(plan) {
|
|
68
|
-
if (plan.interval !== 'year' || plan.price == null) return '';
|
|
69
|
-
var monthly = Math.round((Number(plan.price) / 12) * 100) / 100;
|
|
70
|
-
return money(monthly, plan.currency) + '/mo, billed annually';
|
|
71
|
-
}
|
|
72
57
|
|
|
73
58
|
function boot(sc) {
|
|
74
59
|
if (!sc) return;
|
|
@@ -90,86 +75,12 @@ function boot(sc) {
|
|
|
90
75
|
: 'That generation needs more credits than you have left.');
|
|
91
76
|
}
|
|
92
77
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
el('toggle').style.display = '';
|
|
98
|
-
if (!intervals[interval]) interval = intervals.year ? 'year' : 'month';
|
|
99
|
-
} else {
|
|
100
|
-
interval = intervals.year ? 'year' : 'month';
|
|
101
|
-
}
|
|
102
|
-
wireToggle();
|
|
103
|
-
render();
|
|
104
|
-
window.kolbo.notifySize();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function wireToggle() {
|
|
108
|
-
Array.prototype.forEach.call(document.querySelectorAll('.k-toggle-btn'), function (b) {
|
|
109
|
-
b.onclick = function () { interval = b.getAttribute('data-interval'); render(); window.kolbo.notifySize(); };
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function render() {
|
|
114
|
-
Array.prototype.forEach.call(document.querySelectorAll('.k-toggle-btn'), function (b) {
|
|
115
|
-
b.classList.toggle('active', b.getAttribute('data-interval') === interval);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
var plans = (state.plans || []).filter(function (p) { return p.interval === interval; });
|
|
119
|
-
// Cheapest first so the ladder reads left-to-right. A zero-price plan is not
|
|
120
|
-
// an upgrade path, so it never takes a card slot.
|
|
121
|
-
plans = plans.filter(function (p) { return Number(p.price) > 0; });
|
|
122
|
-
plans.sort(function (a, b) { return (a.price || 0) - (b.price || 0); });
|
|
123
|
-
|
|
124
|
-
var current = state.current_plan && state.current_plan.key;
|
|
125
|
-
var html = plans.length
|
|
126
|
-
? '<div class="k-plan-grid">' + plans.map(function (p) { return planCard(p, current); }).join('') + '</div>'
|
|
127
|
-
: '<div class="k-empty">Plan details are on the pricing page</div>';
|
|
128
|
-
|
|
129
|
-
var packs = (state.credit_packs || []).filter(function (p) { return Number(p.price) > 0; });
|
|
130
|
-
if (packs.length) {
|
|
131
|
-
packs.sort(function (a, b) { return (a.price || 0) - (b.price || 0); });
|
|
132
|
-
html += '<div class="k-pack-head">One-time credit packs</div>' +
|
|
133
|
-
packs.slice(0, 4).map(function (p) {
|
|
134
|
-
return '<div class="k-audio-row k-pack-row"><div class="k-audio-meta">' +
|
|
135
|
-
'<div class="k-audio-title">' + esc(p.name || '') + '</div>' +
|
|
136
|
-
(p.credits != null ? '<div class="k-audio-sub">' + esc(String(p.credits)) + ' credits</div>' : '') +
|
|
137
|
-
'</div><span class="k-chip">' + esc(money(p.price, p.currency)) + '</span></div>';
|
|
138
|
-
}).join('');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
el('stage').innerHTML = html;
|
|
142
|
-
Array.prototype.forEach.call(el('stage').querySelectorAll('[data-buy]'), function (b) {
|
|
143
|
-
b.onclick = function () { window.kolbo.openLink(pricingUrl()); };
|
|
144
|
-
});
|
|
78
|
+
el('stage').innerHTML = '<iframe class="k-pricing-frame" src="' + PRICING_EMBED +
|
|
79
|
+
'" title="Kolbo plans" referrerpolicy="no-referrer-when-downgrade"></iframe>';
|
|
80
|
+
var frame = el('stage').querySelector('iframe');
|
|
81
|
+
if (frame) frame.onload = function () { window.kolbo.notifySize(); };
|
|
145
82
|
renderActions();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
function planCard(p, currentKey) {
|
|
149
|
-
var isCurrent = currentKey && p.key === currentKey;
|
|
150
|
-
var badges = '';
|
|
151
|
-
if (p.discount_percent) badges += '<span class="k-plan-badge">' + esc(String(p.discount_percent)) + '% OFF</span>';
|
|
152
|
-
if (isCurrent) badges += '<span class="k-plan-badge current">Current</span>';
|
|
153
|
-
|
|
154
|
-
var price = '<span class="k-plan-price">' + esc(money(p.price, p.currency)) + '</span>';
|
|
155
|
-
if (p.original_price && p.original_price > p.price) {
|
|
156
|
-
price = '<span class="k-plan-was">' + esc(money(p.original_price, p.currency)) + '</span> ' + price;
|
|
157
|
-
}
|
|
158
|
-
var note = perMonthNote(p);
|
|
159
|
-
|
|
160
|
-
return '<div class="k-plan' + (isCurrent ? ' current' : '') + '">' +
|
|
161
|
-
'<div class="k-plan-top"><span class="k-plan-name">' + esc(p.name || p.key || '') + '</span>' + badges + '</div>' +
|
|
162
|
-
(p.credits != null
|
|
163
|
-
? '<div class="k-plan-credits">' + ICONS.sparkle + ' ' + esc(String(p.credits)) + ' credits' +
|
|
164
|
-
(p.interval === 'month' ? '/mo' : p.interval === 'year' ? '/yr' : '') + '</div>'
|
|
165
|
-
: '') +
|
|
166
|
-
'<div class="k-plan-pricing">' + price + '</div>' +
|
|
167
|
-
(note ? '<div class="k-plan-note">' + esc(note) + '</div>' : '') +
|
|
168
|
-
(p.promo_text ? '<div class="k-plan-note">' + esc(p.promo_text) + '</div>' : '') +
|
|
169
|
-
(isCurrent
|
|
170
|
-
? '<button class="k-btn" disabled>Your plan</button>'
|
|
171
|
-
: '<button class="k-btn primary" data-buy="' + esc(p.key || '') + '">Get ' + esc(p.name || 'plan') + '</button>') +
|
|
172
|
-
'</div>';
|
|
83
|
+
window.kolbo.notifySize();
|
|
173
84
|
}
|
|
174
85
|
|
|
175
86
|
function renderActions() {
|
|
@@ -184,8 +95,7 @@ window.kolbo.onToolResult(function (result) {
|
|
|
184
95
|
});
|
|
185
96
|
window.kolbo.onToolInput(function () {
|
|
186
97
|
if (state) return;
|
|
187
|
-
el('stage').innerHTML = '<div class="k-
|
|
188
|
-
+ '<div class="k-skel square" style="min-height:120px"></div></div>';
|
|
98
|
+
el('stage').innerHTML = '<div class="k-skel square" style="min-height:240px"></div>';
|
|
189
99
|
window.kolbo.notifySize();
|
|
190
100
|
});
|
|
191
101
|
window.kolbo.ready(function (ctx) {
|
package/src/tools/generate.js
CHANGED
|
@@ -89,7 +89,7 @@ async function pollBatch(client, batch, { interval, timeout }, toolName) {
|
|
|
89
89
|
const polls = await Promise.all(batch.ids.map((id) => pollOrTimedOut(client, id, { interval, timeout })));
|
|
90
90
|
const generations = polls.map((p, i) => p.timedOut
|
|
91
91
|
? { prompt: batch.ok[i].prompt, generation_id: batch.ids[i], status: 'processing', note: 'Still running — call get_generation_status with wait=true to collect it.' }
|
|
92
|
-
: { prompt: batch.ok[i].prompt, generation_id: batch.ids[i], status: 'completed', ...creditFields(polls[i].result), urls: p.result.result.urls });
|
|
92
|
+
: { prompt: batch.ok[i].prompt, generation_id: batch.ids[i], status: 'completed', ...creditFields(polls[i].result), urls: p.result.result.urls, playback_urls: p.result.result.playback_urls });
|
|
93
93
|
const text = JSON.stringify({
|
|
94
94
|
batch: true,
|
|
95
95
|
session_id: batch.ok[0].gen.session_id,
|
|
@@ -290,11 +290,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
290
290
|
// input arg is only what the caller happened to name.
|
|
291
291
|
reference_images: result.result.reference_images || reference_images,
|
|
292
292
|
urls: result.result.urls,
|
|
293
|
+
playback_urls: result.result.playback_urls,
|
|
293
294
|
credits_used: creditFields(result).credits_used,
|
|
294
295
|
}, JSON.stringify({
|
|
295
296
|
...creditFields(result),
|
|
296
297
|
session_id: gen.session_id,
|
|
297
298
|
urls: result.result.urls,
|
|
299
|
+
playback_urls: result.result.playback_urls,
|
|
298
300
|
model: result.result.model,
|
|
299
301
|
prompt_used: result.result.prompt_used,
|
|
300
302
|
_followup_hint: 'If the user asks to edit/change/modify this image next (scene, lighting, objects, style, color — any content edit), pass urls[0] to generate_image_edit. Use edit_image ONLY for mechanical ops (upscale/reframe/removebg/enhance_skin). Do NOT call generate_image again.'
|
|
@@ -380,11 +382,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
380
382
|
reference_images: result.result.reference_images
|
|
381
383
|
|| [...(source_images || []), ...(reference_images || [])],
|
|
382
384
|
urls: result.result.urls,
|
|
385
|
+
playback_urls: result.result.playback_urls,
|
|
383
386
|
credits_used: creditFields(result).credits_used,
|
|
384
387
|
}, JSON.stringify({
|
|
385
388
|
...creditFields(result),
|
|
386
389
|
session_id: gen.session_id,
|
|
387
390
|
urls: result.result.urls,
|
|
391
|
+
playback_urls: result.result.playback_urls,
|
|
388
392
|
model: result.result.model,
|
|
389
393
|
prompt_used: result.result.prompt_used,
|
|
390
394
|
_followup_hint: 'If the user asks for another edit on this output, pass urls[0] back into generate_image_edit as source_images. For targeted ops (upscale/reframe/removebg/enhance_skin) use edit_image instead. Do NOT call generate_image from scratch.'
|
|
@@ -627,6 +631,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
627
631
|
settings: videoSettings(shared),
|
|
628
632
|
reference_images,
|
|
629
633
|
urls: result.result.urls,
|
|
634
|
+
playback_urls: result.result.playback_urls,
|
|
630
635
|
thumbnail_url: result.result.thumbnail_url,
|
|
631
636
|
duration: result.result.duration,
|
|
632
637
|
credits_used: creditFields(result).credits_used,
|
|
@@ -634,6 +639,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
634
639
|
...creditFields(result),
|
|
635
640
|
session_id: gen.session_id,
|
|
636
641
|
urls: result.result.urls,
|
|
642
|
+
playback_urls: result.result.playback_urls,
|
|
637
643
|
model: result.result.model,
|
|
638
644
|
duration: result.result.duration,
|
|
639
645
|
thumbnail_url: result.result.thumbnail_url,
|
|
@@ -714,6 +720,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
714
720
|
settings: videoSettings({ duration, resolution, aspect_ratio, enhance_prompt, visual_dna_ids }),
|
|
715
721
|
reference_images: [image_url],
|
|
716
722
|
urls: result.result.urls,
|
|
723
|
+
playback_urls: result.result.playback_urls,
|
|
717
724
|
thumbnail_url: result.result.thumbnail_url,
|
|
718
725
|
duration: result.result.duration,
|
|
719
726
|
credits_used: creditFields(result).credits_used,
|
|
@@ -721,6 +728,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
721
728
|
...creditFields(result),
|
|
722
729
|
session_id: gen.session_id,
|
|
723
730
|
urls: result.result.urls,
|
|
731
|
+
playback_urls: result.result.playback_urls,
|
|
724
732
|
model: result.result.model,
|
|
725
733
|
duration: result.result.duration,
|
|
726
734
|
thumbnail_url: result.result.thumbnail_url,
|
|
@@ -781,6 +789,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
781
789
|
tool: 'generate_music', kind: 'audio', gen, client, model: model || 'Suno', prompt,
|
|
782
790
|
settings: { mode: instrumental ? 'instrumental' : (style || undefined), preset_id },
|
|
783
791
|
urls: result.result.urls,
|
|
792
|
+
playback_urls: result.result.playback_urls,
|
|
784
793
|
title: result.result.title,
|
|
785
794
|
duration: result.result.duration,
|
|
786
795
|
credits_used: creditFields(result).credits_used,
|
|
@@ -788,6 +797,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
788
797
|
...creditFields(result),
|
|
789
798
|
session_id: gen.session_id,
|
|
790
799
|
urls: result.result.urls,
|
|
800
|
+
playback_urls: result.result.playback_urls,
|
|
791
801
|
title: result.result.title,
|
|
792
802
|
duration: result.result.duration,
|
|
793
803
|
lyrics: result.result.lyrics
|
|
@@ -899,12 +909,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
899
909
|
language,
|
|
900
910
|
},
|
|
901
911
|
urls: result.result.urls,
|
|
912
|
+
playback_urls: result.result.playback_urls,
|
|
902
913
|
duration: result.result.duration,
|
|
903
914
|
credits_used: creditFields(result).credits_used,
|
|
904
915
|
}, JSON.stringify({
|
|
905
916
|
...creditFields(result),
|
|
906
917
|
session_id: gen.session_id,
|
|
907
918
|
urls: result.result.urls,
|
|
919
|
+
playback_urls: result.result.playback_urls,
|
|
908
920
|
voice: result.result.voice,
|
|
909
921
|
duration: result.result.duration,
|
|
910
922
|
...(unknownVoice ? { _warning: unknownVoice } : {})
|
|
@@ -964,12 +976,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
964
976
|
settings: { duration },
|
|
965
977
|
reference_images: seed_reference_image_url ? [seed_reference_image_url] : [],
|
|
966
978
|
urls: result.result.urls,
|
|
979
|
+
playback_urls: result.result.playback_urls,
|
|
967
980
|
duration: result.result.duration,
|
|
968
981
|
credits_used: creditFields(result).credits_used,
|
|
969
982
|
}, JSON.stringify({
|
|
970
983
|
...creditFields(result),
|
|
971
984
|
session_id: gen.session_id,
|
|
972
985
|
urls: result.result.urls,
|
|
986
|
+
playback_urls: result.result.playback_urls,
|
|
973
987
|
duration: result.result.duration
|
|
974
988
|
}, null, 2));
|
|
975
989
|
}
|