@kolbo/mcp 1.34.1 → 1.35.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 +6 -0
- package/package.json +1 -1
- package/skill/GENERATED.md +1 -1
- package/skill/SKILL.md +6 -1
- package/skill/VERSION +1 -1
- package/src/tools/generate.js +40 -2
- package/src/tools/moodboards.js +51 -0
- package/src/tools/projects.js +139 -0
- package/src/tools/voices.js +53 -0
package/README.md
CHANGED
|
@@ -234,6 +234,12 @@ Every generation tool also accepts an optional `project_id` arg that routes the
|
|
|
234
234
|
| `move_session` | Move a session (generation, chat, transcription…) and ALL its media to another project |
|
|
235
235
|
| `create_doc` / `list_docs` / `get_doc` / `update_doc` / `share_doc` / `delete_doc` | AI Docs (Magic Pad): author project-scoped HTML documents, edit them, get public share links |
|
|
236
236
|
| `list_visual_dna_folders` / `create_visual_dna_folder` / `update_visual_dna_folder` / `delete_visual_dna_folder` / `move_visual_dna_to_folder` | Organize Visual DNA characters into user folders (create/rename/recolor/delete + move DNAs in/out) |
|
|
237
|
+
| `create_project` / `update_project` / `archive_project` / `unarchive_project` | Project lifecycle (create/rename/describe/archive; deletion stays in-app) |
|
|
238
|
+
| `list_sessions` | Enumerate sessions across all types, filterable by project and type |
|
|
239
|
+
| `add_project_context` / `list_project_context` / `delete_project_context` / `get_project_profile` / `regenerate_project_profile` | Project knowledge base (RAG): feed scripts/URLs/notes, read the synthesized living brief |
|
|
240
|
+
| `create_moodboard` / `update_moodboard` / `delete_moodboard` | Build/edit moodboards from image URLs (AI style analysis → master prompt) |
|
|
241
|
+
| `clone_voice` / `import_elevenlabs_voice` / `delete_voice` | Custom voices: clone from an audio sample, import by ElevenLabs ID, delete |
|
|
242
|
+
| `trim_video` | Frame-accurate server-side trim of a Kolbo-hosted video (async job, tool waits) |
|
|
237
243
|
| `check_credits` | Check credit balance |
|
|
238
244
|
| `get_generation_status` | Check one or many generations (`generation_ids`); `wait=true` blocks server-side until done — replaces client polling loops |
|
|
239
245
|
|
package/package.json
CHANGED
package/skill/GENERATED.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AUTO-GENERATED — do not edit
|
|
2
2
|
|
|
3
3
|
This skill/ tree is mirrored from kolbo-code (the single source of truth)
|
|
4
|
-
by .github/workflows/sync-skill-to-plugin.yml — synced from kolbo-code@
|
|
4
|
+
by .github/workflows/sync-skill-to-plugin.yml — synced from kolbo-code@d3a54fb.
|
|
5
5
|
|
|
6
6
|
It is the skill that 'npx @kolbo/mcp install' deploys into the user's agent.
|
|
7
7
|
To change it, edit packages/opencode/skills/kolbo/ in kolbo-code and push;
|
package/skill/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 0.
|
|
2
|
+
version: 0.7.0
|
|
3
3
|
name: kolbo
|
|
4
4
|
description: |
|
|
5
5
|
Generate, edit, or analyze creative media via the Kolbo AI MCP server:
|
|
@@ -117,6 +117,11 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
117
117
|
| `list_moodboards` / `get_moodboard` / `list_presets` | Style overlays |
|
|
118
118
|
| `search_stock_media` / `get_stock_sources` / `get_stock_categories` / `get_stock_collections` / `get_stock_asset` / `analyze_script_for_stock` / `import_stock_asset` | Stock library (free, no credits) — EXISTING photos / videos / 3D / SFX / music. For stock **music** use `search_stock_media` with `mediaType: "music"` (semantic vibe query, e.g. "uplifting corporate background") → `get_stock_asset` for downloads. The older `*_music_library` tools are deprecated adapters over this — prefer the stock tools. |
|
|
119
119
|
| `list_projects` / `move_session` | Projects: resolve a project NAME → the `project_id` you pass on generation/upload/doc calls; `move_session` relocates a whole session + its media when work landed in the wrong project. NOT the same as `app_builder_list_projects`. See "Projects — Where Work Lands" below. |
|
|
120
|
+
| `create_project` / `update_project` / `archive_project` / `unarchive_project` / `list_sessions` | Project lifecycle + session inventory (deletion stays in-app). Create a project when the user starts new work, then pass its id on EVERY call. |
|
|
121
|
+
| `add_project_context` / `list_project_context` / `delete_project_context` / `get_project_profile` / `regenerate_project_profile` | Project knowledge base (RAG): feed scripts/URLs/notes; `get_project_profile` = the living brief — read it to ground work in the project |
|
|
122
|
+
| `create_moodboard` / `update_moodboard` / `delete_moodboard` | Moodboards from image URLs → AI master style prompt → pass `moodboard_id` to generation tools |
|
|
123
|
+
| `clone_voice` / `import_elevenlabs_voice` / `delete_voice` | Custom voices (clone CHARGES CREDITS — confirm first; new voices show in `list_voices`) |
|
|
124
|
+
| `trim_video` | Frame-accurate trim of a Kolbo-hosted video (tool waits and returns the URL). `edit_video` also gained `remove_background`. |
|
|
120
125
|
| `create_doc` / `list_docs` / `get_doc` / `update_doc` / `share_doc` / `delete_doc` | AI Docs (Magic Pad): YOU author full HTML documents (plans, briefs, scripts, research) saved into the user's project, editable in the Kolbo app. `share_doc` returns a public link. `update_doc` content replaces the WHOLE doc — `get_doc` first. |
|
|
121
126
|
| `chat_send_message` / `chat_list_conversations` / `chat_get_messages` | Kolbo chat with optional `media_urls` (up to 10 per call) |
|
|
122
127
|
| `app_builder_*` (9 tools) | Full React app generation — see `workflows/app-builder.md` |
|
package/skill/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.7.0
|
package/src/tools/generate.js
CHANGED
|
@@ -1035,8 +1035,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1035
1035
|
'Apply a targeted AI edit to an existing video. Operations: upscale (4K resolution boost), reframe (change aspect ratio), generate_audio (add AI-generated sound/music from a prompt), remove_watermark, face_swap (replace faces using a reference image URL), extend (lengthen at start or end), magic_edit (restyle/transform with a prompt), lipsync (sync an audio track to a face in the video). Returns the edited video URL when complete.',
|
|
1036
1036
|
{
|
|
1037
1037
|
video_url: z.string().describe('URL of the source video to edit'),
|
|
1038
|
-
operation: z.enum(['upscale', 'reframe', 'generate_audio', 'remove_watermark', 'face_swap', 'extend', 'magic_edit', 'lipsync'])
|
|
1039
|
-
.describe('Edit operation: "upscale", "reframe" (requires aspect_ratio), "generate_audio" (requires prompt), "remove_watermark", "face_swap" (requires image_url), "extend" (requires duration), "magic_edit" (requires prompt), "lipsync" (requires audio_url)'),
|
|
1038
|
+
operation: z.enum(['upscale', 'reframe', 'generate_audio', 'remove_watermark', 'face_swap', 'extend', 'magic_edit', 'lipsync', 'remove_background'])
|
|
1039
|
+
.describe('Edit operation: "upscale", "reframe" (requires aspect_ratio), "generate_audio" (requires prompt), "remove_watermark", "face_swap" (requires image_url), "extend" (requires duration), "magic_edit" (requires prompt), "lipsync" (requires audio_url), "remove_background" (removes/greenscreens the video background)'),
|
|
1040
1040
|
model: z.string().optional().describe('Model identifier override. Omit to use the default model for the operation.'),
|
|
1041
1041
|
aspect_ratio: z.string().optional().describe('Target aspect ratio (e.g., "16:9", "9:16"). Required for operation="reframe".'),
|
|
1042
1042
|
scale: z.number().optional().describe('Upscale factor. Only used when operation="upscale".'),
|
|
@@ -1088,6 +1088,44 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1088
1088
|
};
|
|
1089
1089
|
}
|
|
1090
1090
|
);
|
|
1091
|
+
|
|
1092
|
+
// ─── trim_video ────────────────────────────────────────────
|
|
1093
|
+
server.tool(
|
|
1094
|
+
'trim_video',
|
|
1095
|
+
'Cut a section out of a video by start/end time (frame-accurate server-side trim, no credits for AI generation — pure processing). Source must be a Kolbo-hosted video URL (generated output or upload_media result). The tool waits for the job to finish (usually seconds) and returns the trimmed video URL.',
|
|
1096
|
+
{
|
|
1097
|
+
video_url: z.string().describe('Kolbo-hosted URL of the source video.'),
|
|
1098
|
+
start_time: z.number().describe('Trim start, in seconds (>= 0).'),
|
|
1099
|
+
end_time: z.number().describe('Trim end, in seconds (> start_time, max 3600).'),
|
|
1100
|
+
project_id: z.string().optional().describe('Project to file the trimmed video into (from list_projects).')
|
|
1101
|
+
},
|
|
1102
|
+
async ({ video_url, start_time, end_time, project_id }) => {
|
|
1103
|
+
const body = { video_url, start_time, end_time };
|
|
1104
|
+
if (project_id) body.project_id = project_id;
|
|
1105
|
+
const submit = await client.post('/v1/video/trim', body);
|
|
1106
|
+
const jobId = submit.jobId || submit.generationId;
|
|
1107
|
+
if (!jobId) return { content: [{ type: 'text', text: JSON.stringify(submit, null, 2) }] };
|
|
1108
|
+
// Poll the dedicated trim progress endpoint until done (~seconds).
|
|
1109
|
+
const deadline = Date.now() + 180000;
|
|
1110
|
+
let last = submit;
|
|
1111
|
+
while (Date.now() < deadline) {
|
|
1112
|
+
await new Promise(r => setTimeout(r, 4000));
|
|
1113
|
+
last = await client.get(`/v1/video/trim/${encodeURIComponent(jobId)}`);
|
|
1114
|
+
if (last.status === 'completed' || last.url || last.videoUrl) break;
|
|
1115
|
+
if (last.status === 'failed') break;
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
content: [{
|
|
1119
|
+
type: 'text',
|
|
1120
|
+
text: JSON.stringify({
|
|
1121
|
+
job_id: jobId,
|
|
1122
|
+
status: last.status || 'processing',
|
|
1123
|
+
video_url: last.url || last.videoUrl || null
|
|
1124
|
+
}, null, 2)
|
|
1125
|
+
}]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
);
|
|
1091
1129
|
}
|
|
1092
1130
|
|
|
1093
1131
|
module.exports = { registerGenerateTools };
|
package/src/tools/moodboards.js
CHANGED
|
@@ -63,6 +63,57 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
);
|
|
66
|
+
|
|
67
|
+
// ─── create_moodboard ──────────────────────────────────────
|
|
68
|
+
server.tool(
|
|
69
|
+
'create_moodboard',
|
|
70
|
+
'Create a moodboard from 1–15 image URLs. The server analyzes the images and synthesizes a reusable master style prompt — then pass the returned moodboard id as `moodboard_id` on generation tools to apply the style. Use Kolbo URLs (generated images or `upload_media` output) or any public image URL. Typical flow: generate/upload reference images → create_moodboard → generate with moodboard_id.',
|
|
71
|
+
{
|
|
72
|
+
name: z.string().describe('Moodboard name (1–100 chars).'),
|
|
73
|
+
image_urls: z.array(z.string()).min(1).max(15).describe('1–15 public image URLs. For local files, call upload_media first and use the returned URLs.'),
|
|
74
|
+
style_guide: z.string().optional().describe('Optional style notes (max 500 chars) that steer the analysis, e.g. "focus on the color grading, not the subjects".')
|
|
75
|
+
},
|
|
76
|
+
async ({ name, image_urls, style_guide }) => {
|
|
77
|
+
const body = {
|
|
78
|
+
name,
|
|
79
|
+
images: image_urls.map(u => ({ type: 'url', url: u })),
|
|
80
|
+
...(style_guide ? { style_guide } : {})
|
|
81
|
+
};
|
|
82
|
+
const result = await client.post('/v1/moodboards', body);
|
|
83
|
+
return { content: [{ type: 'text', text: JSON.stringify({ moodboard: result.moodboard, _hint: 'Pass this id as moodboard_id on generate_image / generate_creative_director to apply the style.' }, null, 2) }] };
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// ─── update_moodboard ──────────────────────────────────────
|
|
88
|
+
server.tool(
|
|
89
|
+
'update_moodboard',
|
|
90
|
+
'Update a moodboard\'s name, style guide, and/or images. Providing `image_urls` REPLACES the whole image set and re-analyzes the style (master prompt regenerates). Owner only.',
|
|
91
|
+
{
|
|
92
|
+
moodboard_id: z.string().describe('Moodboard id (from list_moodboards).'),
|
|
93
|
+
name: z.string().optional().describe('New name.'),
|
|
94
|
+
style_guide: z.string().optional().describe('New style notes (empty string clears them).'),
|
|
95
|
+
image_urls: z.array(z.string()).min(1).max(15).optional().describe('Full replacement image set (1–15 URLs). Omit to keep current images.')
|
|
96
|
+
},
|
|
97
|
+
async ({ moodboard_id, name, style_guide, image_urls }) => {
|
|
98
|
+
const body = {};
|
|
99
|
+
if (name !== undefined) body.name = name;
|
|
100
|
+
if (style_guide !== undefined) body.style_guide = style_guide;
|
|
101
|
+
if (image_urls) body.images = image_urls.map(u => ({ type: 'url', url: u }));
|
|
102
|
+
const result = await client.put(`/v1/moodboards/${encodeURIComponent(moodboard_id)}`, body);
|
|
103
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.moodboard, null, 2) }] };
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// ─── delete_moodboard ──────────────────────────────────────
|
|
108
|
+
server.tool(
|
|
109
|
+
'delete_moodboard',
|
|
110
|
+
'Permanently delete a moodboard (owner only; system presets cannot be deleted). The underlying image files stay in storage — only the board is removed. Confirm with the user before deleting boards they did not just create.',
|
|
111
|
+
{ moodboard_id: z.string().describe('Moodboard id to delete.') },
|
|
112
|
+
async ({ moodboard_id }) => {
|
|
113
|
+
const result = await client.delete(`/v1/moodboards/${encodeURIComponent(moodboard_id)}`);
|
|
114
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
115
|
+
}
|
|
116
|
+
);
|
|
66
117
|
}
|
|
67
118
|
|
|
68
119
|
module.exports = { registerMoodboardTools };
|
package/src/tools/projects.js
CHANGED
|
@@ -56,6 +56,145 @@ function registerProjectTools(server, client) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
);
|
|
59
|
+
// ─── create_project ────────────────────────────────────────
|
|
60
|
+
server.tool(
|
|
61
|
+
'create_project',
|
|
62
|
+
'Create a new Kolbo project. Use when the user starts a new body of work ("new project for film X", "set up a workspace for the campaign"). After creating, pass the returned id as `project_id` on EVERY subsequent generation/upload/doc call for that work. Plan limits apply (server rejects when the plan\'s project cap is reached).',
|
|
63
|
+
{
|
|
64
|
+
name: z.string().describe('Project name.'),
|
|
65
|
+
description: z.string().optional().describe('Optional description (max 10k chars, markdown OK). Great place for the brief/logline — it also feeds the project\'s AI profile.')
|
|
66
|
+
},
|
|
67
|
+
async ({ name, description }) => {
|
|
68
|
+
const body = { name };
|
|
69
|
+
if (description) body.description = description;
|
|
70
|
+
const result = await client.post('/v1/projects', body);
|
|
71
|
+
return { content: [{ type: 'text', text: JSON.stringify({ project: result.project, _hint: 'Pass this id as project_id on every subsequent call for this work.' }, null, 2) }] };
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// ─── update_project ────────────────────────────────────────
|
|
76
|
+
server.tool(
|
|
77
|
+
'update_project',
|
|
78
|
+
'Rename a project and/or update its description. Changing the description also refreshes the project\'s AI profile in the background.',
|
|
79
|
+
{
|
|
80
|
+
project_id: z.string().describe('Project ObjectId (from list_projects).'),
|
|
81
|
+
name: z.string().optional().describe('New name.'),
|
|
82
|
+
description: z.string().optional().describe('New description (replaces the old one).')
|
|
83
|
+
},
|
|
84
|
+
async ({ project_id, name, description }) => {
|
|
85
|
+
const body = {};
|
|
86
|
+
if (name !== undefined) body.name = name;
|
|
87
|
+
if (description !== undefined) body.description = description;
|
|
88
|
+
const result = await client.put(`/v1/projects/${encodeURIComponent(project_id)}`, body);
|
|
89
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.project, null, 2) }] };
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// ─── archive_project / unarchive_project ───────────────────
|
|
94
|
+
server.tool(
|
|
95
|
+
'archive_project',
|
|
96
|
+
'Archive a project — hides it from the default project list without deleting anything. Fully reversible with `unarchive_project`. (Permanent project DELETION is intentionally not available via the API — it cascades to all content and stays an in-app action.)',
|
|
97
|
+
{ project_id: z.string().describe('Project ObjectId to archive.') },
|
|
98
|
+
async ({ project_id }) => {
|
|
99
|
+
const result = await client.put(`/v1/projects/${encodeURIComponent(project_id)}/archive`, {});
|
|
100
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.project, null, 2) }] };
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
server.tool(
|
|
104
|
+
'unarchive_project',
|
|
105
|
+
'Restore an archived project back to the active list.',
|
|
106
|
+
{ project_id: z.string().describe('Project ObjectId to unarchive.') },
|
|
107
|
+
async ({ project_id }) => {
|
|
108
|
+
const result = await client.put(`/v1/projects/${encodeURIComponent(project_id)}/unarchive`, {});
|
|
109
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.project, null, 2) }] };
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// ─── list_sessions ─────────────────────────────────────────
|
|
114
|
+
server.tool(
|
|
115
|
+
'list_sessions',
|
|
116
|
+
'List the user\'s sessions across ALL generation types (image, video, music, chat, transcription…), newest-activity first. Use to answer "what\'s in this project?", to find a session_id for `move_session`, or to locate past work. Filter by `project_id` and/or `type`.',
|
|
117
|
+
{
|
|
118
|
+
project_id: z.string().optional().describe('Restrict to one project (ObjectId from list_projects).'),
|
|
119
|
+
type: z.string().optional().describe('Restrict to one session type: image, video, video_from_image, music, speech, sound, image_edit, creative_director, chat, elements, first_last_frame, lipsync, video_from_video, transcription, global_image_edit, global_video_edit, shorts.'),
|
|
120
|
+
page: z.number().optional().describe('Page number, 1-indexed. Default: 1'),
|
|
121
|
+
limit: z.number().optional().describe('Results per page, max 50. Default: 20')
|
|
122
|
+
},
|
|
123
|
+
async ({ project_id, type, page, limit }) => {
|
|
124
|
+
const params = new URLSearchParams();
|
|
125
|
+
if (project_id) params.set('project_id', project_id);
|
|
126
|
+
if (type) params.set('type', type);
|
|
127
|
+
if (page) params.set('page', String(page));
|
|
128
|
+
if (limit) params.set('limit', String(limit));
|
|
129
|
+
const qs = params.toString();
|
|
130
|
+
const result = await client.get(`/v1/sessions${qs ? '?' + qs : ''}`);
|
|
131
|
+
return { content: [{ type: 'text', text: JSON.stringify({ sessions: result.sessions || [], pagination: result.pagination || null }, null, 2) }] };
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// ─── Project context / knowledge base (NotebookLM-style) ───
|
|
136
|
+
server.tool(
|
|
137
|
+
'add_project_context',
|
|
138
|
+
'Feed knowledge into a project\'s AI knowledge base (RAG): a website URL or pasted text (script, brief, research, brand facts). The server analyzes it in the background (source returns status "analyzing" and settles on its own) and synthesizes everything into the project\'s living profile. Use when the user says "add this to the project", "here\'s the script", "the project should know about X". Provide exactly ONE of url / text.',
|
|
139
|
+
{
|
|
140
|
+
project_id: z.string().describe('Project ObjectId (from list_projects).'),
|
|
141
|
+
url: z.string().optional().describe('Website URL to fetch and analyze as a source.'),
|
|
142
|
+
text: z.string().optional().describe('Raw text to store as a source (script, notes, research). Kept verbatim for RAG; an AI summary is generated for display.'),
|
|
143
|
+
title: z.string().optional().describe('Optional title for a text source.')
|
|
144
|
+
},
|
|
145
|
+
async ({ project_id, url, text, title }) => {
|
|
146
|
+
if (!url && !text) throw new Error('Provide url or text');
|
|
147
|
+
const path = url
|
|
148
|
+
? `/v1/projects/${encodeURIComponent(project_id)}/context/url`
|
|
149
|
+
: `/v1/projects/${encodeURIComponent(project_id)}/context/text`;
|
|
150
|
+
const body = url ? { url } : { text, ...(title ? { title } : {}) };
|
|
151
|
+
const result = await client.post(path, body);
|
|
152
|
+
return { content: [{ type: 'text', text: JSON.stringify({ source: result.source, _hint: 'Analysis runs in the background — no need to poll; the project profile updates on its own.' }, null, 2) }] };
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
server.tool(
|
|
157
|
+
'list_project_context',
|
|
158
|
+
'List a project\'s knowledge-base sources (URLs, texts, files) with their AI summaries and analysis status.',
|
|
159
|
+
{ project_id: z.string().describe('Project ObjectId.') },
|
|
160
|
+
async ({ project_id }) => {
|
|
161
|
+
const result = await client.get(`/v1/projects/${encodeURIComponent(project_id)}/context`);
|
|
162
|
+
return { content: [{ type: 'text', text: JSON.stringify({ sources: result.sources || [], count: result.count || 0 }, null, 2) }] };
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
server.tool(
|
|
167
|
+
'delete_project_context',
|
|
168
|
+
'Remove one source from a project\'s knowledge base by its file_key (from list_project_context).',
|
|
169
|
+
{
|
|
170
|
+
project_id: z.string().describe('Project ObjectId.'),
|
|
171
|
+
file_key: z.string().describe('The source\'s file_key (URL-encode is handled for you).')
|
|
172
|
+
},
|
|
173
|
+
async ({ project_id, file_key }) => {
|
|
174
|
+
const result = await client.delete(`/v1/projects/${encodeURIComponent(project_id)}/context/${encodeURIComponent(file_key)}`);
|
|
175
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
server.tool(
|
|
180
|
+
'get_project_profile',
|
|
181
|
+
'Read a project\'s synthesized AI profile — the living markdown brief the platform maintains from the project\'s description, context sources, and activity. Use it to ground your work in what the project is about before generating.',
|
|
182
|
+
{ project_id: z.string().describe('Project ObjectId.') },
|
|
183
|
+
async ({ project_id }) => {
|
|
184
|
+
const result = await client.get(`/v1/projects/${encodeURIComponent(project_id)}/profile`);
|
|
185
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.profile, null, 2) }] };
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
server.tool(
|
|
190
|
+
'regenerate_project_profile',
|
|
191
|
+
'Force-regenerate a project\'s AI profile from its current context sources (also clears any manual-edit lock). Use after adding several new sources when the user wants the brief refreshed now.',
|
|
192
|
+
{ project_id: z.string().describe('Project ObjectId.') },
|
|
193
|
+
async ({ project_id }) => {
|
|
194
|
+
const result = await client.post(`/v1/projects/${encodeURIComponent(project_id)}/profile/regenerate`, {});
|
|
195
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.profile || result, null, 2) }] };
|
|
196
|
+
}
|
|
197
|
+
);
|
|
59
198
|
}
|
|
60
199
|
|
|
61
200
|
module.exports = { registerProjectTools };
|
package/src/tools/voices.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* CONTRACT. Never rename, remove, or break an existing tool/arg. Full rules: ../index.js top-of-file. */
|
|
3
3
|
|
|
4
4
|
const { z } = require('zod');
|
|
5
|
+
const FormData = require('form-data');
|
|
6
|
+
const { resolveToBuffer } = require('./_shared');
|
|
5
7
|
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
6
8
|
|
|
7
9
|
function registerVoiceTools(server, client, options = {}) {
|
|
@@ -60,6 +62,57 @@ function registerVoiceTools(server, client, options = {}) {
|
|
|
60
62
|
return { content: [{ type: 'text', text }] };
|
|
61
63
|
}
|
|
62
64
|
);
|
|
65
|
+
|
|
66
|
+
// ─── clone_voice ───────────────────────────────────────────
|
|
67
|
+
server.tool(
|
|
68
|
+
'clone_voice',
|
|
69
|
+
'Clone a custom TTS voice from an audio sample (the user\'s voice or any voice they own the rights to). Costs credits (provider-dependent) — confirm with the user before firing. Providers: "elevenlabs" (recommended, 2s–180s sample) or "deepdub" (2s–300s). After cloning, the voice appears in `list_voices` and can be used with generate_speech. Free plan caps at 3 custom voices.',
|
|
70
|
+
{
|
|
71
|
+
audio: z.string().describe('URL or absolute local path of the voice sample audio (any common format; converted server-side).'),
|
|
72
|
+
voice_name: z.string().describe('Name for the cloned voice.'),
|
|
73
|
+
provider: z.enum(['elevenlabs', 'deepdub']).optional().describe('Cloning provider. Default: elevenlabs (recommended).'),
|
|
74
|
+
language: z.string().optional().describe('Optional language hint (auto-detected when omitted).'),
|
|
75
|
+
project_id: z.string().optional().describe('Project to associate the voice with (from list_projects). Omit for the default project.')
|
|
76
|
+
},
|
|
77
|
+
async ({ audio, voice_name, provider, language, project_id }) => {
|
|
78
|
+
const resolved = await resolveToBuffer(audio, 'audio');
|
|
79
|
+
const form = new FormData();
|
|
80
|
+
form.append('audioFile', resolved.buffer, { filename: resolved.filename, contentType: resolved.contentType });
|
|
81
|
+
form.append('voiceName', voice_name);
|
|
82
|
+
form.append('provider', provider || 'elevenlabs');
|
|
83
|
+
if (language) form.append('language', language);
|
|
84
|
+
if (project_id) form.append('project_id', project_id);
|
|
85
|
+
const result = await client.postMultipart('/v1/voices/clone', form);
|
|
86
|
+
return { content: [{ type: 'text', text: JSON.stringify({ voice: result.voice || result, _hint: 'Use this voice with generate_speech (find it via list_voices).' }, null, 2) }] };
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// ─── import_elevenlabs_voice ───────────────────────────────
|
|
91
|
+
server.tool(
|
|
92
|
+
'import_elevenlabs_voice',
|
|
93
|
+
'Import a voice from the ElevenLabs voice library into the user\'s Kolbo voices by its ElevenLabs voice ID. Use when the user already has/knows a specific ElevenLabs voice they want available for generate_speech.',
|
|
94
|
+
{
|
|
95
|
+
elevenlabs_voice_id: z.string().describe('The ElevenLabs voice ID to import.'),
|
|
96
|
+
project_id: z.string().optional().describe('Project to associate the voice with. Omit for the default project.')
|
|
97
|
+
},
|
|
98
|
+
async ({ elevenlabs_voice_id, project_id }) => {
|
|
99
|
+
const body = { elevenLabsVoiceId: elevenlabs_voice_id };
|
|
100
|
+
if (project_id) body.project_id = project_id;
|
|
101
|
+
const result = await client.post('/v1/voices/import-elevenlabs', body);
|
|
102
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.voice || result, null, 2) }] };
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// ─── delete_voice ──────────────────────────────────────────
|
|
107
|
+
server.tool(
|
|
108
|
+
'delete_voice',
|
|
109
|
+
'Delete one of the user\'s custom cloned voices (owner only, soft delete). Preset/platform voices cannot be deleted. Confirm with the user first.',
|
|
110
|
+
{ voice_id: z.string().describe('The custom voice id to delete (from list_voices — custom voices only).') },
|
|
111
|
+
async ({ voice_id }) => {
|
|
112
|
+
const result = await client.delete(`/v1/voices/${encodeURIComponent(voice_id)}`);
|
|
113
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
114
|
+
}
|
|
115
|
+
);
|
|
63
116
|
}
|
|
64
117
|
|
|
65
118
|
module.exports = { registerVoiceTools };
|