@kolbo/mcp 1.5.6 → 1.6.5
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 +2 -0
- package/package.json +1 -1
- package/src/index.js +2 -0
- package/src/tools/app_builder.js +254 -0
- package/src/tools/generate.js +152 -36
package/README.md
CHANGED
|
@@ -65,6 +65,8 @@ Just ask Claude naturally:
|
|
|
65
65
|
|
|
66
66
|
Every image/video/creative-director tool accepts `visual_dna_ids` and `moodboard_id` for character/style consistency across outputs — you can compose `create_visual_dna` → `generate_image` (with the DNA applied server-side) in a single agent turn. `generate_creative_director` also accepts `moodboard_ids` plural for blending.
|
|
67
67
|
|
|
68
|
+
Every generation tool also accepts an optional `resolution` arg. Images use `"1K"` (~1024px) / `"2K"` (Full HD) / `"3K"` (QHD) / `"4K"` (UHD); videos use vertical-pixel tiers like `"720p"` / `"1080p"` / `"1440p"` / `"2160p"`. Values are model-dependent — call `list_models` and read the chosen model's `supported_resolutions` and `resolutionMultipliers`. Omit to use the model default.
|
|
69
|
+
|
|
68
70
|
**Chat & Vision**
|
|
69
71
|
| Tool | Description |
|
|
70
72
|
|------|-------------|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -66,6 +66,7 @@ const { registerVisualDnaTools } = require('./tools/visual_dna');
|
|
|
66
66
|
const { registerMoodboardTools } = require('./tools/moodboards');
|
|
67
67
|
const { registerMediaTools } = require('./tools/media');
|
|
68
68
|
const { registerPresetTools } = require('./tools/presets');
|
|
69
|
+
const { registerAppBuilderTools } = require('./tools/app_builder');
|
|
69
70
|
|
|
70
71
|
async function main() {
|
|
71
72
|
const client = new KolboClient();
|
|
@@ -83,6 +84,7 @@ async function main() {
|
|
|
83
84
|
registerMoodboardTools(server, client);
|
|
84
85
|
registerMediaTools(server, client);
|
|
85
86
|
registerPresetTools(server, client);
|
|
87
|
+
registerAppBuilderTools(server, client);
|
|
86
88
|
|
|
87
89
|
// Start the server with stdio transport
|
|
88
90
|
const transport = new StdioServerTransport();
|
|
@@ -0,0 +1,254 @@
|
|
|
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 { z } = require('zod');
|
|
7
|
+
const { PollingTimeoutError } = require('../polling');
|
|
8
|
+
|
|
9
|
+
// ─── Build-status polling (App Builder uses a different endpoint than /v1/generate) ──
|
|
10
|
+
async function pollBuildStatus(client, sessionId, options = {}) {
|
|
11
|
+
const {
|
|
12
|
+
interval = 5000,
|
|
13
|
+
timeout = 300000 // 5 minutes
|
|
14
|
+
} = options;
|
|
15
|
+
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
const url = `/app-builder/${encodeURIComponent(sessionId)}/build-status`;
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
if (Date.now() - startTime > timeout) {
|
|
21
|
+
throw new PollingTimeoutError(sessionId, timeout);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = await client.get(url);
|
|
25
|
+
|
|
26
|
+
if (result.buildStatus === 'deployed') {
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (result.buildStatus === 'failed') {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`App build failed for session_id="${sessionId}". ` +
|
|
33
|
+
`Call app_builder_get_build_status to check the current state.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function registerAppBuilderTools(server, client) {
|
|
42
|
+
// ─── app_builder_list_projects ─────────────────────────────────────────────
|
|
43
|
+
server.tool(
|
|
44
|
+
'app_builder_list_projects',
|
|
45
|
+
'List all Kolbo projects for the authenticated user. Use this to find the project_id required by app_builder_create_session and app_builder_list_sessions. Projects are the top-level containers — each project can hold multiple App Builder sessions.',
|
|
46
|
+
{},
|
|
47
|
+
async () => {
|
|
48
|
+
const res = await client.get('/project/lightweight');
|
|
49
|
+
const projects = (Array.isArray(res) ? res : (res.data || [])).map(p => ({
|
|
50
|
+
project_id: p._id,
|
|
51
|
+
name: p.name,
|
|
52
|
+
description: p.description || '',
|
|
53
|
+
created_at: p.createdAt
|
|
54
|
+
}));
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// ─── app_builder_create_session ────────────────────────────────────────────
|
|
62
|
+
server.tool(
|
|
63
|
+
'app_builder_create_session',
|
|
64
|
+
'Create a new App Builder session inside a Kolbo project. Returns a session_id to pass to app_builder_generate_app. Sessions hold the full app state across multiple generations and edits.',
|
|
65
|
+
{
|
|
66
|
+
project_id: z.string().describe('Kolbo project ID to scope this session. Use app_builder_list_projects to find your project_id.'),
|
|
67
|
+
name: z.string().optional().describe('Optional initial session name. The backend will auto-generate a name on first generation if omitted.')
|
|
68
|
+
},
|
|
69
|
+
async ({ project_id, name }) => {
|
|
70
|
+
const body = name ? { name } : {};
|
|
71
|
+
const res = await client.post(`/app-builder/session/${encodeURIComponent(project_id)}`, body);
|
|
72
|
+
const session = res.data || res;
|
|
73
|
+
return {
|
|
74
|
+
content: [{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: JSON.stringify({
|
|
77
|
+
session_id: session._id,
|
|
78
|
+
name: session.name,
|
|
79
|
+
build_status: session.buildStatus,
|
|
80
|
+
deployment_url: session.deploymentUrl || null
|
|
81
|
+
}, null, 2)
|
|
82
|
+
}]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// ─── app_builder_generate_app ──────────────────────────────────────────────
|
|
88
|
+
server.tool(
|
|
89
|
+
'app_builder_generate_app',
|
|
90
|
+
'Generate a React app from a text prompt inside an App Builder session. On the FIRST call the backend auto-generates a punchy app name, URL slug, GitHub repo, and (if needed) a Supabase database. The build runs in the background — this tool polls until the app is deployed (up to 5 minutes) then returns the live deployment_url. Always show the user the deployment_url when done.',
|
|
91
|
+
{
|
|
92
|
+
session_id: z.string().describe('Session ID from app_builder_create_session.'),
|
|
93
|
+
prompt: z.string().describe('Natural language description of the app to build (e.g. "a todo app with drag-and-drop and Supabase persistence").')
|
|
94
|
+
},
|
|
95
|
+
async ({ session_id, prompt }) => {
|
|
96
|
+
await client.post(`/app-builder/generation/${encodeURIComponent(session_id)}`, { userPrompt: prompt });
|
|
97
|
+
const status = await pollBuildStatus(client, session_id);
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: JSON.stringify({
|
|
102
|
+
session_id,
|
|
103
|
+
build_status: status.buildStatus,
|
|
104
|
+
deployment_url: status.deploymentUrl || null,
|
|
105
|
+
app_name: status.appName || null
|
|
106
|
+
}, null, 2)
|
|
107
|
+
}]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// ─── app_builder_edit_app ──────────────────────────────────────────────────
|
|
113
|
+
server.tool(
|
|
114
|
+
'app_builder_edit_app',
|
|
115
|
+
'Edit an existing generated app with a natural language instruction — "add a dark mode toggle", "change the color scheme to blue", "add a contact form". Like app_builder_generate_app but for modifications. Use app_builder_list_generations to get the current generation_id before calling this.',
|
|
116
|
+
{
|
|
117
|
+
session_id: z.string().describe('Session ID of the app to edit.'),
|
|
118
|
+
generation_id: z.string().describe('The generation to edit. Use app_builder_list_generations to find the latest generation_id.'),
|
|
119
|
+
edit_prompt: z.string().describe('Natural language instruction describing the change to make.')
|
|
120
|
+
},
|
|
121
|
+
async ({ session_id, generation_id, edit_prompt }) => {
|
|
122
|
+
await client.request(
|
|
123
|
+
'PUT',
|
|
124
|
+
`/app-builder/generation/${encodeURIComponent(session_id)}/${encodeURIComponent(generation_id)}`,
|
|
125
|
+
{ editPrompt: edit_prompt }
|
|
126
|
+
);
|
|
127
|
+
const status = await pollBuildStatus(client, session_id);
|
|
128
|
+
return {
|
|
129
|
+
content: [{
|
|
130
|
+
type: 'text',
|
|
131
|
+
text: JSON.stringify({
|
|
132
|
+
session_id,
|
|
133
|
+
build_status: status.buildStatus,
|
|
134
|
+
deployment_url: status.deploymentUrl || null,
|
|
135
|
+
app_name: status.appName || null
|
|
136
|
+
}, null, 2)
|
|
137
|
+
}]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// ─── app_builder_get_build_status ──────────────────────────────────────────
|
|
143
|
+
server.tool(
|
|
144
|
+
'app_builder_get_build_status',
|
|
145
|
+
'Check the current build status of an App Builder session. Use this to manually poll after app_builder_generate_app or app_builder_edit_app, or to check on an app at any time. Returns "deployed" when the live URL is ready.',
|
|
146
|
+
{
|
|
147
|
+
session_id: z.string().describe('Session ID to check.')
|
|
148
|
+
},
|
|
149
|
+
async ({ session_id }) => {
|
|
150
|
+
const result = await client.get(`/app-builder/${encodeURIComponent(session_id)}/build-status`);
|
|
151
|
+
return {
|
|
152
|
+
content: [{
|
|
153
|
+
type: 'text',
|
|
154
|
+
text: JSON.stringify({
|
|
155
|
+
build_status: result.buildStatus,
|
|
156
|
+
deployment_url: result.deploymentUrl || null,
|
|
157
|
+
deployed_at: result.deployedAt || null
|
|
158
|
+
}, null, 2)
|
|
159
|
+
}]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// ─── app_builder_get_session ───────────────────────────────────────────────
|
|
165
|
+
server.tool(
|
|
166
|
+
'app_builder_get_session',
|
|
167
|
+
'Get full details of an App Builder session including metadata, build status, deployment URL, and GitHub/Supabase integration info. Use this when the user wants to clone the app locally — it returns the GitHub repo URL and Supabase connection details needed for local development.',
|
|
168
|
+
{
|
|
169
|
+
session_id: z.string().describe('Session ID to retrieve.')
|
|
170
|
+
},
|
|
171
|
+
async ({ session_id }) => {
|
|
172
|
+
const res = await client.get(`/app-builder/session/${encodeURIComponent(session_id)}`);
|
|
173
|
+
const session = res.data || res;
|
|
174
|
+
return {
|
|
175
|
+
content: [{
|
|
176
|
+
type: 'text',
|
|
177
|
+
text: JSON.stringify({
|
|
178
|
+
session_id: session._id,
|
|
179
|
+
name: session.name,
|
|
180
|
+
build_status: session.buildStatus,
|
|
181
|
+
deployment_url: session.deploymentUrl || null,
|
|
182
|
+
github_repo_url: session.githubRepoUrl || null,
|
|
183
|
+
supabase_url: session.supabaseUrl || null,
|
|
184
|
+
supabase_anon_key: session.supabaseAnonKey || null,
|
|
185
|
+
created_at: session.createdAt
|
|
186
|
+
}, null, 2)
|
|
187
|
+
}]
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// ─── app_builder_list_sessions ─────────────────────────────────────────────
|
|
193
|
+
server.tool(
|
|
194
|
+
'app_builder_list_sessions',
|
|
195
|
+
'List all App Builder sessions in a project. Use this to find existing sessions before creating a new one, or to pick a session_id to continue working on.',
|
|
196
|
+
{
|
|
197
|
+
project_id: z.string().describe('Kolbo project ID. Use app_builder_list_projects to find it.')
|
|
198
|
+
},
|
|
199
|
+
async ({ project_id }) => {
|
|
200
|
+
const res = await client.get(`/app-builder/sessions/${encodeURIComponent(project_id)}`);
|
|
201
|
+
const sessions = (Array.isArray(res) ? res : (res.data || [])).map(s => ({
|
|
202
|
+
session_id: s._id,
|
|
203
|
+
name: s.name,
|
|
204
|
+
build_status: s.buildStatus,
|
|
205
|
+
deployment_url: s.deploymentUrl || null,
|
|
206
|
+
created_at: s.createdAt
|
|
207
|
+
}));
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: 'text', text: JSON.stringify(sessions, null, 2) }]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// ─── app_builder_list_generations ──────────────────────────────────────────
|
|
215
|
+
server.tool(
|
|
216
|
+
'app_builder_list_generations',
|
|
217
|
+
'List all generations for an App Builder session, newest first. Use this to find the current generation_id before calling app_builder_edit_app.',
|
|
218
|
+
{
|
|
219
|
+
session_id: z.string().describe('Session ID to list generations for.')
|
|
220
|
+
},
|
|
221
|
+
async ({ session_id }) => {
|
|
222
|
+
const res = await client.get(`/app-builder/generations/${encodeURIComponent(session_id)}`);
|
|
223
|
+
const generations = (Array.isArray(res) ? res : (res.data || [])).map(g => ({
|
|
224
|
+
generation_id: g._id,
|
|
225
|
+
user_prompt: g.userPrompt || g.editPrompt || '',
|
|
226
|
+
build_status: g.buildStatus,
|
|
227
|
+
created_at: g.createdAt
|
|
228
|
+
}));
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: 'text', text: JSON.stringify(generations, null, 2) }]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// ─── app_builder_delete_session ────────────────────────────────────────────
|
|
236
|
+
server.tool(
|
|
237
|
+
'app_builder_delete_session',
|
|
238
|
+
'Permanently delete an App Builder session and ALL associated resources: GitHub repo, Supabase database (unless user-connected), deployed files, generation history, messages, and form submissions. THIS IS IRREVERSIBLE — always confirm with the user before calling.',
|
|
239
|
+
{
|
|
240
|
+
session_id: z.string().describe('Session ID to permanently delete. This cannot be undone.')
|
|
241
|
+
},
|
|
242
|
+
async ({ session_id }) => {
|
|
243
|
+
await client.delete(`/app-builder/session/${encodeURIComponent(session_id)}`);
|
|
244
|
+
return {
|
|
245
|
+
content: [{
|
|
246
|
+
type: 'text',
|
|
247
|
+
text: JSON.stringify({ success: true }, null, 2)
|
|
248
|
+
}]
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = { registerAppBuilderTools };
|
package/src/tools/generate.js
CHANGED
|
@@ -22,12 +22,14 @@ function registerGenerateTools(server, client) {
|
|
|
22
22
|
reference_images: z.array(z.string()).optional().describe('Array of image URLs used as composition/style references (NOT as source images for editing — use generate_image_edit for that).'),
|
|
23
23
|
visual_dna_ids: z.array(z.string()).optional().describe('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.'),
|
|
24
24
|
moodboard_id: z.string().optional().describe('Moodboard ID (from list_moodboards / get_moodboard) whose master_prompt and style_guide should be applied to this generation.'),
|
|
25
|
-
enable_web_search: z.boolean().optional().describe('Enable web-search grounding for the prompt (useful for current events, brand references, real-world accuracy). Default: false')
|
|
25
|
+
enable_web_search: z.boolean().optional().describe('Enable web-search grounding for the prompt (useful for current events, brand references, real-world accuracy). Default: false'),
|
|
26
|
+
resolution: z.string().optional().describe('Image resolution tier: "1K" (~1024px), "2K" (Full HD), "3K" (QHD), or "4K" (UHD). Model-dependent — call list_models and read supported_resolutions on the chosen model. Read resolutionMultipliers on the same model to predict credit cost. Omit to use the model default.'),
|
|
27
|
+
preset_id: z.string().optional().describe('Preset ID from list_presets type="image" to apply a saved style preset to this generation.')
|
|
26
28
|
},
|
|
27
|
-
async ({ prompt, model, aspect_ratio, enhance_prompt, num_images, reference_images, visual_dna_ids, moodboard_id, enable_web_search }) => {
|
|
29
|
+
async ({ prompt, model, aspect_ratio, enhance_prompt, num_images, reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, preset_id }) => {
|
|
28
30
|
const gen = await client.post('/v1/generate/image', {
|
|
29
31
|
prompt, model, aspect_ratio, enhance_prompt, num_images,
|
|
30
|
-
reference_images, visual_dna_ids, moodboard_id, enable_web_search
|
|
32
|
+
reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, preset_id
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -61,12 +63,13 @@ function registerGenerateTools(server, client) {
|
|
|
61
63
|
num_images: z.number().optional().describe('Number of output images. Default: 1'),
|
|
62
64
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for consistency with an existing character / style / product.'),
|
|
63
65
|
moodboard_id: z.string().optional().describe('Moodboard ID whose master_prompt and style_guide should be applied.'),
|
|
64
|
-
enable_web_search: z.boolean().optional().describe('Enable web-search grounding. Default: false')
|
|
66
|
+
enable_web_search: z.boolean().optional().describe('Enable web-search grounding. Default: false'),
|
|
67
|
+
resolution: z.string().optional().describe('Image resolution tier: "1K" / "2K" / "3K" / "4K". Model-dependent — call list_models and read supported_resolutions. Default: "1K" for most edit models.')
|
|
65
68
|
},
|
|
66
|
-
async ({ prompt, model, source_images, aspect_ratio, enhance_prompt, num_images, visual_dna_ids, moodboard_id, enable_web_search }) => {
|
|
69
|
+
async ({ prompt, model, source_images, aspect_ratio, enhance_prompt, num_images, visual_dna_ids, moodboard_id, enable_web_search, resolution }) => {
|
|
67
70
|
const gen = await client.post('/v1/generate/image-edit', {
|
|
68
71
|
prompt, model, source_images, aspect_ratio, enhance_prompt, num_images,
|
|
69
|
-
visual_dna_ids, moodboard_id, enable_web_search
|
|
72
|
+
visual_dna_ids, moodboard_id, enable_web_search, resolution
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -102,12 +105,13 @@ function registerGenerateTools(server, client) {
|
|
|
102
105
|
reference_images: z.array(z.string()).optional().describe('Array of reference image URLs to guide style/composition of every scene.'),
|
|
103
106
|
visual_dna_ids: z.array(z.string()).optional().describe('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.'),
|
|
104
107
|
moodboard_id: z.string().optional().describe('A single moodboard ID whose master_prompt and style_guide should shape every scene.'),
|
|
105
|
-
moodboard_ids: z.array(z.string()).optional().describe('Multiple moodboard IDs when blending styles. Prefer `moodboard_id` for single moodboards.')
|
|
108
|
+
moodboard_ids: z.array(z.string()).optional().describe('Multiple moodboard IDs when blending styles. Prefer `moodboard_id` for single moodboards.'),
|
|
109
|
+
resolution: z.string().optional().describe('Resolution tier applied to every scene. Images: "1K" / "2K" / "3K" / "4K". Videos: "720p" / "1080p" / "1440p" / "2160p". Values are model-dependent — call list_models and read supported_resolutions on the target model. Multiplied across every scene.')
|
|
106
110
|
},
|
|
107
|
-
async ({ prompt, scene_count, model, aspect_ratio, workflow_type, duration, enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids }) => {
|
|
111
|
+
async ({ prompt, scene_count, model, aspect_ratio, workflow_type, duration, enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids, resolution }) => {
|
|
108
112
|
const gen = await client.post('/v1/generate/creative-director', {
|
|
109
113
|
prompt, scene_count, model, aspect_ratio, workflow_type, duration,
|
|
110
|
-
enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids
|
|
114
|
+
enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids, resolution
|
|
111
115
|
});
|
|
112
116
|
|
|
113
117
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -149,11 +153,13 @@ function registerGenerateTools(server, client) {
|
|
|
149
153
|
duration: z.number().optional().describe('Duration in seconds. Must be a value the chosen model supports — check supported_durations from list_models. Default: 5'),
|
|
150
154
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
151
155
|
reference_images: z.array(z.string()).optional().describe('Array of image URLs used as visual references (style / composition / subject).'),
|
|
152
|
-
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to keep a character / style consistent with prior generations.')
|
|
156
|
+
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to keep a character / style consistent with prior generations.'),
|
|
157
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Some models use labels like "512P"/"1024P"/"768P"/"1080P". Model-dependent — call list_models and read supported_resolutions. Read resolutionMultipliers to predict cost.'),
|
|
158
|
+
preset_id: z.string().optional().describe('Preset ID from list_presets type="video" to apply a saved motion/style preset to this generation.')
|
|
153
159
|
},
|
|
154
|
-
async ({ prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, visual_dna_ids }) => {
|
|
160
|
+
async ({ prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, visual_dna_ids, resolution, preset_id }) => {
|
|
155
161
|
const gen = await client.post('/v1/generate/video', {
|
|
156
|
-
prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, visual_dna_ids
|
|
162
|
+
prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, visual_dna_ids, resolution, preset_id
|
|
157
163
|
});
|
|
158
164
|
|
|
159
165
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -187,11 +193,12 @@ function registerGenerateTools(server, client) {
|
|
|
187
193
|
aspect_ratio: z.string().optional().describe('Output aspect ratio (e.g., "16:9", "9:16", "1:1"). Default: "16:9"'),
|
|
188
194
|
duration: z.number().optional().describe('Duration in seconds. Must be a value the chosen model supports. Default: 5'),
|
|
189
195
|
enhance_prompt: z.boolean().optional().describe('Enhance the motion prompt. Default: true'),
|
|
190
|
-
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to maintain consistency with prior characters / styles.')
|
|
196
|
+
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to maintain consistency with prior characters / styles.'),
|
|
197
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Some models use labels like "512P"/"1024P"/"768P"/"1080P". Model-dependent — call list_models and read supported_resolutions. Read resolutionMultipliers to predict cost.')
|
|
191
198
|
},
|
|
192
|
-
async ({ image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids }) => {
|
|
199
|
+
async ({ image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution }) => {
|
|
193
200
|
const gen = await client.post('/v1/generate/video/from-image', {
|
|
194
|
-
image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids
|
|
201
|
+
image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution
|
|
195
202
|
});
|
|
196
203
|
|
|
197
204
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -224,11 +231,12 @@ function registerGenerateTools(server, client) {
|
|
|
224
231
|
instrumental: z.boolean().optional().describe('Generate instrumental only, no vocals. Default: false'),
|
|
225
232
|
lyrics: z.string().optional().describe('Custom lyrics for the song. If omitted, lyrics are generated automatically from the prompt unless instrumental is true.'),
|
|
226
233
|
vocal_gender: z.string().optional().describe('Preferred vocal gender: "male" or "female". Only applies when instrumental is false.'),
|
|
227
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true')
|
|
234
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
235
|
+
preset_id: z.string().optional().describe('Preset ID from list_presets type="music" to apply a saved music style preset.')
|
|
228
236
|
},
|
|
229
|
-
async ({ prompt, model, style, instrumental, lyrics, vocal_gender, enhance_prompt }) => {
|
|
237
|
+
async ({ prompt, model, style, instrumental, lyrics, vocal_gender, enhance_prompt, preset_id }) => {
|
|
230
238
|
const gen = await client.post('/v1/generate/music', {
|
|
231
|
-
prompt, model, style, instrumental, lyrics, vocal_gender, enhance_prompt
|
|
239
|
+
prompt, model, style, instrumental, lyrics, vocal_gender, enhance_prompt, preset_id
|
|
232
240
|
});
|
|
233
241
|
|
|
234
242
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -290,11 +298,12 @@ function registerGenerateTools(server, client) {
|
|
|
290
298
|
{
|
|
291
299
|
prompt: z.string().describe('Text description of the sound effect (e.g., "thunder clap with rain", "door creaking open", "futuristic UI beep")'),
|
|
292
300
|
model: z.string().optional().describe('Model identifier. Use list_models type="text_to_sound" to see options. Default: elevenlabs-sound-effects-v1'),
|
|
293
|
-
duration: z.number().optional().describe('Duration in seconds. Omit for automatic duration.')
|
|
301
|
+
duration: z.number().optional().describe('Duration in seconds. Omit for automatic duration.'),
|
|
302
|
+
prompt_influence: z.number().optional().describe('How strongly the prompt guides the generation (0–1). Default: 0.5. Lower values give the model more creative freedom; higher values follow the prompt more literally.')
|
|
294
303
|
},
|
|
295
|
-
async ({ prompt, model, duration }) => {
|
|
304
|
+
async ({ prompt, model, duration, prompt_influence }) => {
|
|
296
305
|
const gen = await client.post('/v1/generate/sound', {
|
|
297
|
-
prompt, model, duration
|
|
306
|
+
prompt, model, duration, prompt_influence
|
|
298
307
|
});
|
|
299
308
|
|
|
300
309
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -377,20 +386,23 @@ function registerGenerateTools(server, client) {
|
|
|
377
386
|
// ─── generate_elements ─────────────────────────────────────
|
|
378
387
|
server.tool(
|
|
379
388
|
'generate_elements',
|
|
380
|
-
'Generate a video from reference elements (images and/or
|
|
389
|
+
'Generate a video from reference elements (images, videos, and/or audio) + a text prompt. Use when the user wants to animate specific uploaded/referenced assets — e.g. "animate this product", "put these 3 characters into a scene". IMPORTANT: different models accept different numbers of inputs — call list_models type="elements" and read elementsMaxImages / elementsMaxVideos / elementsMaxAudio on the chosen model before generating. For text-only → video use generate_video instead. For animating a single still image use generate_video_from_image. Returns the final video URL when complete.',
|
|
381
390
|
{
|
|
382
391
|
prompt: z.string().describe('Text description of the desired video / animation'),
|
|
383
|
-
model: z.string().optional().describe('Model identifier. Use list_models type="elements" to see options (Seedance 2, Kling O3 Reference, Grok Imagine, Veo 3.1, etc.). Omit for Smart Select.'),
|
|
384
|
-
reference_images: z.array(z.string()).optional().describe('Array of public image URLs used as reference elements (product shots, character references, etc.).
|
|
392
|
+
model: z.string().optional().describe('Model identifier. Use list_models type="elements" to see options (Seedance 2, Kling O3 Reference, Grok Imagine, Veo 3.1, etc.). Check elementsMaxImages / elementsMaxVideos / elementsMaxAudio on the model. Omit for Smart Select.'),
|
|
393
|
+
reference_images: z.array(z.string()).optional().describe('Array of public image URLs used as reference elements (product shots, character references, etc.). Check elementsMaxImages on the chosen model — pass at most that many URLs.'),
|
|
394
|
+
reference_videos: z.array(z.string()).optional().describe('Array of reference video URLs for models that accept video inputs (elementsMaxVideos > 0). Check elementsMaxVideos on the chosen model from list_models before passing.'),
|
|
395
|
+
audio_url: z.string().optional().describe('URL of a reference audio track for models that accept audio inputs (elementsMaxAudio > 0). Check elementsMaxAudio on the chosen model from list_models before passing.'),
|
|
385
396
|
files: z.array(z.string()).optional().describe('Array of URLs or absolute local paths — alternative to reference_images. Use this when you have local files to upload. Each item can be a URL OR a local path.'),
|
|
386
397
|
duration: z.number().optional().describe('Duration in seconds. Default: 5'),
|
|
387
398
|
aspect_ratio: z.string().optional().describe('Aspect ratio (e.g., "16:9", "9:16", "1:1"). Default: "16:9"'),
|
|
388
399
|
motion: z.string().optional().describe('Motion style / intensity hint (optional)'),
|
|
389
400
|
preset_id: z.string().optional().describe('Preset ID from list_presets type="video" (optional)'),
|
|
390
401
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
391
|
-
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency across outputs.')
|
|
402
|
+
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency across outputs.'),
|
|
403
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.')
|
|
392
404
|
},
|
|
393
|
-
async ({ prompt, model, reference_images, files, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids }) => {
|
|
405
|
+
async ({ prompt, model, reference_images, reference_videos, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution }) => {
|
|
394
406
|
if (!prompt) throw new Error('prompt is required');
|
|
395
407
|
|
|
396
408
|
let startResponse;
|
|
@@ -407,6 +419,9 @@ function registerGenerateTools(server, client) {
|
|
|
407
419
|
if (enhance_prompt !== undefined) form.append('enhance_prompt', String(enhance_prompt));
|
|
408
420
|
if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
|
|
409
421
|
if (reference_images) form.append('reference_images', JSON.stringify(reference_images));
|
|
422
|
+
if (reference_videos) form.append('reference_videos', JSON.stringify(reference_videos));
|
|
423
|
+
if (audio_url) form.append('audio_url', audio_url);
|
|
424
|
+
if (resolution) form.append('resolution', resolution);
|
|
410
425
|
for (const f of resolved) {
|
|
411
426
|
form.append('files', f.buffer, { filename: f.filename, contentType: f.contentType });
|
|
412
427
|
}
|
|
@@ -414,7 +429,7 @@ function registerGenerateTools(server, client) {
|
|
|
414
429
|
} else {
|
|
415
430
|
// URL-only mode: plain JSON.
|
|
416
431
|
startResponse = await client.post('/v1/generate/elements', {
|
|
417
|
-
prompt, model, reference_images, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids
|
|
432
|
+
prompt, model, reference_images, reference_videos, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution
|
|
418
433
|
});
|
|
419
434
|
}
|
|
420
435
|
|
|
@@ -451,9 +466,10 @@ function registerGenerateTools(server, client) {
|
|
|
451
466
|
duration: z.number().optional().describe('Duration in seconds. Default: 5'),
|
|
452
467
|
aspect_ratio: z.string().optional().describe('Aspect ratio (auto-detected from first frame if not provided). Default: "16:9"'),
|
|
453
468
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
454
|
-
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply.')
|
|
469
|
+
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply.'),
|
|
470
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.')
|
|
455
471
|
},
|
|
456
|
-
async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids }) => {
|
|
472
|
+
async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution }) => {
|
|
457
473
|
const urlMode = first_frame_url && last_frame_url;
|
|
458
474
|
const fileMode = first_frame && last_frame;
|
|
459
475
|
if (!urlMode && !fileMode) {
|
|
@@ -478,10 +494,11 @@ function registerGenerateTools(server, client) {
|
|
|
478
494
|
if (aspect_ratio) form.append('aspect_ratio', aspect_ratio);
|
|
479
495
|
if (enhance_prompt !== undefined) form.append('enhance_prompt', String(enhance_prompt));
|
|
480
496
|
if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
|
|
497
|
+
if (resolution) form.append('resolution', resolution);
|
|
481
498
|
startResponse = await client.postMultipart('/v1/generate/first-last-frame', form);
|
|
482
499
|
} else {
|
|
483
500
|
startResponse = await client.post('/v1/generate/first-last-frame', {
|
|
484
|
-
first_frame_url, last_frame_url, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids
|
|
501
|
+
first_frame_url, last_frame_url, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution
|
|
485
502
|
});
|
|
486
503
|
}
|
|
487
504
|
|
|
@@ -577,17 +594,21 @@ function registerGenerateTools(server, client) {
|
|
|
577
594
|
// ─── generate_video_from_video ─────────────────────────────
|
|
578
595
|
server.tool(
|
|
579
596
|
'generate_video_from_video',
|
|
580
|
-
'Restyle / transform an existing video using a text prompt (video-to-video). Use for style transfer, scene restyling, subject swap —
|
|
597
|
+
'Restyle / transform an existing video using a text prompt (video-to-video). Use for style transfer, scene restyling, subject swap, motion transfer, or character replacement. Source video can be a URL or absolute local path. IMPORTANT: different models support different extra inputs — call list_models type="video_to_video" and read maxImages / maxVideos / maxElements on the chosen model before generating. Pass reference_images for models with maxImages > 0 (e.g. Kling O1/O3, Aleph, WAN VACE), reference_videos for models with maxVideos > 1 (e.g. WAN 2.6 reference-to-video accepts up to 3), and elements for models with maxElements > 0. For animating a still image use generate_video_from_image instead. For text-only → video use generate_video.',
|
|
581
598
|
{
|
|
582
|
-
source_video: z.string().describe('URL or absolute local path to the source video to restyle'),
|
|
599
|
+
source_video: z.string().describe('URL or absolute local path to the primary source video to restyle. For models that use reference_videos as their primary input (e.g. WAN 2.6 reference-to-video), pass the first reference video here and also include it in reference_videos.'),
|
|
583
600
|
prompt: z.string().describe('Text description of the desired restyle / transformation'),
|
|
584
|
-
model: z.string().optional().describe('Model identifier. Use list_models type="video_to_video" to see options. Omit for Smart Select.'),
|
|
601
|
+
model: z.string().optional().describe('Model identifier. Use list_models type="video_to_video" to see options and check maxImages / maxVideos / maxElements per model. Omit for Smart Select.'),
|
|
585
602
|
aspect_ratio: z.string().optional().describe('Output aspect ratio. Default: matches source'),
|
|
586
603
|
duration: z.number().optional().describe('Duration in seconds (default: matches source)'),
|
|
587
604
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
588
|
-
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency.')
|
|
605
|
+
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency.'),
|
|
606
|
+
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
|
|
607
|
+
reference_images: z.array(z.string()).optional().describe('Array of reference image URLs for models that support additional image inputs (maxImages > 0). Examples: character reference images for Kling O1/O3, style reference for Aleph/gen4_aleph, character image for WAN VACE video-edit. Check maxImages on the model from list_models before passing.'),
|
|
608
|
+
reference_videos: z.array(z.string()).optional().describe('Array of additional reference video URLs for models that support multiple video inputs (maxVideos > 1). Example: WAN 2.6 reference-to-video accepts 1–3 reference videos. Check maxVideos on the model from list_models before passing.'),
|
|
609
|
+
elements: z.array(z.string()).optional().describe('Array of element image URLs for models with maxElements > 0. Elements are used as style or character reference assets alongside the main video. Check maxElements on the model from list_models before passing.')
|
|
589
610
|
},
|
|
590
|
-
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids }) => {
|
|
611
|
+
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, reference_images, reference_videos, elements }) => {
|
|
591
612
|
if (!source_video) throw new Error('source_video is required');
|
|
592
613
|
if (!prompt) throw new Error('prompt is required');
|
|
593
614
|
|
|
@@ -595,7 +616,8 @@ function registerGenerateTools(server, client) {
|
|
|
595
616
|
let startResponse;
|
|
596
617
|
if (isUrl) {
|
|
597
618
|
startResponse = await client.post('/v1/generate/video-from-video', {
|
|
598
|
-
video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids
|
|
619
|
+
video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution,
|
|
620
|
+
reference_images, reference_videos, elements
|
|
599
621
|
});
|
|
600
622
|
} else {
|
|
601
623
|
const resolved = await resolveToBuffer(source_video, 'video');
|
|
@@ -607,6 +629,10 @@ function registerGenerateTools(server, client) {
|
|
|
607
629
|
if (duration !== undefined) form.append('duration', String(duration));
|
|
608
630
|
if (enhance_prompt !== undefined) form.append('enhance_prompt', String(enhance_prompt));
|
|
609
631
|
if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
|
|
632
|
+
if (resolution) form.append('resolution', resolution);
|
|
633
|
+
if (reference_images) form.append('reference_images', JSON.stringify(reference_images));
|
|
634
|
+
if (reference_videos) form.append('reference_videos', JSON.stringify(reference_videos));
|
|
635
|
+
if (elements) form.append('elements', JSON.stringify(elements));
|
|
610
636
|
startResponse = await client.postMultipart('/v1/generate/video-from-video', form);
|
|
611
637
|
}
|
|
612
638
|
|
|
@@ -720,6 +746,96 @@ function registerGenerateTools(server, client) {
|
|
|
720
746
|
};
|
|
721
747
|
}
|
|
722
748
|
);
|
|
749
|
+
// ─── edit_image ────────────────────────────────────────────
|
|
750
|
+
server.tool(
|
|
751
|
+
'edit_image',
|
|
752
|
+
'Apply a targeted AI edit to an existing image. Use for upscaling resolution, changing aspect ratio (reframe), removing the background, portrait skin enhancement, or a text-guided edit (magic_edit). Faster and cheaper than generate_image_edit for these specific operations because it routes to specialized models. Returns the edited image URL when complete.',
|
|
753
|
+
{
|
|
754
|
+
image_url: z.string().describe('URL of the source image to edit'),
|
|
755
|
+
operation: z.enum(['upscale', 'reframe', 'removebg', 'enhance_skin', 'magic_edit'])
|
|
756
|
+
.describe('Edit operation to apply: "upscale" (increase resolution 2×–4×), "reframe" (change aspect ratio), "removebg" (remove background), "enhance_skin" (portrait retouching), "magic_edit" (text-guided edit — requires prompt)'),
|
|
757
|
+
model: z.string().optional().describe('Model identifier override. Omit to use the default model for the operation.'),
|
|
758
|
+
scale: z.number().optional().describe('Upscale factor: 2, 3, or 4. Only used when operation="upscale". Default: 2.'),
|
|
759
|
+
aspect_ratio: z.string().optional().describe('Target aspect ratio (e.g., "16:9", "9:16", "1:1"). Required for operation="reframe".'),
|
|
760
|
+
skin_strength: z.enum(['subtle', 'realistic', 'pimple', 'freckle']).optional()
|
|
761
|
+
.describe('Skin enhancement style. Only used when operation="enhance_skin". Default: "realistic".'),
|
|
762
|
+
prompt: z.string().optional().describe('Text instruction for the edit. Required for operation="magic_edit" (e.g., "add sunglasses", "change the sky to sunset").')
|
|
763
|
+
},
|
|
764
|
+
async ({ image_url, operation, model, scale, aspect_ratio, skin_strength, prompt }) => {
|
|
765
|
+
if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit operation');
|
|
766
|
+
if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe operation');
|
|
767
|
+
|
|
768
|
+
const gen = await client.post('/v1/edit/image', {
|
|
769
|
+
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
773
|
+
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
774
|
+
timeout: 180000
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
return {
|
|
778
|
+
content: [{
|
|
779
|
+
type: 'text',
|
|
780
|
+
text: JSON.stringify({
|
|
781
|
+
urls: result.result?.urls || [],
|
|
782
|
+
edit_type: result.result?.edit_type || null,
|
|
783
|
+
model: result.result?.model || null
|
|
784
|
+
}, null, 2)
|
|
785
|
+
}]
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
// ─── edit_video ────────────────────────────────────────────
|
|
791
|
+
server.tool(
|
|
792
|
+
'edit_video',
|
|
793
|
+
'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.',
|
|
794
|
+
{
|
|
795
|
+
video_url: z.string().describe('URL of the source video to edit'),
|
|
796
|
+
operation: z.enum(['upscale', 'reframe', 'generate_audio', 'remove_watermark', 'face_swap', 'extend', 'magic_edit', 'lipsync'])
|
|
797
|
+
.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)'),
|
|
798
|
+
model: z.string().optional().describe('Model identifier override. Omit to use the default model for the operation.'),
|
|
799
|
+
aspect_ratio: z.string().optional().describe('Target aspect ratio (e.g., "16:9", "9:16"). Required for operation="reframe".'),
|
|
800
|
+
scale: z.number().optional().describe('Upscale factor. Only used when operation="upscale".'),
|
|
801
|
+
prompt: z.string().optional().describe('Text prompt. Required for operation="magic_edit" and "generate_audio". Optional hint for "extend".'),
|
|
802
|
+
image_url: z.string().optional().describe('URL of the reference face image. Required for operation="face_swap".'),
|
|
803
|
+
audio_url: z.string().optional().describe('URL of the audio track to sync. Required for operation="lipsync".'),
|
|
804
|
+
duration: z.number().optional().describe('Seconds of video to generate. Required for operation="extend". Typical range: 1–20.'),
|
|
805
|
+
mode: z.string().optional().describe('Where to extend: "start" or "end". Only used when operation="extend". Default: "end".')
|
|
806
|
+
},
|
|
807
|
+
async ({ video_url, operation, model, aspect_ratio, scale, prompt, image_url, audio_url, duration, mode }) => {
|
|
808
|
+
if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit');
|
|
809
|
+
if (operation === 'generate_audio' && !prompt) throw new Error('prompt is required for generate_audio');
|
|
810
|
+
if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe');
|
|
811
|
+
if (operation === 'face_swap' && !image_url) throw new Error('image_url (reference face) is required for face_swap');
|
|
812
|
+
if (operation === 'lipsync' && !audio_url) throw new Error('audio_url is required for lipsync');
|
|
813
|
+
if (operation === 'extend' && !duration) throw new Error('duration is required for extend');
|
|
814
|
+
|
|
815
|
+
const gen = await client.post('/v1/edit/video', {
|
|
816
|
+
video_url, operation, model, aspect_ratio, scale, prompt,
|
|
817
|
+
image_url, audio_url, duration, mode
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
const result = await pollUntilDone(client, gen.generation_id, {
|
|
821
|
+
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
822
|
+
timeout: 600000
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
return {
|
|
826
|
+
content: [{
|
|
827
|
+
type: 'text',
|
|
828
|
+
text: JSON.stringify({
|
|
829
|
+
urls: result.result?.urls || [],
|
|
830
|
+
download_url: result.result?.download_url || null,
|
|
831
|
+
edit_type: result.result?.edit_type || null,
|
|
832
|
+
duration: result.result?.duration || null,
|
|
833
|
+
model: result.result?.model || null
|
|
834
|
+
}, null, 2)
|
|
835
|
+
}]
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
);
|
|
723
839
|
}
|
|
724
840
|
|
|
725
841
|
module.exports = { registerGenerateTools };
|