@kolbo/mcp 1.81.11 → 1.82.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/skill/GENERATED.md +1 -1
- package/skill/SKILL.md +2 -3
- package/skill/VERSION +1 -1
- package/src/apps/index.js +5 -0
- package/src/apps/theme.js +24 -0
- package/src/apps/widgets/plans.js +203 -0
- package/src/index.js +248 -233
- package/src/toolAnnotations.js +1 -1
- package/src/tools/_shared.js +56 -0
- package/src/tools/models.js +32 -0
package/package.json
CHANGED
package/skill/GENERATED.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AUTO-GENERATED — do not edit
|
|
2
2
|
|
|
3
|
-
This tree is mirrored from kolbo-code@
|
|
3
|
+
This tree is mirrored from kolbo-code@9c91cff, the single source of truth.
|
|
4
4
|
Canonical source: packages/opencode/skills/kolbo/
|
|
5
5
|
Distribution: .github/workflows/sync-skill-to-plugin.yml
|
|
6
6
|
|
package/skill/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 0.9.
|
|
2
|
+
version: 0.9.10
|
|
3
3
|
name: kolbo
|
|
4
4
|
description: |
|
|
5
5
|
Generate, edit, analyze, and direct creative media through Kolbo AI: images,
|
|
@@ -57,7 +57,6 @@ Then generate **only** with the confirmed parameters. If the user changes an opt
|
|
|
57
57
|
- **Batch totalling 100+ credits**: run `check_credits` first.
|
|
58
58
|
- **Quote real cost**: after firing, log `credits_used` (from the tool result) to `.kolbo/production.md` — never `base × count`.
|
|
59
59
|
- **Never state "credits remaining" from arithmetic** (opening balance − generation costs). Coding/chat usage deducts credits too, so the math is always wrong. Report cost only; if the user asks for their balance, call `check_credits` fresh at that moment.
|
|
60
|
-
- **Out of credits → `show_plans`.** A generation refused for credits already returns the upgrade card automatically — do NOT retry it, and do not re-run the tool "to be sure". Call `show_plans` yourself when the user asks about pricing, plans, upgrading, or how to get more credits. Prices are live and promo-adjusted; never quote them from memory. The user completes any purchase themselves on app.kolbo.ai/pricing — you cannot buy for them.
|
|
61
60
|
|
|
62
61
|
For multi-scene / batch work this pairs with `generate_creative_director` (see below) — still confirm the brief first.
|
|
63
62
|
|
|
@@ -123,7 +122,7 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
123
122
|
### Discovery, Library, Visual DNA, Moodboards, Chat, Publishing
|
|
124
123
|
| Tool | Purpose |
|
|
125
124
|
|------|---------|
|
|
126
|
-
| `list_models` / `list_voices` / `check_credits` / `
|
|
125
|
+
| `list_models` / `list_voices` / `check_credits` / `get_generation_status` / `cancel_generation` / `get_session_usage` | Discovery + status. `list_models` with no args returns the recommended shortlist out of ~428 — pass `type` for a full category with per-model caps. `cancel_generation` stops an in-flight job and refunds what it can: use it when the user changes their mind mid-generation instead of letting it run. |
|
|
127
126
|
| `upload_media` / `create_upload_ticket` / `list_media` / `get_media` / `get_media_stats` / `favorite_media` / `unfavorite_media` / `delete_media` / `restore_media` / `permanently_delete_media` / `move_media` / `bulk_*_media` / `*_media_folder` | Media library — see `workflows/media-library.md`. Getting a LOCAL file in depends on where the server runs: `upload_media` with a path only works on a local (stdio) install; over a remote connector use `create_upload_ticket` and POST the file yourself. |
|
|
128
127
|
| `create_visual_dna` / `update_visual_dna` / `generate_character_sheet` / `list_visual_dnas` / `get_visual_dna` / `delete_visual_dna` / `*_visual_dna_folder` (5 folder tools) | Visual DNA (+ character sheet, character folders) — see `workflows/visual-dna.md`. Edit with `update_visual_dna`; never delete+recreate. |
|
|
129
128
|
| `list_moodboards` / `get_moodboard` / `list_presets` | Style overlays + sheet presets — see **Preset contract** in Core Workflow. Never omit `preset_id` after claiming a preset was used. |
|
package/skill/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.10
|
package/src/apps/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const { catalogWidgetHtml } = require('./widgets/catalog');
|
|
|
22
22
|
const { transcriptWidgetHtml } = require('./widgets/transcript');
|
|
23
23
|
const { uploadWidgetHtml } = require('./widgets/upload');
|
|
24
24
|
const { listWidgetHtml } = require('./widgets/list');
|
|
25
|
+
const { plansWidgetHtml } = require('./widgets/plans');
|
|
25
26
|
|
|
26
27
|
const UI = {
|
|
27
28
|
generation: 'ui://kolbo/generation.html',
|
|
@@ -30,6 +31,7 @@ const UI = {
|
|
|
30
31
|
transcript: 'ui://kolbo/transcript.html',
|
|
31
32
|
upload: 'ui://kolbo/upload.html',
|
|
32
33
|
list: 'ui://kolbo/list.html',
|
|
34
|
+
plans: 'ui://kolbo/plans.html',
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
const WIDGET_BUILDERS = {
|
|
@@ -39,6 +41,7 @@ const WIDGET_BUILDERS = {
|
|
|
39
41
|
[UI.transcript]: transcriptWidgetHtml,
|
|
40
42
|
[UI.upload]: uploadWidgetHtml,
|
|
41
43
|
[UI.list]: listWidgetHtml,
|
|
44
|
+
[UI.plans]: plansWidgetHtml,
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
// Widgets are pure functions of source — build once per process.
|
|
@@ -105,6 +108,7 @@ function registerApps(server) {
|
|
|
105
108
|
[UI.transcript, 'Kolbo Transcription Widget'],
|
|
106
109
|
[UI.upload, 'Kolbo Upload Widget'],
|
|
107
110
|
[UI.list, 'Kolbo List Widget'],
|
|
111
|
+
[UI.plans, 'Kolbo Plans Widget'],
|
|
108
112
|
]) {
|
|
109
113
|
registerAppResource(
|
|
110
114
|
server, name, uri,
|
|
@@ -630,6 +634,7 @@ const TOOL_WIDGETS = {
|
|
|
630
634
|
list_docs: UI.list,
|
|
631
635
|
list_media_folders: UI.list,
|
|
632
636
|
list_visual_dna_folders: UI.list,
|
|
637
|
+
show_plans: UI.plans,
|
|
633
638
|
list_project_assets: UI.list,
|
|
634
639
|
};
|
|
635
640
|
|
package/src/apps/theme.js
CHANGED
|
@@ -159,6 +159,30 @@ body {
|
|
|
159
159
|
html.k-peek-fs, html.k-peek-fs body { height: 100%; overflow: hidden; }
|
|
160
160
|
html.k-peek-fs .k-peek { position: fixed; inset: 0; z-index: 50; }
|
|
161
161
|
html.k-peek-fs .k-peek img, html.k-peek-fs .k-peek video { max-height: calc(100vh - 72px); }
|
|
162
|
+
/* ---- Plans / upgrade card ---- */
|
|
163
|
+
.k-plan-toggle { display: inline-flex; gap: 2px; padding: 3px; margin-bottom: 12px;
|
|
164
|
+
background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; }
|
|
165
|
+
.k-toggle-btn { border: 0; border-radius: 999px; padding: 5px 14px; cursor: pointer;
|
|
166
|
+
background: transparent; color: var(--text-muted); font-family: inherit; font-size: 12px; font-weight: 600; }
|
|
167
|
+
.k-toggle-btn.active { background: var(--brand); color: #fff; }
|
|
168
|
+
.k-plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
|
|
169
|
+
.k-plan { display: flex; flex-direction: column; gap: 7px; padding: 14px;
|
|
170
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--specular); }
|
|
171
|
+
.k-plan.current { border-color: var(--brand); }
|
|
172
|
+
.k-plan-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
|
173
|
+
.k-plan-name { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
|
|
174
|
+
.k-plan-badge { padding: 2px 7px; border-radius: 999px; background: var(--brand-soft); color: var(--brand);
|
|
175
|
+
font-size: 10px; font-weight: 700; }
|
|
176
|
+
.k-plan-badge.current { background: var(--surface-2); color: var(--text-faint); }
|
|
177
|
+
.k-plan-credits { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-muted); }
|
|
178
|
+
.k-plan-pricing { display: flex; align-items: baseline; gap: 6px; margin-top: 2px; }
|
|
179
|
+
.k-plan-price { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
|
|
180
|
+
.k-plan-was { font-size: 13px; color: var(--text-faint); text-decoration: line-through; }
|
|
181
|
+
.k-plan-note { font-size: 11px; color: var(--text-faint); }
|
|
182
|
+
.k-plan .k-btn { margin-top: auto; justify-content: center; }
|
|
183
|
+
.k-pack-head { margin: 14px 0 6px; font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
|
|
184
|
+
text-transform: uppercase; color: var(--text-faint); }
|
|
185
|
+
.k-pack-row { margin-bottom: 6px; }
|
|
162
186
|
/* Visual DNA chips: the character's face, so you can see WHICH DNA is locked in. */
|
|
163
187
|
.k-dna-face { width: 18px; height: 18px; border-radius: 999px; object-fit: cover; margin-left: -3px; background: var(--border-strong); }
|
|
164
188
|
.k-dna-stack .k-dna-stack-item { display: inline-flex; }
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { widgetPage } = require('../html');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plans / upgrade widget — shown by `show_plans`, and by any generation the
|
|
7
|
+
* server refused for credits.
|
|
8
|
+
*
|
|
9
|
+
* The buttons deep-link to app.kolbo.ai/pricing rather than a payment session:
|
|
10
|
+
* promo pricing, tax, currency and saved payment methods live there, and a
|
|
11
|
+
* checkout link minted on the API-key surface would drift from them the first
|
|
12
|
+
* time a campaign changes. The prices below ARE the live promo-adjusted
|
|
13
|
+
* numbers from /v1/account/plans, so the card never quotes a stale figure.
|
|
14
|
+
*
|
|
15
|
+
* structuredContent contract:
|
|
16
|
+
* {
|
|
17
|
+
* widget: 'plans',
|
|
18
|
+
* reason: 'insufficient_credits' | 'requested',
|
|
19
|
+
* balance, required, shortfall, // when reason === 'insufficient_credits'
|
|
20
|
+
* current_plan: { key, name },
|
|
21
|
+
* plans: [{ key, name, interval, credits, price, original_price,
|
|
22
|
+
* discount_percent, promo_text, currency }],
|
|
23
|
+
* credit_packs: [ same shape ],
|
|
24
|
+
* pricing_url
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const BODY = `
|
|
29
|
+
<div class="k-card" id="card">
|
|
30
|
+
<div class="k-head">
|
|
31
|
+
<span class="k-logo" id="logo"></span>
|
|
32
|
+
<span class="k-title" id="tool-title">Plans</span>
|
|
33
|
+
<span class="k-spacer"></span>
|
|
34
|
+
<span class="k-chip" id="balance-chip" style="display:none"></span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="k-body">
|
|
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
|
+
<div id="stage"></div>
|
|
43
|
+
<div class="k-actions" id="actions"></div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="k-footer">
|
|
46
|
+
<span>Powered by <a href="#" id="kolbo-link">Kolbo.AI</a></span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const SCRIPT = `
|
|
52
|
+
var state = null;
|
|
53
|
+
var interval = 'year'; // annual first — it is the better per-credit deal
|
|
54
|
+
|
|
55
|
+
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
56
|
+
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
57
|
+
|
|
58
|
+
function pricingUrl() {
|
|
59
|
+
return (state && state.pricing_url) || 'https://app.kolbo.ai/pricing';
|
|
60
|
+
}
|
|
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
|
+
|
|
73
|
+
function boot(sc) {
|
|
74
|
+
if (!sc) return;
|
|
75
|
+
state = sc;
|
|
76
|
+
el('tool-title').textContent = sc.reason === 'insufficient_credits' ? 'Out of credits' : 'Plans';
|
|
77
|
+
|
|
78
|
+
if (sc.balance != null) {
|
|
79
|
+
var chip = el('balance-chip');
|
|
80
|
+
chip.style.display = '';
|
|
81
|
+
chip.innerHTML = ICONS.sparkle + ' ' + esc(String(sc.balance)) + ' credits left';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (sc.reason === 'insufficient_credits') {
|
|
85
|
+
var need = sc.shortfall != null ? sc.shortfall
|
|
86
|
+
: (sc.required != null && sc.balance != null ? Math.max(0, sc.required - sc.balance) : null);
|
|
87
|
+
el('notice').style.display = '';
|
|
88
|
+
el('notice').innerHTML = ICONS.warn + ' ' +
|
|
89
|
+
esc(need ? ('That generation needs ' + need + ' more credit' + (need === 1 ? '' : 's') + '.')
|
|
90
|
+
: 'That generation needs more credits than you have left.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
var plans = Array.isArray(sc.plans) ? sc.plans : [];
|
|
94
|
+
var intervals = {};
|
|
95
|
+
plans.forEach(function (p) { if (p.interval) intervals[p.interval] = true; });
|
|
96
|
+
if (intervals.month && intervals.year) {
|
|
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
|
+
});
|
|
145
|
+
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>';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function renderActions() {
|
|
176
|
+
el('actions').innerHTML =
|
|
177
|
+
'<button class="k-btn ghost" id="btn-pricing">See all plans ' + ICONS.open + '</button>';
|
|
178
|
+
el('btn-pricing').onclick = function () { window.kolbo.openLink(pricingUrl()); };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
window.kolbo.onToolResult(function (result) {
|
|
182
|
+
var sc = result.structuredContent || structured(result);
|
|
183
|
+
if (sc) boot(sc);
|
|
184
|
+
});
|
|
185
|
+
window.kolbo.onToolInput(function () {
|
|
186
|
+
if (state) return;
|
|
187
|
+
el('stage').innerHTML = '<div class="k-gen-grid n2"><div class="k-skel square" style="min-height:120px"></div>'
|
|
188
|
+
+ '<div class="k-skel square" style="min-height:120px"></div></div>';
|
|
189
|
+
window.kolbo.notifySize();
|
|
190
|
+
});
|
|
191
|
+
window.kolbo.ready(function (ctx) {
|
|
192
|
+
var info = ctx && ctx.toolInfo;
|
|
193
|
+
if (state || !info) return;
|
|
194
|
+
var raw = info.result && (info.result.structuredContent || structured(info.result));
|
|
195
|
+
if (raw) boot(raw);
|
|
196
|
+
});
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
function plansWidgetHtml() {
|
|
200
|
+
return widgetPage({ title: 'Kolbo Plans', body: BODY, script: SCRIPT });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { plansWidgetHtml };
|
package/src/index.js
CHANGED
|
@@ -1,233 +1,248 @@
|
|
|
1
|
-
/* ============================================================================
|
|
2
|
-
* @kolbo/mcp — Kolbo AI MCP Server
|
|
3
|
-
*
|
|
4
|
-
* ⛔ STOP. READ THIS BEFORE TOUCHING ANY TOOL REGISTRATION. ⛔
|
|
5
|
-
*
|
|
6
|
-
* This package is published to npm and installed via `npx -y @kolbo/mcp`.
|
|
7
|
-
* Thousands of users have it CACHED on their machines, pinned to old versions
|
|
8
|
-
* by npx's cache. Every tool name, every arg name, every response shape
|
|
9
|
-
* registered below is a PUBLIC CONTRACT. Breaking it silently strands users
|
|
10
|
-
* whose LLM will keep calling tool names their cached server no longer
|
|
11
|
-
* registers — or worse, calls new-style args that the old server can't parse.
|
|
12
|
-
*
|
|
13
|
-
* THE THREE COMMANDMENTS
|
|
14
|
-
*
|
|
15
|
-
* 1. NEVER RENAME AN EXISTING TOOL.
|
|
16
|
-
* Not `generate_image` → `create_image`. Not `list_models` → `get_models`.
|
|
17
|
-
* Not "just cleaning up the name." Old cached clients break the instant
|
|
18
|
-
* you rename. If you must rename, keep the OLD name as an alias that
|
|
19
|
-
* forwards to the new implementation for at least one full major version.
|
|
20
|
-
*
|
|
21
|
-
* 2. NEVER REMOVE AN EXISTING TOOL.
|
|
22
|
-
* Deprecate it in the description ("[DEPRECATED: use X]") and keep it
|
|
23
|
-
* working. Only remove in a major version bump with release notes.
|
|
24
|
-
*
|
|
25
|
-
* 3. NEVER CHANGE AN EXISTING TOOL'S ARG NAMES, TYPES, OR REQUIRED STATUS
|
|
26
|
-
* IN A BACKWARD-INCOMPATIBLE WAY.
|
|
27
|
-
* Adding a new OPTIONAL arg with a sensible default is fine. Everything
|
|
28
|
-
* else below is forbidden in a minor release:
|
|
29
|
-
* - renaming `prompt` to `text`
|
|
30
|
-
* - making a previously-optional arg required
|
|
31
|
-
* - changing `aspect_ratio: string` to `aspect_ratio: { w, h }`
|
|
32
|
-
* - removing an arg (even one you think nobody uses)
|
|
33
|
-
*
|
|
34
|
-
* VERSION BUMPS
|
|
35
|
-
*
|
|
36
|
-
* - minor (1.1.0 → 1.2.0): new tool, new optional arg, description tweak
|
|
37
|
-
* - patch (1.1.0 → 1.1.1): internal refactor, bug fix with no user impact
|
|
38
|
-
* - major (1.1.0 → 2.0.0): ANY breaking change from commandments 1–3 above,
|
|
39
|
-
* AND only after going through the deprecation path in CLAUDE.md.
|
|
40
|
-
*
|
|
41
|
-
* WHY THIS MATTERS
|
|
42
|
-
*
|
|
43
|
-
* Users install via `npx -y @kolbo/mcp` — npx CACHES packages. A user who
|
|
44
|
-
* installed 3 months ago may still be running v1.0 until their cache
|
|
45
|
-
* invalidates. When their Claude Desktop starts the MCP server, it
|
|
46
|
-
* registers whatever tools ITS VERSION knows about. Their LLM sees that
|
|
47
|
-
* list and calls those names. You cannot force-update them.
|
|
48
|
-
*
|
|
49
|
-
* The matching backend SDK routes in
|
|
50
|
-
* `kolbo-api/src/modules/sdk/index.js` are the same kind of public
|
|
51
|
-
* contract and follow the same rules — never rename, never remove.
|
|
52
|
-
*
|
|
53
|
-
* Full rules, deprecation path, and parity-audit instructions: CLAUDE.md
|
|
54
|
-
*
|
|
55
|
-
* If you are a coding agent about to rename/remove a tool or arg: STOP and
|
|
56
|
-
* ask the human first. This is not optional.
|
|
57
|
-
* ==========================================================================*/
|
|
58
|
-
|
|
59
|
-
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
60
|
-
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
61
|
-
const KolboClient = require('./client');
|
|
62
|
-
const { LOCAL_FILE_ROUTING, attachFileInputHints } = require('./tools/_shared');
|
|
63
|
-
const { registerGenerateTools } = require('./tools/generate');
|
|
64
|
-
const { registerModelTools } = require('./tools/models');
|
|
65
|
-
const { registerChatTools } = require('./tools/chat');
|
|
66
|
-
const { registerVisualDnaTools } = require('./tools/visual_dna');
|
|
67
|
-
const { registerMoodboardTools } = require('./tools/moodboards');
|
|
68
|
-
const { registerColorPaletteTools } = require('./tools/color_palettes');
|
|
69
|
-
const { registerMediaTools } = require('./tools/media');
|
|
70
|
-
const { registerPresetTools } = require('./tools/presets');
|
|
71
|
-
const { registerArtifactTools } = require('./tools/artifacts');
|
|
72
|
-
const { registerProjectTools } = require('./tools/projects');
|
|
73
|
-
const { registerAgentTools } = require('./tools/agents');
|
|
74
|
-
const { registerDocTools } = require('./tools/docs');
|
|
75
|
-
const { registerReviewTools } = require('./tools/review');
|
|
76
|
-
const { registerVoiceTools } = require('./tools/voices');
|
|
77
|
-
const { registerMusicLibraryTools } = require('./tools/music_library');
|
|
78
|
-
const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
79
|
-
const { registerAudioStemTools } = require('./tools/audio_stems');
|
|
80
|
-
const { registerApps, attachToolWidgetMeta } = require('./apps');
|
|
81
|
-
const { attachToolAnnotations } = require('./toolAnnotations');
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Build a fully-configured Kolbo MCP server (all tool groups registered)
|
|
85
|
-
* WITHOUT connecting a transport. This is the reusable core shared by:
|
|
86
|
-
* - the stdio entrypoint below (npx / Kolbo Code), and
|
|
87
|
-
* - a remote HTTP host (kolbo-api) that creates one server per request with
|
|
88
|
-
* the caller's key injected via `opts.apiKey`.
|
|
89
|
-
*
|
|
90
|
-
* @param {object} [opts]
|
|
91
|
-
* @param {string} [opts.apiKey] Per-instance Kolbo API key (overrides env).
|
|
92
|
-
* @param {string} [opts.apiBase] API base URL override.
|
|
93
|
-
* @param {boolean} [opts.apps] Force-enable MCP Apps widget results. Set by
|
|
94
|
-
* the kolbo-api remote connector (claude.ai),
|
|
95
|
-
* whose stateless transport hides client
|
|
96
|
-
* capabilities. stdio hosts are auto-detected
|
|
97
|
-
* from the initialize handshake instead.
|
|
98
|
-
* @returns {McpServer} a server ready to `.connect(transport)`.
|
|
99
|
-
*/
|
|
100
|
-
function createServer(opts = {}) {
|
|
101
|
-
const client = new KolboClient(opts);
|
|
102
|
-
|
|
103
|
-
const server = new McpServer({
|
|
104
|
-
name: 'kolbo',
|
|
105
|
-
title: 'Kolbo',
|
|
106
|
-
version: '1.0.0',
|
|
107
|
-
websiteUrl: 'https://kolbo.ai',
|
|
108
|
-
// Connector avatar for hosts that render server icons (claude.ai tool
|
|
109
|
-
// headers show this instead of a letter monogram).
|
|
110
|
-
icons: [{ src: 'https://api.kolbo.ai/assets/kolbo-ai.png', mimeType: 'image/png', sizes: ['512x512'] }]
|
|
111
|
-
}, {
|
|
112
|
-
// Server-level instructions surfaced to the host model on initialize.
|
|
113
|
-
// The single most common failure mode is project confusion — spell out
|
|
114
|
-
// the project contract here so every client gets it without a skill file.
|
|
115
|
-
instructions: [
|
|
116
|
-
'LOCAL FILES: never upload a user file with your own cloud credentials, an S3/Spaces script, or a third-party host — Kolbo owns this. ' + LOCAL_FILE_ROUTING,
|
|
117
|
-
'PROMPT CONVENTIONS (Kolbo-specific — these change the OUTPUT, not just the metadata):',
|
|
118
|
-
'A. Visual DNA: passing `visual_dna_ids` is not enough — every DNA in play must ALSO be tagged inside the prompt text as `@Name`, using the DNA name (e.g. "@Kobi walks into frame"). Moodboards are referenced the same way with `#Name`. Resolve names via `list_visual_dnas` / `list_moodboards`.',
|
|
119
|
-
'B. The full Kolbo skill is available to you as MCP RESOURCES under `kolbo://skill/`. Read `kolbo://skill/SKILL.md` first — it is the core rules plus a routing index — then read the matching `kolbo://skill/references/...` file before writing prompts for a specific model or workflow (per-model prompt rules, Visual DNA workflow, Creative Director, marketing, cost validation). Do this instead of guessing; the references exist precisely because the rules differ per model.',
|
|
120
|
-
'PROJECT CONTRACT (read this before generating anything):',
|
|
121
|
-
'Everything in Kolbo lives inside a PROJECT — sessions, generations, and media are all project-scoped.',
|
|
122
|
-
'1. When the user names a project ("in my Acme project", "for the summer campaign"), call `list_projects` ONCE to resolve the name to an id, then pass that SAME id as `project_id` on EVERY subsequent generate_* / chat_send_message / upload_media / create_doc call in THIS conversation. There is no server-side sticky store — omitting `project_id` on any later call silently lands in the default "API Generations" bucket (flagged is_default:true). Once resolved, treat that id as required for the rest of the conversation.',
|
|
123
|
-
'2. `list_projects` lists the user\'s platform projects (for generations/media/chat). `list_sessions` returns `project_id` on every row — echo that id on follow-up generate/chat/upload calls for work in that session. Do not confuse a generation `session_id` with a project id; they are not interchangeable. Empty leftover sessions after `move_session` can be removed with `delete_session` (soft-delete; `restore_session` undoes it).',
|
|
124
|
-
'3. Misplaced work is fixable: `move_media` / `bulk_move_media` / `move_folder_contents` move media items between projects; `move_session` moves a whole session (plus its media) to another project. If the user says a generation landed in the wrong project, move it rather than regenerating.',
|
|
125
|
-
'4. If the user has not mentioned any project, omit `project_id` — the default bucket is correct in that case. Do not ask which project to use unless the user\'s intent is ambiguous.',
|
|
126
|
-
'5. Written deliverables (plans, briefs, scripts, research summaries) can live in Kolbo too: author them as AI Docs with `create_doc` (project-scoped, editable in the app, shareable via `share_doc`).',
|
|
127
|
-
'6. TIMEOUT HANDLING (applies to EVERY generate_* / edit_* / chat_send_message / transcribe_audio tool): each tool blocks and polls internally, then gives up after its own window if the job is not yet terminal. A timeout is NOT a failure — it returns `_timed_out:true` with the `generation_id` (not an error), because the job is almost always STILL RUNNING on the server (or already finished). Call `get_generation_status` with that `generation_id` and `wait=true` to keep checking until state="completed". NEVER conclude the generation failed and re-run the same tool from scratch after a `_timed_out:true` result — that wastes the user\'s credits by paying twice. DIRECTOR / BATCH JOBS follow the same convention through a dedicated tool: generate_creative_director runs its scenes (image OR video) in parallel and only reports state="completed" once EVERY scene is terminal; on `_timed_out:true` call `get_creative_director_status` (not get_generation_status) with the returned generation_id and keep checking. If scenes already carry image_urls/video_urls, they are done; do not regenerate.',
|
|
128
|
-
'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.',
|
|
129
|
-
'8. LOCAL FILES / REFERENCE MEDIA — HOW TO HANDLE EVERY CASE. (A) User has a LOCAL file (audio, video, image, document) on their machine. What matters is WHERE THIS SERVER RUNS, not what your client can do — your own filesystem access is irrelevant if the server is somewhere else. On a LOCAL stdio install (server and client share a machine) → call `upload_media` with the absolute path, or pass the path straight to tools like `transcribe_audio` that accept local paths. Over a REMOTE connector the server cannot see that path no matter how capable you are, so a local path will always fail: if you can run shell commands or issue HTTP requests → call `create_upload_ticket` and POST the file to the returned upload_url yourself (fastest, no user interaction); if you cannot → call `media_upload_widget` IMMEDIATELY, the user uploads, and a `media.kolbo.ai` CDN URL comes back for any follow-up call. (B) You already have a public URL → pass it directly. A URL from generate_* / list_media / upload_media / media.kolbo.ai / any *.kolbo.ai host is ALREADY hosted — NEVER call upload_media on it (that duplicates the file). External (non-Kolbo) URLs may need one upload_media re-host; Kolbo URLs never do. 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 anything but a tiny file — it costs context in proportion to file size; use the ticket or the widget instead.',
|
|
130
|
-
'9. MODEL SELECTION — NAMED MODEL WINS, THEN STRENGTHS SUMMARY: ALWAYS pass a specific `model` on every generation tool — do NOT omit it (omitting falls back to "Smart Select" auto-routing, which hides the choice from the user; use it ONLY if the user explicitly asks you to auto-pick). If the user named a model this turn OR earlier in the conversation (including a compaction "Locked choices" / summary), that name is a FAMILY LOCK: pass it (or its display name) on every follow-up, including when the tool changes (text-to-video → image-to-video). Identifier resolution remaps a t2v id to the family\'s i2v sibling automatically. NEVER substitute a different brand because it is cheaper, faster, or "best balance" (Grok Imagine named → do not fire Seedance). If the named family has no variant for this modality, ASK — do not silently switch. Cheapest-summary routing applies ONLY when no model was named on this task: call `list_models` with the matching `type` and read each model\'s STRENGTHS SUMMARY — the "— …" clause printed after the credit cost — then pick the CHEAPEST model whose summary covers the task. `[NEW]` and `[RECOMMENDED]` badges, a high credit number, and "flagship"/"most intelligent" wording are NOT selection signals — never pick a model because it is newest, biggest or most expensive. Escalate to a premium/frontier model only when the user explicitly asks for maximum quality, or when no cheaper summary covers the requirement. Models printed under "Named-only" (no summary) are opt-in: use them only when the user names them. TEXT/CHAT: `chat_send_message` bills PER TOKEN, so the listed credit number is not the cost — a frontier text model (Claude Fable 5, GPT-5.6 Sol, Pro-class) costs 5-30x a mid-tier one per reply. Default ordinary chat (writing, brainstorming, Q&A, summarising) to a balanced mid-tier model and reserve the frontier tier for hard reasoning or long-form code the user asked for.',
|
|
131
|
-
'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`. Auto-pick only Nano Banana 2 (`nano-banana-2` / `nano-banana-2-image-editing`) or GPT Image 2 (`gpt-image-2` / `gpt-image-2/edit`) for photoreal photo edits, object removal, keep-subject/remove-others, or crowd cleanup. Do NOT auto-pick Flux 2 / flux-2/edit / Flux Klein — those are generate-from-scratch / style, named-only for editing. 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.',
|
|
132
|
-
'11. PRESET CONTRACT: if the user asks for a preset, names a preset, or says to use one of their/Kolbo presets, you MUST call `list_presets` with the matching type before generation, resolve the named or closest matching preset, and pass its exact returned `id` as `preset_id`. Use type="image" for generate_image and type="image_edit" for generate_image_edit. Never silently ignore a preset request, never invent an id, and never claim a preset was applied unless `preset_id` was present in the generation call.'
|
|
133
|
-
].join('\n')
|
|
134
|
-
});
|
|
135
|
-
const progress = require('./progress');
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* @kolbo/mcp — Kolbo AI MCP Server
|
|
3
|
+
*
|
|
4
|
+
* ⛔ STOP. READ THIS BEFORE TOUCHING ANY TOOL REGISTRATION. ⛔
|
|
5
|
+
*
|
|
6
|
+
* This package is published to npm and installed via `npx -y @kolbo/mcp`.
|
|
7
|
+
* Thousands of users have it CACHED on their machines, pinned to old versions
|
|
8
|
+
* by npx's cache. Every tool name, every arg name, every response shape
|
|
9
|
+
* registered below is a PUBLIC CONTRACT. Breaking it silently strands users
|
|
10
|
+
* whose LLM will keep calling tool names their cached server no longer
|
|
11
|
+
* registers — or worse, calls new-style args that the old server can't parse.
|
|
12
|
+
*
|
|
13
|
+
* THE THREE COMMANDMENTS
|
|
14
|
+
*
|
|
15
|
+
* 1. NEVER RENAME AN EXISTING TOOL.
|
|
16
|
+
* Not `generate_image` → `create_image`. Not `list_models` → `get_models`.
|
|
17
|
+
* Not "just cleaning up the name." Old cached clients break the instant
|
|
18
|
+
* you rename. If you must rename, keep the OLD name as an alias that
|
|
19
|
+
* forwards to the new implementation for at least one full major version.
|
|
20
|
+
*
|
|
21
|
+
* 2. NEVER REMOVE AN EXISTING TOOL.
|
|
22
|
+
* Deprecate it in the description ("[DEPRECATED: use X]") and keep it
|
|
23
|
+
* working. Only remove in a major version bump with release notes.
|
|
24
|
+
*
|
|
25
|
+
* 3. NEVER CHANGE AN EXISTING TOOL'S ARG NAMES, TYPES, OR REQUIRED STATUS
|
|
26
|
+
* IN A BACKWARD-INCOMPATIBLE WAY.
|
|
27
|
+
* Adding a new OPTIONAL arg with a sensible default is fine. Everything
|
|
28
|
+
* else below is forbidden in a minor release:
|
|
29
|
+
* - renaming `prompt` to `text`
|
|
30
|
+
* - making a previously-optional arg required
|
|
31
|
+
* - changing `aspect_ratio: string` to `aspect_ratio: { w, h }`
|
|
32
|
+
* - removing an arg (even one you think nobody uses)
|
|
33
|
+
*
|
|
34
|
+
* VERSION BUMPS
|
|
35
|
+
*
|
|
36
|
+
* - minor (1.1.0 → 1.2.0): new tool, new optional arg, description tweak
|
|
37
|
+
* - patch (1.1.0 → 1.1.1): internal refactor, bug fix with no user impact
|
|
38
|
+
* - major (1.1.0 → 2.0.0): ANY breaking change from commandments 1–3 above,
|
|
39
|
+
* AND only after going through the deprecation path in CLAUDE.md.
|
|
40
|
+
*
|
|
41
|
+
* WHY THIS MATTERS
|
|
42
|
+
*
|
|
43
|
+
* Users install via `npx -y @kolbo/mcp` — npx CACHES packages. A user who
|
|
44
|
+
* installed 3 months ago may still be running v1.0 until their cache
|
|
45
|
+
* invalidates. When their Claude Desktop starts the MCP server, it
|
|
46
|
+
* registers whatever tools ITS VERSION knows about. Their LLM sees that
|
|
47
|
+
* list and calls those names. You cannot force-update them.
|
|
48
|
+
*
|
|
49
|
+
* The matching backend SDK routes in
|
|
50
|
+
* `kolbo-api/src/modules/sdk/index.js` are the same kind of public
|
|
51
|
+
* contract and follow the same rules — never rename, never remove.
|
|
52
|
+
*
|
|
53
|
+
* Full rules, deprecation path, and parity-audit instructions: CLAUDE.md
|
|
54
|
+
*
|
|
55
|
+
* If you are a coding agent about to rename/remove a tool or arg: STOP and
|
|
56
|
+
* ask the human first. This is not optional.
|
|
57
|
+
* ==========================================================================*/
|
|
58
|
+
|
|
59
|
+
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
60
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
61
|
+
const KolboClient = require('./client');
|
|
62
|
+
const { LOCAL_FILE_ROUTING, attachFileInputHints } = require('./tools/_shared');
|
|
63
|
+
const { registerGenerateTools } = require('./tools/generate');
|
|
64
|
+
const { registerModelTools } = require('./tools/models');
|
|
65
|
+
const { registerChatTools } = require('./tools/chat');
|
|
66
|
+
const { registerVisualDnaTools } = require('./tools/visual_dna');
|
|
67
|
+
const { registerMoodboardTools } = require('./tools/moodboards');
|
|
68
|
+
const { registerColorPaletteTools } = require('./tools/color_palettes');
|
|
69
|
+
const { registerMediaTools } = require('./tools/media');
|
|
70
|
+
const { registerPresetTools } = require('./tools/presets');
|
|
71
|
+
const { registerArtifactTools } = require('./tools/artifacts');
|
|
72
|
+
const { registerProjectTools } = require('./tools/projects');
|
|
73
|
+
const { registerAgentTools } = require('./tools/agents');
|
|
74
|
+
const { registerDocTools } = require('./tools/docs');
|
|
75
|
+
const { registerReviewTools } = require('./tools/review');
|
|
76
|
+
const { registerVoiceTools } = require('./tools/voices');
|
|
77
|
+
const { registerMusicLibraryTools } = require('./tools/music_library');
|
|
78
|
+
const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
79
|
+
const { registerAudioStemTools } = require('./tools/audio_stems');
|
|
80
|
+
const { registerApps, attachToolWidgetMeta } = require('./apps');
|
|
81
|
+
const { attachToolAnnotations } = require('./toolAnnotations');
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build a fully-configured Kolbo MCP server (all tool groups registered)
|
|
85
|
+
* WITHOUT connecting a transport. This is the reusable core shared by:
|
|
86
|
+
* - the stdio entrypoint below (npx / Kolbo Code), and
|
|
87
|
+
* - a remote HTTP host (kolbo-api) that creates one server per request with
|
|
88
|
+
* the caller's key injected via `opts.apiKey`.
|
|
89
|
+
*
|
|
90
|
+
* @param {object} [opts]
|
|
91
|
+
* @param {string} [opts.apiKey] Per-instance Kolbo API key (overrides env).
|
|
92
|
+
* @param {string} [opts.apiBase] API base URL override.
|
|
93
|
+
* @param {boolean} [opts.apps] Force-enable MCP Apps widget results. Set by
|
|
94
|
+
* the kolbo-api remote connector (claude.ai),
|
|
95
|
+
* whose stateless transport hides client
|
|
96
|
+
* capabilities. stdio hosts are auto-detected
|
|
97
|
+
* from the initialize handshake instead.
|
|
98
|
+
* @returns {McpServer} a server ready to `.connect(transport)`.
|
|
99
|
+
*/
|
|
100
|
+
function createServer(opts = {}) {
|
|
101
|
+
const client = new KolboClient(opts);
|
|
102
|
+
|
|
103
|
+
const server = new McpServer({
|
|
104
|
+
name: 'kolbo',
|
|
105
|
+
title: 'Kolbo',
|
|
106
|
+
version: '1.0.0',
|
|
107
|
+
websiteUrl: 'https://kolbo.ai',
|
|
108
|
+
// Connector avatar for hosts that render server icons (claude.ai tool
|
|
109
|
+
// headers show this instead of a letter monogram).
|
|
110
|
+
icons: [{ src: 'https://api.kolbo.ai/assets/kolbo-ai.png', mimeType: 'image/png', sizes: ['512x512'] }]
|
|
111
|
+
}, {
|
|
112
|
+
// Server-level instructions surfaced to the host model on initialize.
|
|
113
|
+
// The single most common failure mode is project confusion — spell out
|
|
114
|
+
// the project contract here so every client gets it without a skill file.
|
|
115
|
+
instructions: [
|
|
116
|
+
'LOCAL FILES: never upload a user file with your own cloud credentials, an S3/Spaces script, or a third-party host — Kolbo owns this. ' + LOCAL_FILE_ROUTING,
|
|
117
|
+
'PROMPT CONVENTIONS (Kolbo-specific — these change the OUTPUT, not just the metadata):',
|
|
118
|
+
'A. Visual DNA: passing `visual_dna_ids` is not enough — every DNA in play must ALSO be tagged inside the prompt text as `@Name`, using the DNA name (e.g. "@Kobi walks into frame"). Moodboards are referenced the same way with `#Name`. Resolve names via `list_visual_dnas` / `list_moodboards`.',
|
|
119
|
+
'B. The full Kolbo skill is available to you as MCP RESOURCES under `kolbo://skill/`. Read `kolbo://skill/SKILL.md` first — it is the core rules plus a routing index — then read the matching `kolbo://skill/references/...` file before writing prompts for a specific model or workflow (per-model prompt rules, Visual DNA workflow, Creative Director, marketing, cost validation). Do this instead of guessing; the references exist precisely because the rules differ per model.',
|
|
120
|
+
'PROJECT CONTRACT (read this before generating anything):',
|
|
121
|
+
'Everything in Kolbo lives inside a PROJECT — sessions, generations, and media are all project-scoped.',
|
|
122
|
+
'1. When the user names a project ("in my Acme project", "for the summer campaign"), call `list_projects` ONCE to resolve the name to an id, then pass that SAME id as `project_id` on EVERY subsequent generate_* / chat_send_message / upload_media / create_doc call in THIS conversation. There is no server-side sticky store — omitting `project_id` on any later call silently lands in the default "API Generations" bucket (flagged is_default:true). Once resolved, treat that id as required for the rest of the conversation.',
|
|
123
|
+
'2. `list_projects` lists the user\'s platform projects (for generations/media/chat). `list_sessions` returns `project_id` on every row — echo that id on follow-up generate/chat/upload calls for work in that session. Do not confuse a generation `session_id` with a project id; they are not interchangeable. Empty leftover sessions after `move_session` can be removed with `delete_session` (soft-delete; `restore_session` undoes it).',
|
|
124
|
+
'3. Misplaced work is fixable: `move_media` / `bulk_move_media` / `move_folder_contents` move media items between projects; `move_session` moves a whole session (plus its media) to another project. If the user says a generation landed in the wrong project, move it rather than regenerating.',
|
|
125
|
+
'4. If the user has not mentioned any project, omit `project_id` — the default bucket is correct in that case. Do not ask which project to use unless the user\'s intent is ambiguous.',
|
|
126
|
+
'5. Written deliverables (plans, briefs, scripts, research summaries) can live in Kolbo too: author them as AI Docs with `create_doc` (project-scoped, editable in the app, shareable via `share_doc`).',
|
|
127
|
+
'6. TIMEOUT HANDLING (applies to EVERY generate_* / edit_* / chat_send_message / transcribe_audio tool): each tool blocks and polls internally, then gives up after its own window if the job is not yet terminal. A timeout is NOT a failure — it returns `_timed_out:true` with the `generation_id` (not an error), because the job is almost always STILL RUNNING on the server (or already finished). Call `get_generation_status` with that `generation_id` and `wait=true` to keep checking until state="completed". NEVER conclude the generation failed and re-run the same tool from scratch after a `_timed_out:true` result — that wastes the user\'s credits by paying twice. DIRECTOR / BATCH JOBS follow the same convention through a dedicated tool: generate_creative_director runs its scenes (image OR video) in parallel and only reports state="completed" once EVERY scene is terminal; on `_timed_out:true` call `get_creative_director_status` (not get_generation_status) with the returned generation_id and keep checking. If scenes already carry image_urls/video_urls, they are done; do not regenerate.',
|
|
128
|
+
'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.',
|
|
129
|
+
'8. LOCAL FILES / REFERENCE MEDIA — HOW TO HANDLE EVERY CASE. (A) User has a LOCAL file (audio, video, image, document) on their machine. What matters is WHERE THIS SERVER RUNS, not what your client can do — your own filesystem access is irrelevant if the server is somewhere else. On a LOCAL stdio install (server and client share a machine) → call `upload_media` with the absolute path, or pass the path straight to tools like `transcribe_audio` that accept local paths. Over a REMOTE connector the server cannot see that path no matter how capable you are, so a local path will always fail: if you can run shell commands or issue HTTP requests → call `create_upload_ticket` and POST the file to the returned upload_url yourself (fastest, no user interaction); if you cannot → call `media_upload_widget` IMMEDIATELY, the user uploads, and a `media.kolbo.ai` CDN URL comes back for any follow-up call. (B) You already have a public URL → pass it directly. A URL from generate_* / list_media / upload_media / media.kolbo.ai / any *.kolbo.ai host is ALREADY hosted — NEVER call upload_media on it (that duplicates the file). External (non-Kolbo) URLs may need one upload_media re-host; Kolbo URLs never do. 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 anything but a tiny file — it costs context in proportion to file size; use the ticket or the widget instead.',
|
|
130
|
+
'9. MODEL SELECTION — NAMED MODEL WINS, THEN STRENGTHS SUMMARY: ALWAYS pass a specific `model` on every generation tool — do NOT omit it (omitting falls back to "Smart Select" auto-routing, which hides the choice from the user; use it ONLY if the user explicitly asks you to auto-pick). If the user named a model this turn OR earlier in the conversation (including a compaction "Locked choices" / summary), that name is a FAMILY LOCK: pass it (or its display name) on every follow-up, including when the tool changes (text-to-video → image-to-video). Identifier resolution remaps a t2v id to the family\'s i2v sibling automatically. NEVER substitute a different brand because it is cheaper, faster, or "best balance" (Grok Imagine named → do not fire Seedance). If the named family has no variant for this modality, ASK — do not silently switch. Cheapest-summary routing applies ONLY when no model was named on this task: call `list_models` with the matching `type` and read each model\'s STRENGTHS SUMMARY — the "— …" clause printed after the credit cost — then pick the CHEAPEST model whose summary covers the task. `[NEW]` and `[RECOMMENDED]` badges, a high credit number, and "flagship"/"most intelligent" wording are NOT selection signals — never pick a model because it is newest, biggest or most expensive. Escalate to a premium/frontier model only when the user explicitly asks for maximum quality, or when no cheaper summary covers the requirement. Models printed under "Named-only" (no summary) are opt-in: use them only when the user names them. TEXT/CHAT: `chat_send_message` bills PER TOKEN, so the listed credit number is not the cost — a frontier text model (Claude Fable 5, GPT-5.6 Sol, Pro-class) costs 5-30x a mid-tier one per reply. Default ordinary chat (writing, brainstorming, Q&A, summarising) to a balanced mid-tier model and reserve the frontier tier for hard reasoning or long-form code the user asked for.',
|
|
131
|
+
'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`. Auto-pick only Nano Banana 2 (`nano-banana-2` / `nano-banana-2-image-editing`) or GPT Image 2 (`gpt-image-2` / `gpt-image-2/edit`) for photoreal photo edits, object removal, keep-subject/remove-others, or crowd cleanup. Do NOT auto-pick Flux 2 / flux-2/edit / Flux Klein — those are generate-from-scratch / style, named-only for editing. 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.',
|
|
132
|
+
'11. PRESET CONTRACT: if the user asks for a preset, names a preset, or says to use one of their/Kolbo presets, you MUST call `list_presets` with the matching type before generation, resolve the named or closest matching preset, and pass its exact returned `id` as `preset_id`. Use type="image" for generate_image and type="image_edit" for generate_image_edit. Never silently ignore a preset request, never invent an id, and never claim a preset was applied unless `preset_id` was present in the generation call.'
|
|
133
|
+
].join('\n')
|
|
134
|
+
});
|
|
135
|
+
const progress = require('./progress');
|
|
136
|
+
const { insufficientCreditsResult } = require('./tools/_shared');
|
|
137
|
+
const tool = server.tool.bind(server);
|
|
138
|
+
server.tool = (...args) => {
|
|
139
|
+
const index = args.length - 1;
|
|
140
|
+
const handler = args[index];
|
|
141
|
+
if (typeof handler !== 'function') return tool(...args);
|
|
142
|
+
args[index] = (params, extra) => progress.run(extra, async () => {
|
|
143
|
+
try {
|
|
144
|
+
return await handler(params, extra);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// Running out of credits is the one refusal with an obvious next step,
|
|
147
|
+
// so it gets a card instead of raw error prose. Wrapped HERE rather
|
|
148
|
+
// than at each of the ~17 generation submit sites: any tool the server
|
|
149
|
+
// can refuse for credits (generation, chat, stems) routes through this
|
|
150
|
+
// single seam, which is also the only way a tool added later inherits
|
|
151
|
+
// the behavior for free.
|
|
152
|
+
const card = await insufficientCreditsResult(client, err).catch(() => null);
|
|
153
|
+
if (card) return card;
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return tool(...args);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Register all tools. `inlineImages` (off by default) is opt-in: only the
|
|
161
|
+
// remote HTTP host enables it, so stdio clients (Kolbo Code / Desktop / Cursor)
|
|
162
|
+
// keep identical text-URL output. `apps` gates interactive widget results
|
|
163
|
+
// (MCP Apps) the same way — see src/apps/index.js.
|
|
164
|
+
const toolOptions = { inlineImages: !!opts.inlineImages, apps: !!opts.apps };
|
|
165
|
+
registerGenerateTools(server, client, toolOptions);
|
|
166
|
+
registerModelTools(server, client, toolOptions);
|
|
167
|
+
registerVoiceTools(server, client, toolOptions);
|
|
168
|
+
registerChatTools(server, client, toolOptions);
|
|
169
|
+
registerVisualDnaTools(server, client, toolOptions);
|
|
170
|
+
registerMoodboardTools(server, client, toolOptions);
|
|
171
|
+
registerColorPaletteTools(server, client, toolOptions);
|
|
172
|
+
registerMediaTools(server, client, toolOptions);
|
|
173
|
+
registerPresetTools(server, client, toolOptions);
|
|
174
|
+
registerArtifactTools(server, client, toolOptions);
|
|
175
|
+
registerProjectTools(server, client, toolOptions);
|
|
176
|
+
registerAgentTools(server, client, toolOptions);
|
|
177
|
+
registerDocTools(server, client, toolOptions);
|
|
178
|
+
registerReviewTools(server, client, toolOptions);
|
|
179
|
+
registerMusicLibraryTools(server, client, toolOptions);
|
|
180
|
+
registerStockLibraryTools(server, client, toolOptions);
|
|
181
|
+
registerAudioStemTools(server, client, toolOptions);
|
|
182
|
+
|
|
183
|
+
// MCP Apps widget resources (ui://kolbo/*). Registering resources is inert
|
|
184
|
+
// for text-only hosts — they never fetch them.
|
|
185
|
+
registerApps(server);
|
|
186
|
+
// Serve skill/ as standard MCP resources so connector clients — which never
|
|
187
|
+
// run `npx @kolbo/mcp install` — can still read the operating guidance.
|
|
188
|
+
registerSkillResources(server);
|
|
189
|
+
// Every media-input tool advertises the local-file route that works on THIS
|
|
190
|
+
// transport. Without it, a remote-connector model reads "absolute local path",
|
|
191
|
+
// sees no filesystem, and tells the user Kolbo cannot accept their file —
|
|
192
|
+
// the single most-reported failure, despite the upload tools existing.
|
|
193
|
+
attachFileInputHints(server, toolOptions);
|
|
194
|
+
// OpenAI public-app review requires every exposed tool to declare the three
|
|
195
|
+
// safety hints explicitly. The exact contract also fails closed when a tool
|
|
196
|
+
// is added or removed without a classification.
|
|
197
|
+
attachToolAnnotations(server);
|
|
198
|
+
// Declaration-level `_meta['ui/resourceUri']` on every widget-carrying tool —
|
|
199
|
+
// claude.ai prepares the widget iframe from tools/list, not from the result.
|
|
200
|
+
attachToolWidgetMeta(server);
|
|
201
|
+
|
|
202
|
+
return server;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function main() {
|
|
206
|
+
const server = createServer();
|
|
207
|
+
|
|
208
|
+
// Node kills the process on an unhandled rejection / uncaught exception. In a
|
|
209
|
+
// long-lived stdio server that is not a stack trace the user ever sees — the
|
|
210
|
+
// host just reports "MCP server disconnected", mid-conversation, with the
|
|
211
|
+
// generation still running server-side. A tool error is recoverable; a dead
|
|
212
|
+
// process is not, so log to stderr (stdout is the JSON-RPC channel) and stay
|
|
213
|
+
// up. Only the stdio entrypoint does this — an embedding host (kolbo-api)
|
|
214
|
+
// keeps its own process semantics.
|
|
215
|
+
process.on('unhandledRejection', (err) => {
|
|
216
|
+
console.error('[kolbo-mcp] unhandled rejection (server staying up):', err);
|
|
217
|
+
});
|
|
218
|
+
process.on('uncaughtException', (err) => {
|
|
219
|
+
console.error('[kolbo-mcp] uncaught exception (server staying up):', err);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Start the server with stdio transport
|
|
223
|
+
const transport = new StdioServerTransport();
|
|
224
|
+
await server.connect(transport);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Widget plumbing for HOST developers embedding Kolbo widgets themselves
|
|
228
|
+
// (e.g. kolbo-api's Kobi Act) — everything else in ./apps stays internal.
|
|
229
|
+
// UI: tool-name-agnostic resource URI map. TOOL_WIDGETS: tool name -> URI, so
|
|
230
|
+
// a host can resolve which widget a given tool call carries without deep-
|
|
231
|
+
// requiring internals. uiMeta/widgetHtml: same helpers registerApps() uses
|
|
232
|
+
// internally, re-exported so a host never has to re-derive them. Additive
|
|
233
|
+
// only — existing consumers (claude.ai, Desktop, npx) are unaffected.
|
|
234
|
+
const { UI, TOOL_WIDGETS, uiMeta, widgetHtml } = require('./apps');
|
|
235
|
+
const { registerSkillResources } = require('./skillResources');
|
|
236
|
+
|
|
237
|
+
module.exports = { main, createServer, UI, TOOL_WIDGETS, uiMeta, widgetHtml };
|
|
238
|
+
|
|
239
|
+
// Auto-run when invoked directly (e.g. `node src/index.js` or via the published
|
|
240
|
+
// bin/kolbo-mcp.js wrapper). Consumers that `require()` this module to embed it
|
|
241
|
+
// inside another process (the Kolbo Code CLI's `kolbo mcp serve` subcommand)
|
|
242
|
+
// should call `main()` themselves.
|
|
243
|
+
if (require.main === module || require.main?.filename?.endsWith('kolbo-mcp.js')) {
|
|
244
|
+
main().catch(err => {
|
|
245
|
+
console.error('Failed to start Kolbo MCP server:', err);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
});
|
|
248
|
+
}
|
package/src/toolAnnotations.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const READ_ONLY = [
|
|
12
12
|
'get_creative_director_status', 'get_generation_status', 'list_models',
|
|
13
|
-
'check_credits', 'get_session_usage', 'list_voices',
|
|
13
|
+
'check_credits', 'show_plans', 'get_session_usage', 'list_voices',
|
|
14
14
|
'chat_list_conversations', 'chat_get_messages',
|
|
15
15
|
'list_visual_dnas', 'get_visual_dna', 'list_visual_dna_folders',
|
|
16
16
|
'list_moodboards', 'get_moodboard',
|
package/src/tools/_shared.js
CHANGED
|
@@ -939,6 +939,61 @@ function compactList(items, { fields, cap = 50, total, extra, note } = {}) {
|
|
|
939
939
|
return text;
|
|
940
940
|
}
|
|
941
941
|
|
|
942
|
+
/**
|
|
943
|
+
* Turn a 403 INSUFFICIENT_CREDITS into the plans/upgrade card.
|
|
944
|
+
*
|
|
945
|
+
* Without this the user just sees the raw API sentence and has to go find the
|
|
946
|
+
* pricing page themselves. The card states the shortfall, shows live
|
|
947
|
+
* promo-adjusted plans, and links to app.kolbo.ai/pricing.
|
|
948
|
+
*
|
|
949
|
+
* Returns null for any other error so callers can rethrow untouched. The plan
|
|
950
|
+
* fetch is best-effort: if it fails we still return a card carrying the
|
|
951
|
+
* balance/shortfall and the pricing link, because the ONE thing this path must
|
|
952
|
+
* never do is swallow the reason the generation did not run.
|
|
953
|
+
*/
|
|
954
|
+
async function insufficientCreditsResult(client, err) {
|
|
955
|
+
if (!err || err.code !== 'INSUFFICIENT_CREDITS') return null;
|
|
956
|
+
|
|
957
|
+
let data = null;
|
|
958
|
+
try {
|
|
959
|
+
data = await client.get('/v1/account/plans');
|
|
960
|
+
} catch {
|
|
961
|
+
// Offline / rate-limited — fall through to the minimal card.
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const balance = err.data?.balance ?? data?.credits?.total;
|
|
965
|
+
const required = err.data?.required;
|
|
966
|
+
const structured = {
|
|
967
|
+
widget: 'plans',
|
|
968
|
+
reason: 'insufficient_credits',
|
|
969
|
+
balance,
|
|
970
|
+
required,
|
|
971
|
+
shortfall: (Number.isFinite(balance) && Number.isFinite(required))
|
|
972
|
+
? Math.max(0, required - balance) : undefined,
|
|
973
|
+
current_plan: data?.current_plan || null,
|
|
974
|
+
plans: data?.plans || [],
|
|
975
|
+
credit_packs: data?.credit_packs || [],
|
|
976
|
+
pricing_url: err.data?.pricing_url || data?.pricing_url || 'https://app.kolbo.ai/pricing',
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
// structuredContent shadows the text on widget hosts, so the refusal reason
|
|
980
|
+
// has to be inside the object too — an agent that only reads structured
|
|
981
|
+
// content must still understand that nothing was generated.
|
|
982
|
+
structured.error = err.message;
|
|
983
|
+
structured._hint = 'The generation did NOT run and nothing was charged. An upgrade card is shown to the user. Tell them they are out of credits and point at the card; do not retry the generation, and do not attempt to purchase anything for them.';
|
|
984
|
+
|
|
985
|
+
const text = JSON.stringify({
|
|
986
|
+
error: err.message,
|
|
987
|
+
code: 'INSUFFICIENT_CREDITS',
|
|
988
|
+
balance,
|
|
989
|
+
required,
|
|
990
|
+
pricing_url: structured.pricing_url,
|
|
991
|
+
_hint: structured._hint,
|
|
992
|
+
}, null, 2);
|
|
993
|
+
|
|
994
|
+
return uiResult(UI.plans, text, structured);
|
|
995
|
+
}
|
|
996
|
+
|
|
942
997
|
module.exports = {
|
|
943
998
|
MAX_FILE_BYTES,
|
|
944
999
|
MAX_TEXT_CHARS,
|
|
@@ -965,5 +1020,6 @@ module.exports = {
|
|
|
965
1020
|
buildProjectUrl,
|
|
966
1021
|
uiGenerating,
|
|
967
1022
|
uiCompleted,
|
|
1023
|
+
insufficientCreditsResult,
|
|
968
1024
|
appsEnabled,
|
|
969
1025
|
};
|
package/src/tools/models.js
CHANGED
|
@@ -466,6 +466,38 @@ function registerModelTools(server, client, options = {}) {
|
|
|
466
466
|
}
|
|
467
467
|
}
|
|
468
468
|
);
|
|
469
|
+
|
|
470
|
+
// ─── show_plans ──────────────────────────────────
|
|
471
|
+
// The upgrade card. Also rendered automatically when a generation is refused
|
|
472
|
+
// for credits — see insufficientCreditsResult() in _shared.js.
|
|
473
|
+
server.tool(
|
|
474
|
+
'show_plans',
|
|
475
|
+
'Show the user their Kolbo credit balance, current plan, and the available upgrade plans / credit packs as an interactive card. Use when the user asks about pricing, plans, upgrading, or how to get more credits. Prices shown are live and promo-adjusted. The card links to app.kolbo.ai/pricing to complete a purchase — never quote prices from memory, and never claim to have made a purchase for them.',
|
|
476
|
+
{},
|
|
477
|
+
async () => {
|
|
478
|
+
const data = await client.get('/v1/account/plans');
|
|
479
|
+
const structured = {
|
|
480
|
+
widget: 'plans',
|
|
481
|
+
reason: 'requested',
|
|
482
|
+
balance: data?.credits?.total,
|
|
483
|
+
current_plan: data?.current_plan || null,
|
|
484
|
+
plans: data?.plans || [],
|
|
485
|
+
credit_packs: data?.credit_packs || [],
|
|
486
|
+
pricing_url: data?.pricing_url || 'https://app.kolbo.ai/pricing',
|
|
487
|
+
};
|
|
488
|
+
// structuredContent SHADOWS the text on widget hosts, so everything the
|
|
489
|
+
// agent needs to talk about pricing has to live in the object above.
|
|
490
|
+
const text = JSON.stringify({
|
|
491
|
+
credits: structured.balance,
|
|
492
|
+
current_plan: structured.current_plan,
|
|
493
|
+
plans: structured.plans,
|
|
494
|
+
credit_packs: structured.credit_packs,
|
|
495
|
+
pricing_url: structured.pricing_url,
|
|
496
|
+
_hint: 'A plans card is rendered for the user. Summarise briefly; do NOT paste the price table. Purchases are completed by the user on the pricing page — you cannot buy on their behalf.',
|
|
497
|
+
}, null, 2);
|
|
498
|
+
return uiResult(UI.plans, text, structured);
|
|
499
|
+
}
|
|
500
|
+
);
|
|
469
501
|
}
|
|
470
502
|
|
|
471
503
|
module.exports = { registerModelTools };
|