@kolbo/mcp 1.0.0 → 1.1.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/README.md +46 -11
- package/package.json +9 -5
- package/src/client.js +85 -2
- package/src/index.js +64 -0
- package/src/polling.js +32 -6
- package/src/tools/chat.js +135 -0
- package/src/tools/generate.js +371 -224
- package/src/tools/models.js +5 -0
- package/src/tools/moodboards.js +45 -0
- package/src/tools/visual_dna.js +198 -0
package/src/tools/generate.js
CHANGED
|
@@ -1,224 +1,371 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
},
|
|
151
|
-
async ({
|
|
152
|
-
const gen = await client.post('/v1/generate/
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const result = await pollUntilDone(client, gen.generation_id, {
|
|
157
|
-
interval: (gen.poll_interval_hint ||
|
|
158
|
-
timeout:
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
content: [{
|
|
163
|
-
type: 'text',
|
|
164
|
-
text: JSON.stringify({
|
|
165
|
-
urls: result.result.urls,
|
|
166
|
-
|
|
167
|
-
duration: result.result.duration
|
|
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
|
-
|
|
1
|
+
/* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC
|
|
2
|
+
* CONTRACT. Never rename, remove, or break an existing tool/arg — old cached
|
|
3
|
+
* `npx @kolbo/mcp` installs in the wild will break silently. Add new tools or
|
|
4
|
+
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
|
+
|
|
6
|
+
const { pollUntilDone } = require('../polling');
|
|
7
|
+
|
|
8
|
+
function registerGenerateTools(server, client) {
|
|
9
|
+
// ─── generate_image ────────────────────────────────────────
|
|
10
|
+
server.tool(
|
|
11
|
+
'generate_image',
|
|
12
|
+
'Generate image(s) from a text prompt using Kolbo AI. Supports Visual DNA profiles (for character/style/product consistency), moodboards (for style direction), reference images (for composition guidance), batch generation (num_images), and web-search grounding. For EDITING an existing image, use generate_image_edit instead. For a coordinated multi-scene set (storyboard, ad campaign), use generate_creative_director. Returns the final image URL(s) when complete.',
|
|
13
|
+
{
|
|
14
|
+
prompt: { type: 'string', description: 'Text description of the image to generate' },
|
|
15
|
+
model: { type: 'string', description: 'Model identifier. Use list_models type="image" to see options. Omit for Smart Select.' },
|
|
16
|
+
aspect_ratio: { type: 'string', description: 'Aspect ratio (e.g., "1:1", "16:9", "9:16"). Default: "1:1"' },
|
|
17
|
+
enhance_prompt: { type: 'boolean', description: 'Enhance the prompt for better results. Default: true' },
|
|
18
|
+
num_images: { type: 'number', description: 'Number of images to generate in one call. Default: 1' },
|
|
19
|
+
reference_images: { type: 'array', description: 'Array of image URLs used as composition/style references (NOT as source images for editing — use generate_image_edit for that).' },
|
|
20
|
+
visual_dna_ids: { type: 'array', description: 'Array of Visual DNA profile IDs (from create_visual_dna / list_visual_dnas) to apply for character / style / product / scene consistency. Pass the `id` field of each profile. Use this when the user wants to keep the same character or style across multiple images.' },
|
|
21
|
+
moodboard_id: { type: 'string', description: 'Moodboard ID (from list_moodboards / get_moodboard) whose master_prompt and style_guide should be applied to this generation.' },
|
|
22
|
+
enable_web_search: { type: 'boolean', description: 'Enable web-search grounding for the prompt (useful for current events, brand references, real-world accuracy). Default: false' }
|
|
23
|
+
},
|
|
24
|
+
async ({ prompt, model, aspect_ratio, enhance_prompt, num_images, reference_images, visual_dna_ids, moodboard_id, enable_web_search }) => {
|
|
25
|
+
const gen = await client.post('/v1/generate/image', {
|
|
26
|
+
prompt, model, aspect_ratio, enhance_prompt, num_images,
|
|
27
|
+
reference_images, visual_dna_ids, moodboard_id, enable_web_search
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
31
|
+
interval: (gen.poll_interval_hint || 3) * 1000,
|
|
32
|
+
timeout: 120000
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: 'text',
|
|
38
|
+
text: JSON.stringify({
|
|
39
|
+
urls: result.result.urls,
|
|
40
|
+
model: result.result.model,
|
|
41
|
+
prompt_used: result.result.prompt_used
|
|
42
|
+
}, null, 2)
|
|
43
|
+
}]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// ─── generate_image_edit ──────────────────────────────────
|
|
49
|
+
server.tool(
|
|
50
|
+
'generate_image_edit',
|
|
51
|
+
'Edit or transform an existing image using AI. Provide the source image URL(s) in `source_images` and describe the edit in `prompt` (e.g., "remove the background", "change the car color to red", "add sunglasses to the person"). Supports Visual DNA profiles and moodboards for style-consistent edits. For creating a brand new image from scratch, use generate_image. Returns the edited image URL(s) when complete.',
|
|
52
|
+
{
|
|
53
|
+
prompt: { type: 'string', description: 'Description of the edit to apply (e.g., "remove the background", "change the sky to sunset")' },
|
|
54
|
+
model: { type: 'string', description: 'Model identifier. Use list_models type="image_edit" to see options. Omit for Smart Select.' },
|
|
55
|
+
source_images: { type: 'array', description: 'Array of source image URLs to edit. Typically one, but some models accept multiple for compositing.' },
|
|
56
|
+
aspect_ratio: { type: 'string', description: 'Output aspect ratio (e.g., "1:1", "16:9", "9:16"). Default: "1:1"' },
|
|
57
|
+
enhance_prompt: { type: 'boolean', description: 'Enhance the prompt for better results. Default: true' },
|
|
58
|
+
num_images: { type: 'number', description: 'Number of output images. Default: 1' },
|
|
59
|
+
visual_dna_ids: { type: 'array', description: 'Array of Visual DNA profile IDs to apply for consistency with an existing character / style / product.' },
|
|
60
|
+
moodboard_id: { type: 'string', description: 'Moodboard ID whose master_prompt and style_guide should be applied.' },
|
|
61
|
+
enable_web_search: { type: 'boolean', description: 'Enable web-search grounding. Default: false' }
|
|
62
|
+
},
|
|
63
|
+
async ({ prompt, model, source_images, aspect_ratio, enhance_prompt, num_images, visual_dna_ids, moodboard_id, enable_web_search }) => {
|
|
64
|
+
const gen = await client.post('/v1/generate/image-edit', {
|
|
65
|
+
prompt, model, source_images, aspect_ratio, enhance_prompt, num_images,
|
|
66
|
+
visual_dna_ids, moodboard_id, enable_web_search
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
70
|
+
interval: (gen.poll_interval_hint || 3) * 1000,
|
|
71
|
+
timeout: 120000
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
content: [{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: JSON.stringify({
|
|
78
|
+
urls: result.result.urls,
|
|
79
|
+
model: result.result.model,
|
|
80
|
+
prompt_used: result.result.prompt_used
|
|
81
|
+
}, null, 2)
|
|
82
|
+
}]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// ─── generate_creative_director ─────────────────────────────
|
|
88
|
+
server.tool(
|
|
89
|
+
'generate_creative_director',
|
|
90
|
+
'Generate a multi-scene coordinated set from ONE creative brief. Use this INSTEAD of calling generate_image/generate_video multiple times when the user wants a storyboard, multi-scene ad, product showcase, or any set of related outputs that should share visual language. Produces 1–8 scenes in a single request with consistent style. Supports image mode and video mode (`workflow_type`). Visual DNA and moodboard references keep character/style consistent across every scene.',
|
|
91
|
+
{
|
|
92
|
+
prompt: { type: 'string', description: 'Creative brief or concept describing the full set of scenes to generate' },
|
|
93
|
+
scene_count: { type: 'number', description: 'Number of scenes to generate, 1–8. Default: 4' },
|
|
94
|
+
model: { type: 'string', description: 'Model identifier applied to every scene. Omit for Smart Select.' },
|
|
95
|
+
aspect_ratio: { type: 'string', description: 'Aspect ratio applied to every scene (e.g., "1:1", "16:9", "9:16"). Default: "1:1"' },
|
|
96
|
+
workflow_type: { type: 'string', description: '"image" (default) or "video"' },
|
|
97
|
+
duration: { type: 'number', description: 'Duration in seconds per scene (video mode only). E.g., 5 or 10.' },
|
|
98
|
+
enhance_prompt: { type: 'boolean', description: 'Enhance prompts per scene. Default: true' },
|
|
99
|
+
reference_images: { type: 'array', description: 'Array of reference image URLs to guide style/composition of every scene.' },
|
|
100
|
+
visual_dna_ids: { type: 'array', description: 'Array of Visual DNA profile IDs to apply consistently across every scene. This is the ideal way to keep a character or product looking the same in all scenes of a campaign.' },
|
|
101
|
+
moodboard_id: { type: 'string', description: 'A single moodboard ID whose master_prompt and style_guide should shape every scene.' },
|
|
102
|
+
moodboard_ids: { type: 'array', description: 'Multiple moodboard IDs when blending styles. Prefer `moodboard_id` for single moodboards.' }
|
|
103
|
+
},
|
|
104
|
+
async ({ prompt, scene_count, model, aspect_ratio, workflow_type, duration, enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids }) => {
|
|
105
|
+
const gen = await client.post('/v1/generate/creative-director', {
|
|
106
|
+
prompt, scene_count, model, aspect_ratio, workflow_type, duration,
|
|
107
|
+
enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
111
|
+
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
112
|
+
timeout: 600000,
|
|
113
|
+
statusUrl: `/v1/generate/creative-director/${gen.generation_id}/status`
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const scenes = (result.scenes || [])
|
|
117
|
+
.filter(s => s.status === 'completed')
|
|
118
|
+
.map(s => ({
|
|
119
|
+
scene_number: s.scene_number,
|
|
120
|
+
title: s.title,
|
|
121
|
+
image_urls: s.image_urls,
|
|
122
|
+
video_urls: s.video_urls
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
content: [{
|
|
127
|
+
type: 'text',
|
|
128
|
+
text: JSON.stringify({
|
|
129
|
+
scenes,
|
|
130
|
+
total_scenes: result.scenes?.length || 0,
|
|
131
|
+
completed_scenes: scenes.length
|
|
132
|
+
}, null, 2)
|
|
133
|
+
}]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// ─── generate_video ────────────────────────────────────────
|
|
139
|
+
server.tool(
|
|
140
|
+
'generate_video',
|
|
141
|
+
'Generate a video from a text prompt using Kolbo AI. For animating an existing still image into motion, use generate_video_from_image instead. For a coordinated multi-scene video campaign, use generate_creative_director with workflow_type="video". Supports Visual DNA profiles (for character consistency) and reference images (for style guidance). Returns the final video URL when complete.',
|
|
142
|
+
{
|
|
143
|
+
prompt: { type: 'string', description: 'Text description of the video to generate' },
|
|
144
|
+
model: { type: 'string', description: 'Model identifier. Use list_models type="video" to see options. Check supported_durations and supported_aspect_ratios.' },
|
|
145
|
+
aspect_ratio: { type: 'string', description: 'Aspect ratio (e.g., "16:9", "9:16", "1:1"). Default: "16:9"' },
|
|
146
|
+
duration: { type: 'number', description: 'Duration in seconds. Must be a value the chosen model supports — check supported_durations from list_models. Default: 5' },
|
|
147
|
+
enhance_prompt: { type: 'boolean', description: 'Enhance the prompt. Default: true' },
|
|
148
|
+
reference_images: { type: 'array', description: 'Array of image URLs used as visual references (style / composition / subject).' },
|
|
149
|
+
visual_dna_ids: { type: 'array', description: 'Array of Visual DNA profile IDs to keep a character / style consistent with prior generations.' }
|
|
150
|
+
},
|
|
151
|
+
async ({ prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, visual_dna_ids }) => {
|
|
152
|
+
const gen = await client.post('/v1/generate/video', {
|
|
153
|
+
prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, visual_dna_ids
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
157
|
+
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
158
|
+
timeout: 300000
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
content: [{
|
|
163
|
+
type: 'text',
|
|
164
|
+
text: JSON.stringify({
|
|
165
|
+
urls: result.result.urls,
|
|
166
|
+
model: result.result.model,
|
|
167
|
+
duration: result.result.duration,
|
|
168
|
+
thumbnail_url: result.result.thumbnail_url,
|
|
169
|
+
prompt_used: result.result.prompt_used
|
|
170
|
+
}, null, 2)
|
|
171
|
+
}]
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// ─── generate_video_from_image ─────────────────────────────
|
|
177
|
+
server.tool(
|
|
178
|
+
'generate_video_from_image',
|
|
179
|
+
'Animate an existing still image into a video using Kolbo AI. The image comes from `image_url`; `prompt` describes the motion (not the subject — the subject is already in the image). For generating a video from scratch, use generate_video. Returns the final video URL when complete.',
|
|
180
|
+
{
|
|
181
|
+
image_url: { type: 'string', description: 'URL of the source image to animate' },
|
|
182
|
+
prompt: { type: 'string', description: 'Text description of the desired MOTION (e.g., "camera slowly pans right while the character walks forward")' },
|
|
183
|
+
model: { type: 'string', description: 'Model identifier. Use list_models type="video_from_image" to see options.' },
|
|
184
|
+
aspect_ratio: { type: 'string', description: 'Output aspect ratio (e.g., "16:9", "9:16", "1:1"). Default: "16:9"' },
|
|
185
|
+
duration: { type: 'number', description: 'Duration in seconds. Must be a value the chosen model supports. Default: 5' },
|
|
186
|
+
enhance_prompt: { type: 'boolean', description: 'Enhance the motion prompt. Default: true' },
|
|
187
|
+
visual_dna_ids: { type: 'array', description: 'Array of Visual DNA profile IDs to maintain consistency with prior characters / styles.' }
|
|
188
|
+
},
|
|
189
|
+
async ({ image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids }) => {
|
|
190
|
+
const gen = await client.post('/v1/generate/video/from-image', {
|
|
191
|
+
image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
195
|
+
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
196
|
+
timeout: 300000
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [{
|
|
201
|
+
type: 'text',
|
|
202
|
+
text: JSON.stringify({
|
|
203
|
+
urls: result.result.urls,
|
|
204
|
+
model: result.result.model,
|
|
205
|
+
duration: result.result.duration,
|
|
206
|
+
thumbnail_url: result.result.thumbnail_url
|
|
207
|
+
}, null, 2)
|
|
208
|
+
}]
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// ─── generate_music ────────────────────────────────────────
|
|
214
|
+
server.tool(
|
|
215
|
+
'generate_music',
|
|
216
|
+
'Generate music from a text description using Kolbo AI. Supports instrumental mode, custom lyrics, style direction, and vocal gender. Default model is Suno. Returns the final audio URL when complete.',
|
|
217
|
+
{
|
|
218
|
+
prompt: { type: 'string', description: 'Text description of the music to generate (e.g., "upbeat electronic dance track with synthesizers")' },
|
|
219
|
+
model: { type: 'string', description: 'Model identifier. Use list_models type="music" to see options. Omit for Suno (default).' },
|
|
220
|
+
style: { type: 'string', description: 'Music style / genre (e.g., "pop", "rock", "lo-fi", "electronic", "jazz")' },
|
|
221
|
+
instrumental: { type: 'boolean', description: 'Generate instrumental only, no vocals. Default: false' },
|
|
222
|
+
lyrics: { type: 'string', description: 'Custom lyrics for the song. If omitted, lyrics are generated automatically from the prompt unless instrumental is true.' },
|
|
223
|
+
vocal_gender: { type: 'string', description: 'Preferred vocal gender: "male" or "female". Only applies when instrumental is false.' },
|
|
224
|
+
enhance_prompt: { type: 'boolean', description: 'Enhance the prompt. Default: true' }
|
|
225
|
+
},
|
|
226
|
+
async ({ prompt, model, style, instrumental, lyrics, vocal_gender, enhance_prompt }) => {
|
|
227
|
+
const gen = await client.post('/v1/generate/music', {
|
|
228
|
+
prompt, model, style, instrumental, lyrics, vocal_gender, enhance_prompt
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
232
|
+
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
233
|
+
timeout: 300000
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
content: [{
|
|
238
|
+
type: 'text',
|
|
239
|
+
text: JSON.stringify({
|
|
240
|
+
urls: result.result.urls,
|
|
241
|
+
title: result.result.title,
|
|
242
|
+
duration: result.result.duration,
|
|
243
|
+
lyrics: result.result.lyrics
|
|
244
|
+
}, null, 2)
|
|
245
|
+
}]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// ─── generate_speech ───────────────────────────────────────
|
|
251
|
+
server.tool(
|
|
252
|
+
'generate_speech',
|
|
253
|
+
'Convert text to speech using Kolbo AI. Default provider is ElevenLabs. To pick a specific voice by language/gender, call list_voices first and pass the returned voice_id (or a voice display name — both work). Returns the final audio URL when complete.',
|
|
254
|
+
{
|
|
255
|
+
text: { type: 'string', description: 'The text to convert to speech' },
|
|
256
|
+
voice: { type: 'string', description: 'Voice ID (from list_voices) or voice display name (e.g., "Rachel", "Adam"). Default: "Rachel"' },
|
|
257
|
+
model: { type: 'string', description: 'Model identifier. Use list_models type="speech" to see options. Default: eleven_v3' },
|
|
258
|
+
language: { type: 'string', description: 'Language code (e.g., "en-US", "he-IL", "es-ES"). Default: "en-US"' }
|
|
259
|
+
},
|
|
260
|
+
async ({ text, voice, model, language }) => {
|
|
261
|
+
const gen = await client.post('/v1/generate/speech', {
|
|
262
|
+
text, voice, model, language
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
266
|
+
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
267
|
+
timeout: 120000
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
content: [{
|
|
272
|
+
type: 'text',
|
|
273
|
+
text: JSON.stringify({
|
|
274
|
+
urls: result.result.urls,
|
|
275
|
+
voice: result.result.voice,
|
|
276
|
+
duration: result.result.duration
|
|
277
|
+
}, null, 2)
|
|
278
|
+
}]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// ─── generate_sound ────────────────────────────────────────
|
|
284
|
+
server.tool(
|
|
285
|
+
'generate_sound',
|
|
286
|
+
'Generate sound effects (not music, not speech) from a text description using Kolbo AI. Use this for ambient sounds, foley, impacts, atmospheres, UI sounds, etc. For music use generate_music; for voice use generate_speech. Returns the final audio URL when complete.',
|
|
287
|
+
{
|
|
288
|
+
prompt: { type: 'string', description: 'Text description of the sound effect (e.g., "thunder clap with rain", "door creaking open", "futuristic UI beep")' },
|
|
289
|
+
model: { type: 'string', description: 'Model identifier. Use list_models type="sound" to see options. Default: elevenlabs-sound-effects-v1' },
|
|
290
|
+
duration: { type: 'number', description: 'Duration in seconds. Omit for automatic duration.' }
|
|
291
|
+
},
|
|
292
|
+
async ({ prompt, model, duration }) => {
|
|
293
|
+
const gen = await client.post('/v1/generate/sound', {
|
|
294
|
+
prompt, model, duration
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
298
|
+
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
299
|
+
timeout: 120000
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
content: [{
|
|
304
|
+
type: 'text',
|
|
305
|
+
text: JSON.stringify({
|
|
306
|
+
urls: result.result.urls,
|
|
307
|
+
duration: result.result.duration
|
|
308
|
+
}, null, 2)
|
|
309
|
+
}]
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// ─── list_voices ─────────────────────────────────────────────
|
|
315
|
+
server.tool(
|
|
316
|
+
'list_voices',
|
|
317
|
+
'List available TTS voices for generate_speech. Returns preset voices and the user\'s own cloned/designed voices. Filter by provider, language, or gender to find the right voice. Use the returned `voice_id` as the `voice` parameter in generate_speech.',
|
|
318
|
+
{
|
|
319
|
+
provider: { type: 'string', description: 'Filter by provider (e.g., "elevenLabs", "google")' },
|
|
320
|
+
language: { type: 'string', description: 'Filter by language name or code (e.g., "English", "en-US")' },
|
|
321
|
+
gender: { type: 'string', description: 'Filter by gender (e.g., "Female", "Male")' }
|
|
322
|
+
},
|
|
323
|
+
async ({ provider, language, gender }) => {
|
|
324
|
+
const params = new URLSearchParams();
|
|
325
|
+
if (provider) params.set('provider', provider);
|
|
326
|
+
if (language) params.set('language', language);
|
|
327
|
+
if (gender) params.set('gender', gender);
|
|
328
|
+
|
|
329
|
+
const qs = params.toString();
|
|
330
|
+
const result = await client.get(`/v1/voices${qs ? '?' + qs : ''}`);
|
|
331
|
+
|
|
332
|
+
// Summarize for context window efficiency
|
|
333
|
+
const voices = (result.voices || []).map(v => ({
|
|
334
|
+
voice_id: v.voice_id,
|
|
335
|
+
name: v.name,
|
|
336
|
+
provider: v.provider,
|
|
337
|
+
language: v.language,
|
|
338
|
+
gender: v.gender,
|
|
339
|
+
custom: v.custom
|
|
340
|
+
}));
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
content: [{
|
|
344
|
+
type: 'text',
|
|
345
|
+
text: JSON.stringify({ voices, count: result.count }, null, 2)
|
|
346
|
+
}]
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// ─── get_generation_status ─────────────────────────────────
|
|
352
|
+
server.tool(
|
|
353
|
+
'get_generation_status',
|
|
354
|
+
'Check the status of a generation. Use this as a FALLBACK when a generation tool returned a timeout error — the generation is probably still running on the server. Pass the generation_id from the timeout error (or from any prior generation response).',
|
|
355
|
+
{
|
|
356
|
+
generation_id: { type: 'string', description: 'The generation ID to check' }
|
|
357
|
+
},
|
|
358
|
+
async ({ generation_id }) => {
|
|
359
|
+
const result = await client.get(`/v1/generate/${generation_id}/status`);
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
content: [{
|
|
363
|
+
type: 'text',
|
|
364
|
+
text: JSON.stringify(result, null, 2)
|
|
365
|
+
}]
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
module.exports = { registerGenerateTools };
|
package/src/tools/models.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC
|
|
2
|
+
* CONTRACT. Never rename, remove, or break an existing tool/arg — old cached
|
|
3
|
+
* `npx @kolbo/mcp` installs in the wild will break silently. Add new tools or
|
|
4
|
+
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
|
+
|
|
1
6
|
function registerModelTools(server, client) {
|
|
2
7
|
// ─── list_models ───────────────────────────────────────────
|
|
3
8
|
server.tool(
|