@kolbo/mcp 1.74.0 → 1.75.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/apps/widgets/list.js +6 -1
- package/src/tools/projects.js +2 -0
- package/src/tools/visual_dna.js +12 -3
package/package.json
CHANGED
package/src/apps/widgets/list.js
CHANGED
|
@@ -15,6 +15,8 @@ const { widgetPage } = require('../html');
|
|
|
15
15
|
* title: 'Your Projects',
|
|
16
16
|
* items: [{
|
|
17
17
|
* id, title, subtitle, // main two lines
|
|
18
|
+
* thumbnail, // optional — real image; falls back to the letter monogram on
|
|
19
|
+
* // missing/failed load, same idiom as catalog.js model icons
|
|
18
20
|
* badge, // small pill, e.g. role/status ("owner", "default", "shared")
|
|
19
21
|
* meta, // small trailing text, e.g. a date or count
|
|
20
22
|
* open_url, // optional — renders an "Open" button (openLink)
|
|
@@ -71,8 +73,11 @@ function apply(result) {
|
|
|
71
73
|
|
|
72
74
|
function itemHTML(item, i) {
|
|
73
75
|
var clickable = !!item.use_hint;
|
|
76
|
+
var avatar = item.thumbnail
|
|
77
|
+
? '<img class="k-audio-art" src="' + esc(item.thumbnail) + '" alt="" loading="lazy" onerror="this.outerHTML=monogram(\\'' + esc(item.title || '?').replace(/'/g, '') + '\\')">'
|
|
78
|
+
: monogram(item.title || '?');
|
|
74
79
|
return '<div class="k-audio-row" data-i="' + i + '"' + (clickable ? ' style="cursor:pointer"' : '') + '>' +
|
|
75
|
-
|
|
80
|
+
avatar +
|
|
76
81
|
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(item.title || '') + '</div>' +
|
|
77
82
|
(item.subtitle ? '<div class="k-audio-sub">' + esc(item.subtitle) + '</div>' : '') + '</div>' +
|
|
78
83
|
(item.badge ? '<span class="k-chip" style="flex:none">' + esc(item.badge) + '</span>' : '') +
|
package/src/tools/projects.js
CHANGED
|
@@ -32,6 +32,7 @@ function registerProjectTools(server, client) {
|
|
|
32
32
|
role: p.role,
|
|
33
33
|
is_default: !!p.is_default,
|
|
34
34
|
is_archived: !!p.is_archived,
|
|
35
|
+
thumbnail_url: p.thumbnail_url || null,
|
|
35
36
|
open_url: buildProjectUrl(p.id, { is_default: !!p.is_default })
|
|
36
37
|
}));
|
|
37
38
|
const text = JSON.stringify({
|
|
@@ -48,6 +49,7 @@ function registerProjectTools(server, client) {
|
|
|
48
49
|
id: p.id,
|
|
49
50
|
title: p.name,
|
|
50
51
|
subtitle: p.role + (p.is_default ? ' · default' : '') + (p.is_archived ? ' · archived' : ''),
|
|
52
|
+
thumbnail: p.thumbnail_url,
|
|
51
53
|
open_url: p.open_url,
|
|
52
54
|
use_hint: 'Use my "{TITLE}" project (project_id: {ID}) for what I do next.'
|
|
53
55
|
})),
|
package/src/tools/visual_dna.js
CHANGED
|
@@ -30,10 +30,12 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
30
30
|
prompt_helper: z.string().optional().describe('Optional description/notes to guide DNA extraction'),
|
|
31
31
|
images: z.array(z.string()).optional().describe('Array of image sources (URLs or absolute local paths). Max 4.'),
|
|
32
32
|
video: z.string().optional().describe('Optional video source (URL or absolute local path)'),
|
|
33
|
-
audio: z.string().optional().describe('Optional audio source (URL or absolute local path)'),
|
|
33
|
+
audio: z.string().optional().describe('Optional audio source (URL or absolute local path) — the character\'s voice, 5-30s of clean speech. Stored on the DNA and used two ways: (1) as REFERENCE AUDIO in video generation — attaching this DNA to an image-to-video generation on a model with audio slots (Seedance 2.x, Wan 3.0) auto-attaches the clip and tells the model it is that character\'s voice; (2) as the source for a real speaking voice, but ONLY when you ask for one — see `voice_source`.'),
|
|
34
|
+
voice_source: z.enum(['none', 'clone', 'assign', 'design']).optional().describe('What to do about a SPEAKING voice. **Pass "none" when the audio is just a reference clip** (the usual case for video work) — the clip is stored and usable as video reference audio, and nothing else happens. "clone" mints an ElevenLabs voice from the uploaded audio, which consumes a voice slot and may EVICT another of the user\'s voices to free one; it also makes the DNA addressable as `dna_<id>` in text-to-speech. "assign" points at an existing voice (pass `assigned_voice_id`). "design" generates a voice from the character\'s look. ⚠️ Omitting this while passing `audio` keeps the legacy behaviour and CLONES — pass "none" explicitly unless the user asked for a voice.'),
|
|
35
|
+
assigned_voice_id: z.string().optional().describe('Voice to attach when voice_source="assign" — a `custom_<id>` from the user\'s clones or a voice_id from `list_voices`.'),
|
|
34
36
|
character_sheet_url: z.string().optional().describe('URL of a reference sheet (from `generate_character_sheet`, any sheet_type) to set as the DNA\'s primary reference. Works for ALL DNA types — character turnaround, product detail sheet, location sheet, or style board — and is the single biggest consistency booster. Omit only when the user declines.')
|
|
35
37
|
},
|
|
36
|
-
async ({ name, dna_type, prompt_helper, images, video, audio, character_sheet_url }) => {
|
|
38
|
+
async ({ name, dna_type, prompt_helper, images, video, audio, voice_source, assigned_voice_id, character_sheet_url }) => {
|
|
37
39
|
if (!name || !name.trim()) {
|
|
38
40
|
throw new Error('name is required');
|
|
39
41
|
}
|
|
@@ -58,6 +60,10 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
58
60
|
if (dna_type) form.append('dnaType', dna_type);
|
|
59
61
|
if (prompt_helper) form.append('promptHelper', prompt_helper);
|
|
60
62
|
if (character_sheet_url) form.append('characterSheetUrl', character_sheet_url);
|
|
63
|
+
// Omitted stays omitted: the server infers 'clone' from a present audio clip, which is the
|
|
64
|
+
// long-standing behaviour older installs depend on. Only an explicit choice is forwarded.
|
|
65
|
+
if (voice_source) form.append('voiceSource', voice_source);
|
|
66
|
+
if (assigned_voice_id) form.append('assignedVoiceId', assigned_voice_id);
|
|
61
67
|
|
|
62
68
|
for (const f of imageFiles) {
|
|
63
69
|
form.append('images', f.buffer, { filename: f.filename, contentType: f.contentType });
|
|
@@ -125,8 +131,11 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
125
131
|
const total = result.total != null ? result.total : (result.count || dnas.length);
|
|
126
132
|
// Full profiles measured 74,310 chars — the embedded analysis/description
|
|
127
133
|
// blobs are large and the model only needs enough to pick an id.
|
|
134
|
+
// `has_voice_reference` rides along because it changes what a DNA DOES in a generation:
|
|
135
|
+
// such a DNA brings its own voice as reference audio on models with audio slots. Without
|
|
136
|
+
// it the model has to fetch each DNA in full just to find out.
|
|
128
137
|
const text = compactList(dnas, {
|
|
129
|
-
fields: ['id', 'name', 'type', 'folder_id', 'tags', 'thumbnail'],
|
|
138
|
+
fields: ['id', 'name', 'type', 'folder_id', 'tags', 'thumbnail', 'has_voice_reference'],
|
|
130
139
|
cap: 60,
|
|
131
140
|
total,
|
|
132
141
|
note: 'Narrow with `search`, `tags`, or `collection`, or pass `page`/`limit` for the rest; get_visual_dna returns one in full.',
|