@kolbo/mcp 1.87.7 → 1.87.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/apps/widgets/list.js +31 -1
- package/src/apps/widgets/mediaGrid.js +25 -5
- package/src/tools/color_palettes.js +3 -3
- package/src/tools/docs.js +3 -1
- package/src/tools/moodboards.js +8 -6
- package/src/tools/music_library.js +9 -4
- package/src/tools/projects.js +6 -2
- package/src/tools/stock_library.js +8 -3
- package/src/tools/visual_dna.js +7 -2
- package/src/tools/voices.js +4 -1
package/package.json
CHANGED
package/src/apps/widgets/list.js
CHANGED
|
@@ -47,6 +47,8 @@ var state = null;
|
|
|
47
47
|
function boot(sc) {
|
|
48
48
|
var list = listPayload(sc);
|
|
49
49
|
if (!list) return false;
|
|
50
|
+
// listPayload() normalises plain shapes; carry the paging contract through.
|
|
51
|
+
if (sc && sc.page_tool && !list.page_tool) { list.page_tool = sc.page_tool; list.next_args = sc.next_args; list.page = sc.page; }
|
|
50
52
|
state = list;
|
|
51
53
|
el('title').textContent = list.title || 'List';
|
|
52
54
|
var total = list.total != null ? list.total : list.items.length;
|
|
@@ -58,13 +60,41 @@ function boot(sc) {
|
|
|
58
60
|
window.kolbo.notifySize();
|
|
59
61
|
return true;
|
|
60
62
|
}
|
|
61
|
-
|
|
63
|
+
// Render every item the server sent (the old slice(0, 40) silently dropped
|
|
64
|
+
// rows, and nothing told the user). Load more only when the server said how.
|
|
65
|
+
var h = list.items.map(itemHTML).join('');
|
|
66
|
+
var shown = list.items.length;
|
|
67
|
+
if (list.page_tool && list.next_args) {
|
|
68
|
+
h += '<button class="k-btn" id="load-more" style="width:100%;margin-top:10px">Load more' +
|
|
69
|
+
(total > shown ? ' (' + shown + ' of ' + total + ' shown)' : '') + '</button>';
|
|
70
|
+
}
|
|
71
|
+
el('stage').innerHTML = h;
|
|
62
72
|
el('stage').classList.remove('k-empty');
|
|
63
73
|
wire();
|
|
74
|
+
var lm = el('load-more');
|
|
75
|
+
if (lm) lm.onclick = function () { fetchNextPage(lm); };
|
|
64
76
|
window.kolbo.notifySize();
|
|
65
77
|
return true;
|
|
66
78
|
}
|
|
67
79
|
|
|
80
|
+
function fetchNextPage(btn) {
|
|
81
|
+
if (!state || !state.page_tool || !state.next_args || btn.disabled) return;
|
|
82
|
+
btn.disabled = true;
|
|
83
|
+
var label = btn.textContent;
|
|
84
|
+
btn.innerHTML = '<span class="k-spin"></span> Loading';
|
|
85
|
+
window.kolbo.callTool(state.page_tool, state.next_args).then(function (res) {
|
|
86
|
+
var sc = structured(res);
|
|
87
|
+
var next = sc ? listPayload(sc) || sc : null;
|
|
88
|
+
var more = (next && next.items) || [];
|
|
89
|
+
if ((res && res.isError) || !next) { btn.disabled = false; btn.textContent = 'Could not load more — try again'; return; }
|
|
90
|
+
if (!more.length) { btn.textContent = 'No more results'; return; }
|
|
91
|
+
state.items = state.items.concat(more);
|
|
92
|
+
state.next_args = sc.next_args;
|
|
93
|
+
if (sc.total != null) state.total = sc.total;
|
|
94
|
+
boot(Object.assign({}, state, { widget: 'list', page_tool: state.page_tool, next_args: state.next_args }));
|
|
95
|
+
}).catch(function () { btn.disabled = false; btn.textContent = label; });
|
|
96
|
+
}
|
|
97
|
+
|
|
68
98
|
function apply(result) {
|
|
69
99
|
if (!result) return false;
|
|
70
100
|
var inner = result.result || result;
|
|
@@ -60,9 +60,14 @@ function boot(sc) {
|
|
|
60
60
|
h += audioItems.map(audioRowHTML).join('');
|
|
61
61
|
}
|
|
62
62
|
var shown = visualItems.length + audioItems.length;
|
|
63
|
-
|
|
63
|
+
// Only a tool that told us HOW to page (page_tool + next_args / query) gets a
|
|
64
|
+
// button. Every other grid used to render a Load more that did nothing —
|
|
65
|
+
// fetchNextPage returned early with no page_tool — which read as "broken".
|
|
66
|
+
if (sc.total != null && sc.total > shown && sc.page_tool && (sc.next_args || sc.query || sc.page)) {
|
|
64
67
|
h += '<button class="k-btn" id="load-more" style="width:100%;margin-top:10px">Load more (' +
|
|
65
68
|
shown + ' of ' + sc.total + ' shown)</button>';
|
|
69
|
+
} else if (sc.total != null && sc.total > shown) {
|
|
70
|
+
h += '<div style="text-align:center;font-size:12px;color:var(--text-muted);margin-top:10px">' + shown + ' of ' + sc.total + ' shown</div>';
|
|
66
71
|
}
|
|
67
72
|
el('stage').innerHTML = h;
|
|
68
73
|
el('stage').classList.remove('k-empty');
|
|
@@ -177,14 +182,26 @@ function fetchNextPage(btn) {
|
|
|
177
182
|
btn.disabled = true;
|
|
178
183
|
var label = btn.textContent;
|
|
179
184
|
btn.innerHTML = '<span class="k-spin"></span> Loading';
|
|
185
|
+
// The server knows its own paging arg names (page/limit, offset, cursor…):
|
|
186
|
+
// when it shipped next_args, send exactly that. The query+page shape is the
|
|
187
|
+
// legacy fallback list_media has always used.
|
|
180
188
|
var args = {};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
if (state.next_args) {
|
|
190
|
+
for (var nk in state.next_args) { if (state.next_args[nk] !== undefined && state.next_args[nk] !== null) args[nk] = state.next_args[nk]; }
|
|
191
|
+
} else {
|
|
192
|
+
var q = state.query || {};
|
|
193
|
+
for (var k in q) { if (q[k] !== undefined && q[k] !== null && q[k] !== '') args[k] = q[k]; }
|
|
194
|
+
args.page = next;
|
|
195
|
+
if (state.page_size) args.page_size = state.page_size;
|
|
196
|
+
}
|
|
185
197
|
|
|
186
198
|
window.kolbo.callTool(state.page_tool, args).then(function (res) {
|
|
187
199
|
var sc = structured(res);
|
|
200
|
+
if (res && res.isError) {
|
|
201
|
+
btn.disabled = false;
|
|
202
|
+
btn.textContent = 'Could not load more — try again';
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
188
205
|
var more = (sc && sc.items) || [];
|
|
189
206
|
if (!more.length) {
|
|
190
207
|
// Nothing came back: say so rather than restoring a button that still
|
|
@@ -194,6 +211,9 @@ function fetchNextPage(btn) {
|
|
|
194
211
|
}
|
|
195
212
|
state.items = (state.items || []).concat(more);
|
|
196
213
|
state.page = (sc && sc.page) || next;
|
|
214
|
+
// The next page's args come from THIS response; none means this was the last page.
|
|
215
|
+
state.next_args = sc && sc.next_args;
|
|
216
|
+
if (sc && sc.next_args == null && sc.page_tool == null && state.next_args === undefined) state.next_args = undefined;
|
|
197
217
|
if (sc && sc.total != null) state.total = sc.total;
|
|
198
218
|
boot(state); // re-renders the grid + a fresh Load more button
|
|
199
219
|
}).catch(function () {
|
|
@@ -40,7 +40,7 @@ function registerColorPaletteTools(server, client, options = {}) {
|
|
|
40
40
|
return uiResult(UI.mediaGrid, text, {
|
|
41
41
|
widget: 'media-grid',
|
|
42
42
|
title: 'Color DNA Palettes',
|
|
43
|
-
items: palettes.slice(0,
|
|
43
|
+
items: palettes.slice(0, 300).map(p => ({
|
|
44
44
|
id: p.id,
|
|
45
45
|
title: p.is_active ? `${p.name} (active)` : p.name,
|
|
46
46
|
// First source image when available, else an SVG strip of the palette itself.
|
|
@@ -48,8 +48,8 @@ function registerColorPaletteTools(server, client, options = {}) {
|
|
|
48
48
|
media_type: 'image',
|
|
49
49
|
use_hint: 'Activate Color DNA palette "{TITLE}" (color_palette_id: {ID}) so it grades my generations.'
|
|
50
50
|
})),
|
|
51
|
-
total:
|
|
52
|
-
has_more: palettes.length >
|
|
51
|
+
total: palettes.length,
|
|
52
|
+
has_more: palettes.length > 300
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
package/src/tools/docs.js
CHANGED
|
@@ -63,7 +63,9 @@ function registerDocTools(server, client) {
|
|
|
63
63
|
subtitle: (d.updated_at ? String(d.updated_at).slice(0, 10) : '') + (d.is_shared ? ' · shared' : ''),
|
|
64
64
|
open_url: d.share_url || null
|
|
65
65
|
})),
|
|
66
|
-
total: docs.length
|
|
66
|
+
total: docs.length,
|
|
67
|
+
page_tool: 'list_docs',
|
|
68
|
+
next_args: docs.length >= (limit || 20) ? { project_id, page: (page || 1) + 1, limit } : undefined
|
|
67
69
|
});
|
|
68
70
|
}
|
|
69
71
|
);
|
package/src/tools/moodboards.js
CHANGED
|
@@ -11,14 +11,16 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
11
11
|
// ─── list_moodboards ───────────────────────────────────────
|
|
12
12
|
server.tool(
|
|
13
13
|
'list_moodboards',
|
|
14
|
-
'List moodboards.
|
|
14
|
+
'List moodboards. Default scope is "personal" — the user\'s OWN custom moodboards, which is what the card shows. Pass scope "preset"/"global" for the system presets, "organization" for org-shared, or "all" for everything. Returns id, name, master_prompt, thumbnail, and image URLs for each.',
|
|
15
15
|
{
|
|
16
|
-
scope: z.enum(['all', 'personal', 'preset', 'global', 'organization']).optional().describe('Filter by scope. Default: "
|
|
16
|
+
scope: z.enum(['all', 'personal', 'preset', 'global', 'organization']).optional().describe('Filter by scope. Default: "personal" (only the user\'s own custom moodboards). "preset"/"global" = system presets. "organization" = org-shared. "all" = everything accessible.'),
|
|
17
17
|
project_id: projectScopeReadField
|
|
18
18
|
},
|
|
19
19
|
async ({ scope, project_id } = {}) => {
|
|
20
20
|
const params = new URLSearchParams();
|
|
21
|
-
|
|
21
|
+
// Users asked to see only THEIR moodboards on the card, not the system presets.
|
|
22
|
+
const effectiveScope = scope || 'personal';
|
|
23
|
+
if (effectiveScope !== 'all') params.set('scope', effectiveScope);
|
|
22
24
|
if (project_id) params.set('project_id', project_id);
|
|
23
25
|
const qs = params.toString();
|
|
24
26
|
const result = await client.get(`/v1/moodboards${qs ? '?' + qs : ''}`);
|
|
@@ -36,7 +38,7 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
36
38
|
return uiResult(UI.mediaGrid, text, {
|
|
37
39
|
widget: 'media-grid',
|
|
38
40
|
title: 'Moodboards',
|
|
39
|
-
items: moodboards.slice(0,
|
|
41
|
+
items: moodboards.slice(0, 300).map(mb => ({
|
|
40
42
|
id: mb.id,
|
|
41
43
|
title: mb.name,
|
|
42
44
|
// API returns thumbnail_url + images[] (sdk listMoodboards) — both
|
|
@@ -46,8 +48,8 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
46
48
|
media_type: 'image',
|
|
47
49
|
use_hint: 'Apply moodboard "{TITLE}" (moodboard_id: {ID}) to my next generation.'
|
|
48
50
|
})),
|
|
49
|
-
total:
|
|
50
|
-
has_more: moodboards.length >
|
|
51
|
+
total: moodboards.length,
|
|
52
|
+
has_more: moodboards.length > 300
|
|
51
53
|
});
|
|
52
54
|
}
|
|
53
55
|
);
|
|
@@ -26,7 +26,7 @@ function trackItem(track) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function tracksResult(ui, title, tracks, total) {
|
|
29
|
+
function tracksResult(ui, title, tracks, total, paging) {
|
|
30
30
|
if (!tracks.length) return { content: [{ type: 'text', text: 'No SYNCI tracks found.' }] };
|
|
31
31
|
const text = [
|
|
32
32
|
`Found ${tracks.length} track${tracks.length === 1 ? '' : 's'}${total ? ` (of ${total})` : ''}.`,
|
|
@@ -38,8 +38,11 @@ function tracksResult(ui, title, tracks, total) {
|
|
|
38
38
|
return uiResult(UI.mediaGrid, text, {
|
|
39
39
|
widget: 'media-grid',
|
|
40
40
|
title,
|
|
41
|
-
items: tracks.
|
|
41
|
+
items: tracks.map(trackItem),
|
|
42
42
|
total: total != null ? total : tracks.length,
|
|
43
|
+
...(paging && total != null && (paging.offset + tracks.length) < total
|
|
44
|
+
? { page_tool: paging.tool, next_args: { ...paging.args, offset: paging.offset + tracks.length, limit: paging.limit } }
|
|
45
|
+
: {}),
|
|
43
46
|
});
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -90,7 +93,8 @@ function registerMusicLibraryTools(server, client, options = {}) {
|
|
|
90
93
|
},
|
|
91
94
|
async (args) => {
|
|
92
95
|
const result = await client.post('/v1/music-library/search', args);
|
|
93
|
-
return tracksResult(ui, `SYNCI — ${args.query || 'Search'}`, result.tracks || [], result.total
|
|
96
|
+
return tracksResult(ui, `SYNCI — ${args.query || 'Search'}`, result.tracks || [], result.total,
|
|
97
|
+
{ tool: 'search_music_library', args, offset: args.offset || 0, limit: args.limit || (result.tracks || []).length || 20 });
|
|
94
98
|
},
|
|
95
99
|
);
|
|
96
100
|
|
|
@@ -118,7 +122,8 @@ function registerMusicLibraryTools(server, client, options = {}) {
|
|
|
118
122
|
if (limit != null) params.set('limit', String(limit));
|
|
119
123
|
if (offset != null) params.set('offset', String(offset));
|
|
120
124
|
const result = await client.get(`/v1/music-library/catalog?${params.toString()}`);
|
|
121
|
-
return tracksResult(ui, 'SYNCI Music Library', result.tracks || [], result.total
|
|
125
|
+
return tracksResult(ui, 'SYNCI Music Library', result.tracks || [], result.total,
|
|
126
|
+
{ tool: 'browse_music_library', args: { sort }, offset: offset || 0, limit: limit || (result.tracks || []).length || 20 });
|
|
122
127
|
},
|
|
123
128
|
);
|
|
124
129
|
|
package/src/tools/projects.js
CHANGED
|
@@ -60,7 +60,9 @@ function registerProjectTools(server, client) {
|
|
|
60
60
|
open_url: p.open_url,
|
|
61
61
|
use_hint: 'Use my "{TITLE}" project (project_id: {ID}) for what I do next.'
|
|
62
62
|
})),
|
|
63
|
-
total: projects.length
|
|
63
|
+
total: projects.length,
|
|
64
|
+
page_tool: 'list_projects',
|
|
65
|
+
next_args: projects.length >= (limit || 50) ? { search, include_archived, page: (page || 1) + 1, limit } : undefined
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
);
|
|
@@ -359,7 +361,9 @@ function registerProjectTools(server, client) {
|
|
|
359
361
|
].filter(Boolean).join(' · '),
|
|
360
362
|
badge: (s.types && s.types[0]) || undefined
|
|
361
363
|
})),
|
|
362
|
-
total: sessions.length
|
|
364
|
+
total: sessions.length,
|
|
365
|
+
page_tool: 'list_sessions',
|
|
366
|
+
next_args: sessions.length >= (limit || 20) ? { project_id, type, types, page: (page || 1) + 1, limit } : undefined
|
|
363
367
|
});
|
|
364
368
|
}
|
|
365
369
|
);
|
|
@@ -84,7 +84,7 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
84
84
|
// the media grid on them, so the card rendered one broken-file glyph per cell.
|
|
85
85
|
// media.js and listResult() have always done it this way; these five lagged.
|
|
86
86
|
{
|
|
87
|
-
const items = assets.
|
|
87
|
+
const items = assets.map((a) => {
|
|
88
88
|
const mt = widgetMediaType(a.mediaType);
|
|
89
89
|
return {
|
|
90
90
|
id: a.source + ':' + a.sourceId,
|
|
@@ -107,7 +107,12 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
107
107
|
title: 'Stock — "' + (args.query || 'browse') + '"',
|
|
108
108
|
items,
|
|
109
109
|
total: result.total != null ? result.total : assets.length,
|
|
110
|
-
has_more: !!result.hasMore
|
|
110
|
+
has_more: !!result.hasMore,
|
|
111
|
+
page: args.page || 1,
|
|
112
|
+
page_tool: 'search_stock_media',
|
|
113
|
+
next_args: result.hasMore
|
|
114
|
+
? { ...args, page: (args.page || 1) + 1, ...(result.nextCursor ? { cursor: result.nextCursor } : {}) }
|
|
115
|
+
: undefined
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -172,7 +177,7 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
172
177
|
// media.js and listResult() have always done it this way; these five lagged.
|
|
173
178
|
{
|
|
174
179
|
const collections = result.collections || [];
|
|
175
|
-
const items = collections.slice(0,
|
|
180
|
+
const items = collections.slice(0, 300).map((c) => ({
|
|
176
181
|
id: c.id,
|
|
177
182
|
title: c.title,
|
|
178
183
|
subtitle: [c.kind, c.mediaType].filter(Boolean).join(' · '),
|
package/src/tools/visual_dna.js
CHANGED
|
@@ -154,7 +154,7 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
154
154
|
return uiResult(UI.mediaGrid, text, {
|
|
155
155
|
widget: 'media-grid',
|
|
156
156
|
title: 'Visual DNA Profiles',
|
|
157
|
-
items: dnas.
|
|
157
|
+
items: dnas.map(d => ({
|
|
158
158
|
id: d.id,
|
|
159
159
|
title: d.name,
|
|
160
160
|
subtitle: (d.dna_type || '') + (Array.isArray(d.tags) && d.tags.length ? ' · ' + d.tags.slice(0, 3).join(', ') : ''),
|
|
@@ -164,7 +164,12 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
164
164
|
use_hint: 'Use Visual DNA "{TITLE}" (id: {ID}) in my next generation for character/style consistency.'
|
|
165
165
|
})),
|
|
166
166
|
total,
|
|
167
|
-
has_more: result.has_more
|
|
167
|
+
has_more: !!result.has_more,
|
|
168
|
+
page: page && page > 0 ? Math.floor(page) : 1,
|
|
169
|
+
page_tool: 'list_visual_dnas',
|
|
170
|
+
next_args: result.has_more
|
|
171
|
+
? { scope, search, collection, tags, project_id, page: (page && page > 0 ? Math.floor(page) : 1) + 1, limit }
|
|
172
|
+
: undefined
|
|
168
173
|
});
|
|
169
174
|
}
|
|
170
175
|
);
|
package/src/tools/voices.js
CHANGED
|
@@ -99,7 +99,10 @@ function registerVoiceTools(server, client, options = {}) {
|
|
|
99
99
|
use_hint: 'Use voice "{TITLE}" (voice_id: {ID}) for text-to-speech — ask me what text to speak.'
|
|
100
100
|
})),
|
|
101
101
|
total: voices.length,
|
|
102
|
-
has_more: more > 0
|
|
102
|
+
has_more: more > 0,
|
|
103
|
+
page: pageNum,
|
|
104
|
+
page_tool: 'list_voices',
|
|
105
|
+
next_args: more > 0 ? { language, gender, provider, page: pageNum + 1, limit: perPage } : undefined
|
|
103
106
|
});
|
|
104
107
|
}
|
|
105
108
|
|