@kolbo/mcp 1.67.0 → 1.69.0-rc.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/README.md +1 -1
- package/package.json +4 -2
- package/skill/SKILL.md +1 -1
- package/src/apps/index.js +31 -31
- package/src/index.js +8 -3
- package/src/toolAnnotations.js +130 -0
- package/src/tools/generate.js +22 -8
- package/src/tools/projects.js +136 -1
- package/src/tools/review.js +1 -1
package/README.md
CHANGED
|
@@ -128,7 +128,7 @@ Without the optional skill, the config block alone already exposes every tool
|
|
|
128
128
|
| `generate_lipsync` | Source image/video + audio → lipsynced video (Sync-3 adds active-speaker selection, emotion, model mode, temperature) |
|
|
129
129
|
| `generate_creative_director` | One brief → N coordinated scenes (image or video) |
|
|
130
130
|
| `generate_music` | Text (+ optional lyrics) → song. Style, title, negative tags, length, and Suno fine-controls (style weight, weirdness, audio weight, persona / singing voice) |
|
|
131
|
-
| `generate_speech` | Text + voice → spoken audio. Full expressive/style control:
|
|
131
|
+
| `generate_speech` | Text + voice → spoken audio. Full expressive/style control: Google/Gemini named voice-direction presets (`style_instructions_preset_id`: warm/dramatic/whisper/…) or free-form `style_instructions`, preset styles + emotions (DeepDub / MiniMax / Cartesia), speed, accent/`language`, and per-provider voice settings (ElevenLabs similarity/style, DeepDub accent/variance/tempo, MiniMax pitch/volume/intensity/timbre). Status returns the same fields for reuse. |
|
|
132
132
|
| `generate_sound` | Text → sound effect. Duration, prompt influence, and per-provider controls (Stable Audio guidance, Kie loop/tempo/key, Seed-Audio voice/speed/volume/pitch + reference audio/image) |
|
|
133
133
|
| `generate_3d` | Text or reference images → 3D model (GLB/FBX/OBJ/USDZ) |
|
|
134
134
|
| `transcribe_audio` | Audio/video URL or file → text + SRT subtitles. Language, speaker diarization, audio-event tagging, and SRT formatting (words/line, lines/subtitle, caption stretch) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolbo/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.69.0-rc.1",
|
|
4
4
|
"description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
"start": "node src/index.js",
|
|
11
11
|
"smoke": "node scripts/smoke.js",
|
|
12
12
|
"check-parity": "node scripts/check-parity.js",
|
|
13
|
-
"prepublishOnly": "node scripts/smoke.js && node scripts/check-parity.js && node scripts/check-widget-fields.js && node scripts/check-widget-render.js && node scripts/check-model-catalog.js && node scripts/check-skill-tools.js && node scripts/check-install.js",
|
|
13
|
+
"prepublishOnly": "node scripts/smoke.js && node scripts/check-parity.js && node scripts/check-widget-fields.js && node scripts/check-widget-render.js && node scripts/check-model-catalog.js && node scripts/check-skill-tools.js && node scripts/check-submission-contract.js && node scripts/check-install.js",
|
|
14
14
|
"check-model-catalog": "node scripts/check-model-catalog.js",
|
|
15
15
|
"check-widget-fields": "node scripts/check-widget-fields.js",
|
|
16
16
|
"check-widget-render": "node scripts/check-widget-render.js",
|
|
17
17
|
"check-skill-tools": "node scripts/check-skill-tools.js",
|
|
18
|
+
"check-submission-contract": "node scripts/check-submission-contract.js",
|
|
19
|
+
"generate-chatgpt-submission": "node scripts/generate-chatgpt-submission.js",
|
|
18
20
|
"check-install": "node scripts/check-install.js"
|
|
19
21
|
},
|
|
20
22
|
"keywords": [
|
package/skill/SKILL.md
CHANGED
|
@@ -103,7 +103,7 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
103
103
|
| `generate_first_last_frame` | Keyframe interpolation between two frames. |
|
|
104
104
|
| `generate_lipsync` | Lipsync audio to an image or video face. |
|
|
105
105
|
| `generate_music` | Music generation (Suno + variants). |
|
|
106
|
-
| `generate_speech` | TTS. Use `list_voices` to pick a voice. |
|
|
106
|
+
| `generate_speech` | TTS. Use `list_voices` to pick a voice. Pass provider controls: Google/Gemini `style_instructions_preset_id` (warm/dramatic/whisper/excited/calm/cheerful/serious/storyteller/sad/intimate/british/commercial) or free-form `style_instructions` + `language` as Accent; DeepDub/MiniMax/Cartesia `selected_style`/`emotion`; `speaking_speed`; ElevenLabs `similarity_boost`/`style`; MiniMax `minimax_pitch`/`minimax_vol`/…. |
|
|
107
107
|
| `generate_sound` | Sound effects. |
|
|
108
108
|
| `generate_3d` | 3D models from text / single image / multi-view. Returns GLB/FBX/OBJ/USDZ. |
|
|
109
109
|
|
package/src/apps/index.js
CHANGED
|
@@ -54,44 +54,45 @@ function widgetHtml(uri) {
|
|
|
54
54
|
// img/script/style/font/media-src; connectDomains to connect-src.
|
|
55
55
|
const WIDGET_CSP = {
|
|
56
56
|
resourceDomains: [
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
'https://api.kolbo.ai', // model icons (/assets)
|
|
57
|
+
// Public production hosts owned by Kolbo.
|
|
58
|
+
'https://api.kolbo.ai',
|
|
60
59
|
'https://app.kolbo.ai',
|
|
61
|
-
'https://media.kolbo.ai',
|
|
62
|
-
'https://
|
|
63
|
-
'https://media-dev.kolbo.ai',
|
|
60
|
+
'https://media.kolbo.ai',
|
|
61
|
+
'https://cdn.kolbo.ai',
|
|
64
62
|
'https://kolboai-production.ams3.digitaloceanspaces.com',
|
|
65
63
|
'https://kolboai-production.ams3.cdn.digitaloceanspaces.com',
|
|
66
|
-
'https://
|
|
67
|
-
'https://kolboai-development.ams3.cdn.digitaloceanspaces.com',
|
|
64
|
+
'https://kolbo-general-media.fra1.digitaloceanspaces.com',
|
|
68
65
|
'https://kolbo-general-media.fra1.cdn.digitaloceanspaces.com',
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
'https://
|
|
72
|
-
'https://
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
'https://
|
|
76
|
-
|
|
77
|
-
'https
|
|
78
|
-
'https
|
|
79
|
-
'https
|
|
80
|
-
'https
|
|
81
|
-
'https
|
|
82
|
-
'https
|
|
83
|
-
'https
|
|
84
|
-
'https
|
|
85
|
-
'https
|
|
86
|
-
'https
|
|
66
|
+
|
|
67
|
+
// Fonts used by the shared widget shell.
|
|
68
|
+
'https://fonts.googleapis.com',
|
|
69
|
+
'https://fonts.gstatic.com',
|
|
70
|
+
|
|
71
|
+
// Exact preview hosts returned by the production stock integrations.
|
|
72
|
+
'https://images.pexels.com',
|
|
73
|
+
'https://videos.pexels.com',
|
|
74
|
+
'https://images.unsplash.com',
|
|
75
|
+
'https://plus.unsplash.com',
|
|
76
|
+
'https://pixabay.com',
|
|
77
|
+
'https://cdn.pixabay.com',
|
|
78
|
+
'https://coverr.co',
|
|
79
|
+
'https://cdn.coverr.co',
|
|
80
|
+
'https://freesound.org',
|
|
81
|
+
'https://cdn.freesound.org',
|
|
82
|
+
'https://sketchfab.com',
|
|
83
|
+
'https://media.sketchfab.com',
|
|
84
|
+
'https://cdn.sketchfab.com',
|
|
85
|
+
'https://assets.sketchfab.com',
|
|
86
|
+
'https://sketchfab-prod-media.s3.amazonaws.com',
|
|
87
|
+
|
|
88
|
+
// Default SYNCI catalog project. Any production override must be reviewed
|
|
89
|
+
// and added here as an exact hostname before deployment.
|
|
90
|
+
'https://gfbpxdkripkbbrcvoyeh.supabase.co',
|
|
87
91
|
],
|
|
88
92
|
// connect-src — XHR/fetch FROM widget iframes. Used by the upload widget to
|
|
89
93
|
// POST files to /mcp/upload with its short-lived ticket.
|
|
90
94
|
connectDomains: [
|
|
91
95
|
'https://api.kolbo.ai',
|
|
92
|
-
'https://api-staging.kolbo.ai',
|
|
93
|
-
'https://api-dev.kolbo.ai',
|
|
94
|
-
'https://*.kolbo.ai',
|
|
95
96
|
],
|
|
96
97
|
};
|
|
97
98
|
|
|
@@ -418,7 +419,6 @@ const TOOL_WIDGETS = {
|
|
|
418
419
|
generate_3d: UI.generation,
|
|
419
420
|
edit_image: UI.generation,
|
|
420
421
|
edit_video: UI.generation,
|
|
421
|
-
shorts_render: UI.generation,
|
|
422
422
|
// transcript viewer
|
|
423
423
|
transcribe_audio: UI.transcript,
|
|
424
424
|
// model catalog
|
|
@@ -433,7 +433,6 @@ const TOOL_WIDGETS = {
|
|
|
433
433
|
list_voices: UI.mediaGrid,
|
|
434
434
|
list_visual_dnas: UI.mediaGrid,
|
|
435
435
|
list_moodboards: UI.mediaGrid,
|
|
436
|
-
shorts_analyze: UI.mediaGrid,
|
|
437
436
|
// NOTE: list_color_palettes' handler has always called uiResult(UI.mediaGrid, ...)
|
|
438
437
|
// (see color_palettes.js) but was missing here — hosts that prepare the widget
|
|
439
438
|
// iframe from the tool DECLARATION (claude.ai reads tools/list, not the result)
|
|
@@ -463,6 +462,7 @@ function attachToolWidgetMeta(server) {
|
|
|
463
462
|
|
|
464
463
|
module.exports = {
|
|
465
464
|
UI,
|
|
465
|
+
WIDGET_CSP,
|
|
466
466
|
TOOL_WIDGETS,
|
|
467
467
|
registerApps,
|
|
468
468
|
attachToolWidgetMeta,
|
package/src/index.js
CHANGED
|
@@ -76,7 +76,8 @@ const { registerReviewTools } = require('./tools/review');
|
|
|
76
76
|
const { registerVoiceTools } = require('./tools/voices');
|
|
77
77
|
const { registerMusicLibraryTools } = require('./tools/music_library');
|
|
78
78
|
const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
79
|
-
const { registerApps, attachToolWidgetMeta } = require('./apps');
|
|
79
|
+
const { registerApps, attachToolWidgetMeta } = require('./apps');
|
|
80
|
+
const { attachToolAnnotations } = require('./toolAnnotations');
|
|
80
81
|
|
|
81
82
|
/**
|
|
82
83
|
* Build a fully-configured Kolbo MCP server (all tool groups registered)
|
|
@@ -171,8 +172,12 @@ function createServer(opts = {}) {
|
|
|
171
172
|
// transport. Without it, a remote-connector model reads "absolute local path",
|
|
172
173
|
// sees no filesystem, and tells the user Kolbo cannot accept their file —
|
|
173
174
|
// the single most-reported failure, despite the upload tools existing.
|
|
174
|
-
attachFileInputHints(server, toolOptions);
|
|
175
|
-
//
|
|
175
|
+
attachFileInputHints(server, toolOptions);
|
|
176
|
+
// OpenAI public-app review requires every exposed tool to declare the three
|
|
177
|
+
// safety hints explicitly. The exact contract also fails closed when a tool
|
|
178
|
+
// is added or removed without a classification.
|
|
179
|
+
attachToolAnnotations(server);
|
|
180
|
+
// Declaration-level `_meta['ui/resourceUri']` on every widget-carrying tool —
|
|
176
181
|
// claude.ai prepares the widget iframe from tools/list, not from the result.
|
|
177
182
|
attachToolWidgetMeta(server);
|
|
178
183
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public MCP safety contract.
|
|
5
|
+
*
|
|
6
|
+
* ChatGPT app review requires every exposed tool to declare all three hints.
|
|
7
|
+
* Keep this map exact: the submission gate rejects missing, duplicate, stale,
|
|
8
|
+
* or non-boolean entries whenever the registered tool surface changes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const READ_ONLY = [
|
|
12
|
+
'get_creative_director_status', 'get_generation_status', 'list_models',
|
|
13
|
+
'check_credits', 'get_session_usage', 'list_voices',
|
|
14
|
+
'chat_list_conversations', 'chat_get_messages',
|
|
15
|
+
'list_visual_dnas', 'get_visual_dna', 'list_visual_dna_folders',
|
|
16
|
+
'list_moodboards', 'get_moodboard',
|
|
17
|
+
'list_color_palettes', 'analyze_color_palette',
|
|
18
|
+
'list_media', 'list_media_folders', 'get_media', 'get_media_stats',
|
|
19
|
+
'list_presets', 'list_cinematic_presets',
|
|
20
|
+
'list_projects', 'list_sessions', 'list_project_context', 'get_project_profile',
|
|
21
|
+
'list_session_generations',
|
|
22
|
+
'list_agents', 'list_docs', 'get_doc',
|
|
23
|
+
'get_review_storage_usage', 'list_review_assets', 'get_review_asset',
|
|
24
|
+
'list_review_collections', 'list_review_comments', 'list_review_share_links',
|
|
25
|
+
'search_music_library', 'analyze_script_for_music', 'browse_music_library',
|
|
26
|
+
'get_music_library_facets', 'get_music_track_audio',
|
|
27
|
+
'get_music_track_related', 'get_music_track_lyrics',
|
|
28
|
+
'search_stock_media', 'get_stock_sources', 'get_stock_categories',
|
|
29
|
+
'get_stock_collections', 'get_stock_asset', 'analyze_script_for_stock',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const PRIVATE_WRITE = [
|
|
33
|
+
'media_upload_widget', 'create_upload_ticket', 'upload_media',
|
|
34
|
+
'favorite_media', 'unfavorite_media',
|
|
35
|
+
'create_media_folder', 'update_media_folder',
|
|
36
|
+
'add_media_to_folder', 'remove_media_from_folder',
|
|
37
|
+
'share_media_folder',
|
|
38
|
+
'restore_media', 'move_media', 'bulk_restore_media', 'bulk_move_media',
|
|
39
|
+
'move_folder_contents',
|
|
40
|
+
'import_elevenlabs_voice',
|
|
41
|
+
'create_visual_dna', 'create_visual_dna_folder', 'update_visual_dna_folder',
|
|
42
|
+
'move_visual_dna_to_folder',
|
|
43
|
+
'create_moodboard',
|
|
44
|
+
'create_color_palette', 'activate_color_palette', 'deactivate_color_palette',
|
|
45
|
+
'move_session', 'bulk_move_sessions', 'move_generations_to_session',
|
|
46
|
+
'split_session', 'undo_session_organization',
|
|
47
|
+
'create_project', 'update_project',
|
|
48
|
+
'archive_project', 'unarchive_project', 'add_project_context',
|
|
49
|
+
'create_agent',
|
|
50
|
+
'create_doc',
|
|
51
|
+
'create_review_asset', 'update_review_asset', 'add_review_version',
|
|
52
|
+
'set_review_status', 'create_review_collection', 'update_review_collection',
|
|
53
|
+
'create_review_comment', 'reply_review_comment',
|
|
54
|
+
'resolve_review_comment', 'unresolve_review_comment',
|
|
55
|
+
'import_stock_asset',
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const DESTRUCTIVE_WRITE = [
|
|
59
|
+
// These actions spend credits, enqueue irreversible work, or cancel it.
|
|
60
|
+
'generate_image', 'generate_image_edit', 'generate_creative_director',
|
|
61
|
+
'generate_video', 'generate_video_from_image', 'generate_music',
|
|
62
|
+
'generate_speech', 'generate_sound', 'cancel_generation',
|
|
63
|
+
'generate_elements', 'generate_first_last_frame', 'generate_lipsync',
|
|
64
|
+
'generate_video_from_video', 'transcribe_audio', 'generate_3d',
|
|
65
|
+
'edit_image', 'edit_video', 'trim_video', 'clone_voice',
|
|
66
|
+
'chat_send_message', 'generate_character_sheet',
|
|
67
|
+
'acquire_clean_music_track', 'import_music_track_to_library',
|
|
68
|
+
|
|
69
|
+
// Deletes and whole-value replacement updates are conservatively destructive.
|
|
70
|
+
'delete_voice', 'delete_visual_dna', 'delete_visual_dna_folder',
|
|
71
|
+
'update_moodboard', 'delete_moodboard',
|
|
72
|
+
'update_color_palette', 'delete_color_palette',
|
|
73
|
+
'delete_media_folder', 'delete_media', 'permanently_delete_media',
|
|
74
|
+
'bulk_delete_media', 'bulk_permanently_delete_media',
|
|
75
|
+
'unshare_media_folder',
|
|
76
|
+
'delete_project_context', 'regenerate_project_profile',
|
|
77
|
+
'update_agent', 'delete_agent', 'update_doc', 'delete_doc',
|
|
78
|
+
'delete_review_asset', 'delete_review_collection',
|
|
79
|
+
'edit_review_comment', 'delete_review_comment',
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const OPEN_WORLD_WRITE = [
|
|
83
|
+
'publish_html_artifact', 'create_review_share_link',
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const OPEN_WORLD_DESTRUCTIVE = [
|
|
87
|
+
'share_doc', 'revoke_review_share_link',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const CONTRACT_GROUPS = [
|
|
91
|
+
[READ_ONLY, { readOnlyHint: true, openWorldHint: false, destructiveHint: false }],
|
|
92
|
+
[PRIVATE_WRITE, { readOnlyHint: false, openWorldHint: false, destructiveHint: false }],
|
|
93
|
+
[DESTRUCTIVE_WRITE, { readOnlyHint: false, openWorldHint: false, destructiveHint: true }],
|
|
94
|
+
[OPEN_WORLD_WRITE, { readOnlyHint: false, openWorldHint: true, destructiveHint: false }],
|
|
95
|
+
[OPEN_WORLD_DESTRUCTIVE, { readOnlyHint: false, openWorldHint: true, destructiveHint: true }],
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
function buildToolAnnotations() {
|
|
99
|
+
const annotations = Object.create(null);
|
|
100
|
+
for (const [names, hints] of CONTRACT_GROUPS) {
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
if (annotations[name]) throw new Error(`Duplicate safety annotation contract for tool: ${name}`);
|
|
103
|
+
annotations[name] = Object.freeze({ ...hints });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Object.freeze(annotations);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const TOOL_ANNOTATIONS = buildToolAnnotations();
|
|
110
|
+
|
|
111
|
+
function attachToolAnnotations(server) {
|
|
112
|
+
const registered = server?._registeredTools || {};
|
|
113
|
+
const registeredNames = Object.keys(registered).sort();
|
|
114
|
+
const contractNames = Object.keys(TOOL_ANNOTATIONS).sort();
|
|
115
|
+
const missing = registeredNames.filter((name) => !TOOL_ANNOTATIONS[name]);
|
|
116
|
+
const stale = contractNames.filter((name) => !registered[name]);
|
|
117
|
+
if (missing.length || stale.length) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Tool annotation contract mismatch. Missing: ${missing.join(', ') || 'none'}. `
|
|
120
|
+
+ `Stale: ${stale.join(', ') || 'none'}.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
for (const name of registeredNames) registered[name].annotations = TOOL_ANNOTATIONS[name];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
TOOL_ANNOTATIONS,
|
|
128
|
+
CONTRACT_GROUPS,
|
|
129
|
+
attachToolAnnotations,
|
|
130
|
+
};
|
package/src/tools/generate.js
CHANGED
|
@@ -660,12 +660,20 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
660
660
|
text: z.string().describe('The text to convert to speech'),
|
|
661
661
|
voice: z.string().optional().describe('Voice ID or display name — MUST come from a `list_voices` result, never constructed. Google/Gemini ids in particular are not validated provider-side: an id that is not in the catalog is silently mapped to another voice (or a default one) and the audio comes back in a voice nobody asked for. Do not pattern-match a locale onto an id you saw for another language. Default: "Rachel"'),
|
|
662
662
|
model: z.string().optional().describe('Model identifier. Use list_models type="text_to_speech" to see options. Default: eleven_v3'),
|
|
663
|
-
language: z.string().optional().describe('Language code (e.g., "en-US", "he-IL", "es-ES"). Default: "en-US"'),
|
|
663
|
+
language: z.string().optional().describe('Language / accent code (e.g., "en-US", "he-IL", "es-ES"). For Google/Gemini voices this is the Accent control (does not translate the text). Default: "en-US"'),
|
|
664
664
|
// ── Expressive style / emotion (provider-specific) ──
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
665
|
+
// Google/Gemini: prefer a named preset id from the Kolbo UI picker; free-form
|
|
666
|
+
// style_instructions is the escape hatch for custom direction.
|
|
667
|
+
style_instructions_preset_id: z.enum([
|
|
668
|
+
'warm', 'dramatic', 'whisper', 'excited', 'calm', 'cheerful',
|
|
669
|
+
'serious', 'storyteller', 'sad', 'intimate', 'british', 'commercial',
|
|
670
|
+
'custom', 'none',
|
|
671
|
+
]).optional().describe('Google/Gemini voices ONLY. Named voice-direction preset from the Kolbo TTS picker (warm, dramatic, whisper, excited, calm, cheerful, serious, storyteller, sad, intimate, british, commercial). Use "custom" with style_instructions for free-form direction, or "none" for default delivery. Preferred over raw style_instructions when a preset fits — the API expands it to the English directive fal receives and stores the label for history/reuse.'),
|
|
672
|
+
style_instructions: z.string().optional().describe('Google/Gemini voices ONLY. Free-form natural-language voice direction, e.g. "whisper conspiratorially, slightly amused" or "excited sports announcer". Max 500 chars. When set without a preset_id, treated as custom. Ignored by other providers.'),
|
|
673
|
+
style_instructions_label: z.string().optional().describe('Google/Gemini voices ONLY. Friendly label stored on the generation for history cards (defaults to the preset name or the custom text). Usually omit — the API fills it.'),
|
|
674
|
+
selected_style: z.string().optional().describe('DeepDub, MiniMax & Cartesia voices. Preset expressive style/emotion. DeepDub: reading, conversational, angry, breathy, panic, amused, sad, whisper, singing, shout, scream, mumbling, excited. MiniMax/Cartesia: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper (or provider emotion names). Ignored by other providers.'),
|
|
675
|
+
emotion: z.string().optional().describe('MiniMax / Cartesia voices. Emotion: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper. Prefer this OR selected_style (both map to the same delivery control).'),
|
|
676
|
+
speaking_speed: z.number().optional().describe('Speech speed 0.5 (slow) – 2.0 (fast); Cartesia clamped 0.6–1.5. Default 1.0. Applies to ElevenLabs / OpenAI / Google / MiniMax / Cartesia / DeepDub (as tempo when tempo omitted).'),
|
|
669
677
|
// ── ElevenLabs voice settings ──
|
|
670
678
|
similarity_boost: z.number().optional().describe('ElevenLabs voice similarity, 0–1. Default 0.75. Higher hews closer to the original voice.'),
|
|
671
679
|
style: z.number().optional().describe('ElevenLabs style exaggeration, 0–1. Default 0.5. Higher = more expressive/dramatic.'),
|
|
@@ -689,7 +697,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
689
697
|
project_id: projectIdField,
|
|
690
698
|
session_id: sessionIdField
|
|
691
699
|
},
|
|
692
|
-
async ({ text, voice, model, language, style_instructions, selected_style, emotion, speaking_speed, similarity_boost, style, use_speaker_boost, variance, tempo, promptBoost, seed, accentControl, voiceTitle, minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre, project_id, session_id }) => {
|
|
700
|
+
async ({ text, voice, model, language, style_instructions_preset_id, style_instructions, style_instructions_label, selected_style, emotion, speaking_speed, similarity_boost, style, use_speaker_boost, variance, tempo, promptBoost, seed, accentControl, voiceTitle, minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre, project_id, session_id }) => {
|
|
693
701
|
model = await canonicalModelId(client, model, 'text_to_speech'); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
694
702
|
// Resolve the requested voice against the REAL catalog (cached) so the card
|
|
695
703
|
// can show its display name + portrait instead of a raw id, and so an id
|
|
@@ -703,7 +711,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
703
711
|
: null;
|
|
704
712
|
const gen = await client.post('/v1/generate/speech', {
|
|
705
713
|
text, voice, model, language,
|
|
706
|
-
|
|
714
|
+
style_instructions_preset_id, style_instructions, style_instructions_label,
|
|
715
|
+
selected_style, emotion, speaking_speed,
|
|
707
716
|
similarity_boost, style, use_speaker_boost,
|
|
708
717
|
variance, tempo, promptBoost, seed, accentControl, voiceTitle,
|
|
709
718
|
minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre,
|
|
@@ -713,7 +722,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
713
722
|
if (ui()) return uiGenerating({
|
|
714
723
|
tool: 'generate_speech', kind: 'audio', gen, client, model, prompt: text,
|
|
715
724
|
voice: voiceRecord,
|
|
716
|
-
settings: {
|
|
725
|
+
settings: {
|
|
726
|
+
voice: voice || 'Rachel',
|
|
727
|
+
style: selected_style || emotion || style_instructions_preset_id || style_instructions,
|
|
728
|
+
speaking_speed,
|
|
729
|
+
language,
|
|
730
|
+
},
|
|
717
731
|
warning: unknownVoice
|
|
718
732
|
});
|
|
719
733
|
|
package/src/tools/projects.js
CHANGED
|
@@ -64,7 +64,7 @@ function registerProjectTools(server, client, options = {}) {
|
|
|
64
64
|
// ─── move_session ──────────────────────────────────────────
|
|
65
65
|
server.tool(
|
|
66
66
|
'move_session',
|
|
67
|
-
'Move
|
|
67
|
+
'Move ONE session — and ALL of its generations and media library items — to another project. Works for any session type: generation sessions (the `session_id` returned by generate_* tools), chat conversations, transcription sessions, etc. Use this when work landed in the wrong project (e.g. the default "API Generations" bucket) and the user wants it in a named project — moving is always better than regenerating. For SEVERAL sessions use `bulk_move_sessions` instead: one call, up to 100 sessions, and it reports per-session failures. Caller needs edit/full/owner permission on BOTH the source and target projects (a shared-project member can move a teammate\'s session). Resolve the target project id with `list_projects` first.',
|
|
68
68
|
{
|
|
69
69
|
session_id: z.string().describe('The session ObjectId to move (from a generation submit response, chat_list_conversations, or an "Open in Kolbo" link).'),
|
|
70
70
|
project_id: z.string().describe('Target project ObjectId. Call `list_projects` to resolve a project name to its id.'),
|
|
@@ -85,6 +85,141 @@ function registerProjectTools(server, client, options = {}) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
);
|
|
88
|
+
|
|
89
|
+
// ─── bulk_move_sessions ────────────────────────────────────
|
|
90
|
+
server.tool(
|
|
91
|
+
'bulk_move_sessions',
|
|
92
|
+
'Move MANY sessions into one project in a single call — the tool to use when reorganizing a user\'s library ("file all my Acme work into the Acme project", "clean up the API Generations bucket"). Each session carries ALL of its generations and media with it. Prefer this over looping `move_session`: one call handles up to 100 sessions, while `move_session` is rate limited per call. Sessions of mixed types (chat + image + video) can go in the SAME call. Each session moves independently, so one that cannot move — a generation still running, a Creative Director session, or one you lack edit access to — does NOT block the rest; check `failed[]` in the result and report those to the user. Resolve session ids with `list_sessions` and the target project id with `list_projects` first.',
|
|
93
|
+
{
|
|
94
|
+
session_ids: z.array(z.string()).describe('Session ObjectIds to move (from `list_sessions`, a generation submit response, or an "Open in Kolbo" link). Up to 100 per call; types may be mixed.'),
|
|
95
|
+
project_id: z.string().describe('Target project ObjectId. Call `list_projects` to resolve a project name to its id.'),
|
|
96
|
+
type: z.string().optional().describe('Optional session type hint that speeds up the lookup when EVERY id in the batch is the same type: image, video, video_from_image, music, speech, sound, image_edit, chat, elements, first_last_frame, lipsync. Omit for mixed batches — the server probes all types.')
|
|
97
|
+
},
|
|
98
|
+
async ({ session_ids, project_id, type }) => {
|
|
99
|
+
const body = { session_ids, project_id };
|
|
100
|
+
if (type) body.type = type;
|
|
101
|
+
const result = await client.post('/v1/sessions/move', body);
|
|
102
|
+
return {
|
|
103
|
+
content: [{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: JSON.stringify({
|
|
106
|
+
project_id: result.project_id,
|
|
107
|
+
moved_sessions_count: result.moved_sessions_count,
|
|
108
|
+
moved_generations_count: result.moved_generations_count,
|
|
109
|
+
moved_media_count: result.moved_media_count,
|
|
110
|
+
skipped: result.skipped,
|
|
111
|
+
failed: result.failed,
|
|
112
|
+
operation_ids: result.operation_ids,
|
|
113
|
+
_hint: (result.failed && result.failed.length)
|
|
114
|
+
? 'Some sessions did not move — tell the user which ones and why (see failed[]). The rest are already in the new project.'
|
|
115
|
+
: 'Every session, its generations and its media now live in the new project. Pass any operation_id to `undo_session_organization` within 15 minutes to reverse one.'
|
|
116
|
+
}, null, 2)
|
|
117
|
+
}]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// ─── list_session_generations ──────────────────────────────
|
|
123
|
+
server.tool(
|
|
124
|
+
'list_session_generations',
|
|
125
|
+
'List a session\'s generations as complete GROUPS — each entry is one generation with its prompt and ALL the outputs it produced. Call this FIRST whenever the user wants to reorganize WITHIN or BETWEEN sessions ("move these three shots into their own session", "split the good takes out"), because `move_generations_to_session` and `split_session` take the ids this returns. Also the cheapest way to see what is actually inside a session before moving it. A generation is never separable from its own outputs, so you always move whole entries. Only image, image-to-video, lipsync and video-to-video sessions support this level of organization; other types return SESSION_TYPE_NOT_MOVABLE and should be moved whole with `move_session`.',
|
|
126
|
+
{
|
|
127
|
+
session_id: z.string().describe('The session ObjectId to inspect.'),
|
|
128
|
+
type: z.string().optional().describe('Optional session type hint to speed up the lookup. Omit if unsure.')
|
|
129
|
+
},
|
|
130
|
+
async ({ session_id, type }) => {
|
|
131
|
+
const path = `/v1/sessions/${encodeURIComponent(session_id)}/generations`;
|
|
132
|
+
const result = await client.get(path + (type ? `?type=${encodeURIComponent(type)}` : ''));
|
|
133
|
+
return {
|
|
134
|
+
content: [{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: JSON.stringify({
|
|
137
|
+
session: result.session,
|
|
138
|
+
generations: result.generations,
|
|
139
|
+
_hint: 'Pass the `id` values to `move_generations_to_session` (into an existing session) or `split_session` (into a new one). `in_flight: true` means it is still running and cannot be moved yet.'
|
|
140
|
+
}, null, 2)
|
|
141
|
+
}]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// ─── move_generations_to_session ───────────────────────────
|
|
147
|
+
server.tool(
|
|
148
|
+
'move_generations_to_session',
|
|
149
|
+
'Move SELECTED generations out of one session and into another EXISTING session — the way to merge scattered work ("put these shots into my Hero Sequence session", "these three belong with the earlier batch"). Only the chosen generations and THEIR OWN output media move; shared uploads and reference images stay with the source session, so nothing another generation still depends on is dragged away. The destination must be a session of the SAME kind, and may live in a different project as long as you can edit both. Get the generation ids from `list_session_generations` and the destination id from `list_sessions`. Running generations cannot be moved — wait for them to finish.',
|
|
150
|
+
{
|
|
151
|
+
session_id: z.string().describe('Source session ObjectId — the session the generations are in now.'),
|
|
152
|
+
generation_ids: z.array(z.string()).describe('Generation ids to move, from `list_session_generations`. Whole entries only.'),
|
|
153
|
+
target_session_id: z.string().describe('Destination session ObjectId. Must be the same session kind as the source.'),
|
|
154
|
+
type: z.string().optional().describe('Optional session type hint to speed up the lookup. Omit if unsure.')
|
|
155
|
+
},
|
|
156
|
+
async ({ session_id, generation_ids, target_session_id, type }) => {
|
|
157
|
+
const body = { generation_ids, target_session_id };
|
|
158
|
+
if (type) body.type = type;
|
|
159
|
+
const result = await client.post(
|
|
160
|
+
`/v1/sessions/${encodeURIComponent(session_id)}/generations/move`, body
|
|
161
|
+
);
|
|
162
|
+
return {
|
|
163
|
+
content: [{
|
|
164
|
+
type: 'text',
|
|
165
|
+
text: JSON.stringify({
|
|
166
|
+
moved_generations_count: result.moved_generations_count,
|
|
167
|
+
moved_media_count: result.moved_media_count,
|
|
168
|
+
target_session_id: result.target_session_id,
|
|
169
|
+
project_id: result.project_id,
|
|
170
|
+
operation_id: result.operation_id,
|
|
171
|
+
_hint: 'Reversible for 15 minutes — pass operation_id to `undo_session_organization`.'
|
|
172
|
+
}, null, 2)
|
|
173
|
+
}]
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// ─── split_session ─────────────────────────────────────────
|
|
179
|
+
server.tool(
|
|
180
|
+
'split_session',
|
|
181
|
+
'Carve selected generations out of a session into a BRAND NEW named session, atomically. Use when one session has grown into several distinct pieces of work ("separate the product shots from the lifestyle ones", "give the approved takes their own session"). Creates the new session and moves the chosen generations plus their output media into it in one transaction — nothing half-lands. The new session goes in the same project unless you pass `project_id`. Get the generation ids from `list_session_generations` first.',
|
|
182
|
+
{
|
|
183
|
+
session_id: z.string().describe('Source session ObjectId to split.'),
|
|
184
|
+
generation_ids: z.array(z.string()).describe('Generation ids to move into the new session, from `list_session_generations`.'),
|
|
185
|
+
name: z.string().describe('Name for the new session — make it descriptive, the user sees it in the sidebar.'),
|
|
186
|
+
project_id: z.string().optional().describe('Put the new session in a DIFFERENT project. Omit to keep it in the source session\'s project. You need edit access on both.'),
|
|
187
|
+
type: z.string().optional().describe('Optional session type hint to speed up the lookup. Omit if unsure.')
|
|
188
|
+
},
|
|
189
|
+
async ({ session_id, generation_ids, name, project_id, type }) => {
|
|
190
|
+
const body = { generation_ids, name };
|
|
191
|
+
if (project_id) body.project_id = project_id;
|
|
192
|
+
if (type) body.type = type;
|
|
193
|
+
const result = await client.post(`/v1/sessions/${encodeURIComponent(session_id)}/split`, body);
|
|
194
|
+
return {
|
|
195
|
+
content: [{
|
|
196
|
+
type: 'text',
|
|
197
|
+
text: JSON.stringify({
|
|
198
|
+
session: result.session,
|
|
199
|
+
moved_generations_count: result.moved_generations_count,
|
|
200
|
+
moved_media_count: result.moved_media_count,
|
|
201
|
+
operation_id: result.operation_id,
|
|
202
|
+
_hint: 'Reversible for 15 minutes — `undo_session_organization` removes the new session and returns its generations.'
|
|
203
|
+
}, null, 2)
|
|
204
|
+
}]
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// ─── undo_session_organization ─────────────────────────────
|
|
210
|
+
server.tool(
|
|
211
|
+
'undo_session_organization',
|
|
212
|
+
'Reverse a session move, generation move, or split within 15 minutes of making it. Use immediately when the user says the reorganization was wrong ("no, put that back", "undo that move"). Takes the `operation_id` returned by `move_session`, `bulk_move_sessions`, `move_generations_to_session` or `split_session` — a batch move returns one id PER session, so call this once per id you want to reverse. Refuses safely if the work has moved again since, rather than yanking records out of wherever they now live.',
|
|
213
|
+
{
|
|
214
|
+
operation_id: z.string().describe('The operation_id from the move/split result you want to reverse.')
|
|
215
|
+
},
|
|
216
|
+
async ({ operation_id }) => {
|
|
217
|
+
const result = await client.post(
|
|
218
|
+
`/v1/sessions/organize/undo/${encodeURIComponent(operation_id)}`, {}
|
|
219
|
+
);
|
|
220
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
221
|
+
}
|
|
222
|
+
);
|
|
88
223
|
// ─── create_project ────────────────────────────────────────
|
|
89
224
|
server.tool(
|
|
90
225
|
'create_project',
|
package/src/tools/review.js
CHANGED
|
@@ -297,7 +297,7 @@ function registerReviewTools(server, client) {
|
|
|
297
297
|
canSwitchVersions: z.boolean().optional(),
|
|
298
298
|
canSetStatus: z.boolean().optional(),
|
|
299
299
|
}).optional(),
|
|
300
|
-
password: z.string().optional(),
|
|
300
|
+
password: z.string().optional().describe('Optional NEW password to set on this guest review link. This is not the user\'s Kolbo password and must never contain an account credential.'),
|
|
301
301
|
allowed_emails: z.array(z.string()).optional(),
|
|
302
302
|
expires_at: z.string().optional().describe('ISO8601 expiry datetime.'),
|
|
303
303
|
},
|