@kolbo/mcp 1.54.0 → 1.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -14
- package/package.json +5 -3
- package/skill/GENERATED.md +1 -1
- package/skill/SKILL.md +16 -28
- package/skill/VERSION +1 -1
- package/skill/references/models/music.md +1 -1
- package/skill/references/workflows/media-library.md +29 -1
- package/skill/references/workflows/troubleshooting.md +13 -0
- package/src/client.js +60 -6
- package/src/index.js +31 -2
- package/src/install.js +35 -3
- package/src/polling.js +7 -0
- package/src/progress.js +55 -0
- package/src/tools/_shared.js +137 -11
- package/src/tools/artifacts.js +23 -5
- package/src/tools/chat.js +3 -3
- package/src/tools/generate.js +21 -19
- package/src/tools/media.js +104 -12
- package/src/tools/models.js +55 -3
- package/src/tools/moodboards.js +1 -1
- package/src/tools/music_library.js +31 -4
- package/src/tools/presets.js +56 -17
- package/src/tools/projects.js +1 -1
- package/src/tools/stock_library.js +23 -3
- package/src/tools/visual_dna.js +9 -5
- package/src/tools/voices.js +10 -1
- package/skill/references/models/voice-tts.md +0 -85
- package/skill/references/workflows/app-builder.md +0 -160
- package/src/tools/app_builder.js +0 -253
- package/src/tools/shorts_creator.js +0 -404
package/src/tools/_shared.js
CHANGED
|
@@ -30,6 +30,64 @@ const MAX_FILE_BYTES = 500 * 1024 * 1024; // 500 MB — larger than visual_dna b
|
|
|
30
30
|
const VISUAL_DNA_MAX_BYTES = 25 * 1024 * 1024; // kept for visual_dna backward-compat
|
|
31
31
|
const MAX_REDIRECTS = 5;
|
|
32
32
|
|
|
33
|
+
// THE single statement of how a local file gets into Kolbo. It is repeated to
|
|
34
|
+
// the model on several surfaces — the server `instructions` block (src/index.js),
|
|
35
|
+
// the media tool descriptions, and both local-path errors below — so it lives
|
|
36
|
+
// here and is imported, not retyped. It was previously pasted in five places and
|
|
37
|
+
// a change to it updated only two, leaving `instructions` teaching the opposite.
|
|
38
|
+
const LOCAL_FILE_ROUTING =
|
|
39
|
+
'If you are using Kolbo over a remote connector (e.g. claude.ai), local files are not reachable. ' +
|
|
40
|
+
'DO NOT upload the file yourself with cloud credentials or a shell command — Kolbo has a tool for this. ' +
|
|
41
|
+
'If you can run shell commands, call `create_upload_ticket` and POST the file to the returned upload_url. ' +
|
|
42
|
+
'Otherwise call `media_upload_widget` to have the user pick the file, or `upload_media` ' +
|
|
43
|
+
'when the file IS reachable from where the MCP server runs, then pass the returned https:// URL here. ' +
|
|
44
|
+
'A URL from `list_media` also works if the asset is already in the library.';
|
|
45
|
+
|
|
46
|
+
// Every tool that takes user media as INPUT. Their descriptions promise "URL or
|
|
47
|
+
// absolute local path" — true on a stdio install, a lie over a remote connector,
|
|
48
|
+
// where the model would read it, see no filesystem, and tell the user Kolbo
|
|
49
|
+
// cannot take their file at all. attachFileInputHints() below appends the route
|
|
50
|
+
// that actually works for the current transport, so the refusal never happens.
|
|
51
|
+
const FILE_INPUT_TOOLS = [
|
|
52
|
+
'generate_image', 'generate_image_edit', 'generate_creative_director',
|
|
53
|
+
'generate_video', 'generate_video_from_image', 'generate_video_from_video',
|
|
54
|
+
'generate_elements', 'generate_first_last_frame', 'generate_lipsync',
|
|
55
|
+
'generate_3d', 'edit_image', 'edit_video', 'transcribe_audio',
|
|
56
|
+
'create_visual_dna', 'generate_character_sheet', 'clone_voice',
|
|
57
|
+
'chat_send_message', 'create_moodboard'
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const REMOTE_FILE_HINT =
|
|
61
|
+
' LOCAL FILE (on the user\'s machine, or attached to this chat)? NEVER reply that you cannot upload files — Kolbo uploads it for you. ' +
|
|
62
|
+
'Call `media_upload_widget` so the user picks the file (claude.ai web/mobile), or `create_upload_ticket` and POST the file yourself if you can run shell/HTTP commands; ' +
|
|
63
|
+
'either way you get an https:// URL to pass here. Ignore any "absolute local path" wording in the args below — this server cannot read the caller\'s disk, so a path will fail.';
|
|
64
|
+
|
|
65
|
+
const LOCAL_FILE_HINT =
|
|
66
|
+
' LOCAL FILE? Absolute local paths work here (server and client share a filesystem). ' +
|
|
67
|
+
'Never reply that you cannot upload files — for a file you will reference more than once, call `upload_media` first and reuse the returned https:// URL.';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Append the transport-correct local-file route to every media-input tool's
|
|
71
|
+
* description, post-registration (same pattern as attachToolWidgetMeta).
|
|
72
|
+
* `options.apps === true` is set ONLY by kolbo-api's remote per-request server,
|
|
73
|
+
* so it is a transport signal — not `appsEnabled()`, which is also true for
|
|
74
|
+
* stdio hosts that render widgets but CAN still read local paths.
|
|
75
|
+
*/
|
|
76
|
+
function attachFileInputHints(server, options = {}) {
|
|
77
|
+
const hint = options.apps === true ? REMOTE_FILE_HINT : LOCAL_FILE_HINT;
|
|
78
|
+
const registered = server._registeredTools || {};
|
|
79
|
+
for (const name of FILE_INPUT_TOOLS) {
|
|
80
|
+
const t = registered[name];
|
|
81
|
+
if (t && typeof t.description === 'string' && !t.description.includes(hint)) {
|
|
82
|
+
t.description += hint;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Per-kind upload caps advertised to callers when the ticket endpoint omits them.
|
|
88
|
+
// Mirrors MAX_MB in kolbo-api src/modules/mcpConnector/upload.js.
|
|
89
|
+
const DEFAULT_MAX_FILE_MB = { image: 50, video: 500, audio: 200, document: 50 };
|
|
90
|
+
|
|
33
91
|
function isHttpUrl(s) {
|
|
34
92
|
return typeof s === 'string' && /^https?:\/\//i.test(s);
|
|
35
93
|
}
|
|
@@ -184,13 +242,16 @@ async function resolveToBuffer(source, kind, opts = {}) {
|
|
|
184
242
|
}
|
|
185
243
|
|
|
186
244
|
if (!path.isAbsolute(source)) {
|
|
245
|
+
// `path.isAbsolute` is platform-specific: on a POSIX server (every remote
|
|
246
|
+
// connector deployment) a valid Windows path like `C:\Users\...` or
|
|
247
|
+
// `\\server\share\...` returns false, so "must be absolute" is a lie that
|
|
248
|
+
// sends the caller off retrying slash variants. `path.win32.isAbsolute`
|
|
249
|
+
// answers the same question with Node's own grammar.
|
|
187
250
|
throw new Error(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`when the file IS reachable from where the MCP server runs, then pass the returned https:// URL here. ` +
|
|
193
|
-
`A URL from \`list_media\` also works if the asset is already in the library.`
|
|
251
|
+
(path.win32.isAbsolute(source)
|
|
252
|
+
? `This Kolbo server cannot read files off the calling machine, so the Windows path ${source} is unreachable from here. `
|
|
253
|
+
: `Local file paths must be absolute: ${source}. `) +
|
|
254
|
+
LOCAL_FILE_ROUTING
|
|
194
255
|
);
|
|
195
256
|
}
|
|
196
257
|
let stat;
|
|
@@ -199,11 +260,7 @@ async function resolveToBuffer(source, kind, opts = {}) {
|
|
|
199
260
|
} catch (err) {
|
|
200
261
|
throw new Error(
|
|
201
262
|
`Local file not found or unreadable: ${source}. ` +
|
|
202
|
-
|
|
203
|
-
`DO NOT upload the file yourself with cloud credentials or a shell command — Kolbo has a tool for this. ` +
|
|
204
|
-
`Call \`media_upload_widget\` to have the user pick the file (remote connectors), or \`upload_media\` ` +
|
|
205
|
-
`when the file IS reachable from where the MCP server runs, then pass the returned https:// URL here. ` +
|
|
206
|
-
`A URL from \`list_media\` also works if the asset is already in the library.` +
|
|
263
|
+
LOCAL_FILE_ROUTING +
|
|
207
264
|
(err && err.code ? ` [${err.code}]` : '')
|
|
208
265
|
);
|
|
209
266
|
}
|
|
@@ -498,9 +555,78 @@ async function uiCompleted(p, textPayload) {
|
|
|
498
555
|
return uiResult(UI.generation, textPayload, structured);
|
|
499
556
|
}
|
|
500
557
|
|
|
558
|
+
// ─── Text-payload budget ─────────────────────────────────────────────────────
|
|
559
|
+
//
|
|
560
|
+
// Whatever a tool returns as TEXT is what the model actually reads, and hosts
|
|
561
|
+
// reject or spill-to-disk anything much past this. A list tool that dumps every
|
|
562
|
+
// field of every row blows it instantly: list_presets measured 632,919 chars,
|
|
563
|
+
// get_stock_categories 257,817, list_voices 190,286 — all pretty-printed with
|
|
564
|
+
// `JSON.stringify(x, null, 2)` and no cap. The widget path already slims rows to
|
|
565
|
+
// id/title/thumbnail; the text path has to do the same or the tool is unusable
|
|
566
|
+
// on exactly the text hosts (Claude Code, Cursor, Codex) that depend on it.
|
|
567
|
+
const MAX_TEXT_CHARS = 20000;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Build a compact text payload for a list-shaped result.
|
|
571
|
+
*
|
|
572
|
+
* @param {object[]} items rows from the API
|
|
573
|
+
* @param {object} opts
|
|
574
|
+
* @param {string[]} opts.fields keys to keep per row, in order (others dropped)
|
|
575
|
+
* @param {number} [opts.cap] max rows to include (default 50)
|
|
576
|
+
* @param {number} [opts.total] true total, so the model knows more exist
|
|
577
|
+
* @param {object} [opts.extra] extra top-level keys to merge in
|
|
578
|
+
* @param {string} [opts.note] guidance on how to fetch the rest
|
|
579
|
+
*/
|
|
580
|
+
function compactList(items, { fields, cap = 50, total, extra, note } = {}) {
|
|
581
|
+
const rows = Array.isArray(items) ? items : [];
|
|
582
|
+
const kept = rows.slice(0, cap).map((row) => {
|
|
583
|
+
if (!row || typeof row !== 'object') return row;
|
|
584
|
+
const out = {};
|
|
585
|
+
for (const f of fields || Object.keys(row)) {
|
|
586
|
+
// Drop empties — a row of nulls costs tokens and tells the model nothing.
|
|
587
|
+
if (row[f] !== undefined && row[f] !== null && row[f] !== '') out[f] = row[f];
|
|
588
|
+
}
|
|
589
|
+
return out;
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const payload = { ...(extra || {}), items: kept, count: kept.length };
|
|
593
|
+
if (total != null) payload.total = total;
|
|
594
|
+
const omitted = Math.max(rows.length - kept.length, 0);
|
|
595
|
+
if (omitted > 0 || (total != null && total > kept.length)) {
|
|
596
|
+
payload._truncated = {
|
|
597
|
+
omitted_from_this_page: omitted,
|
|
598
|
+
hint: note || 'Narrow with the tool\'s filter args, or page for more.',
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
let text = JSON.stringify(payload);
|
|
603
|
+
if (text.length > MAX_TEXT_CHARS) {
|
|
604
|
+
// Still too big even trimmed (very long descriptions). Halve until it fits
|
|
605
|
+
// rather than returning something the host will truncate at a random byte.
|
|
606
|
+
let n = kept.length;
|
|
607
|
+
while (n > 1 && text.length > MAX_TEXT_CHARS) {
|
|
608
|
+
n = Math.floor(n / 2);
|
|
609
|
+
payload.items = kept.slice(0, n);
|
|
610
|
+
payload.count = n;
|
|
611
|
+
payload._truncated = {
|
|
612
|
+
omitted_from_this_page: rows.length - n,
|
|
613
|
+
hint: note || 'Result was too large for one response; narrow with filter args.',
|
|
614
|
+
};
|
|
615
|
+
text = JSON.stringify(payload);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return text;
|
|
619
|
+
}
|
|
620
|
+
|
|
501
621
|
module.exports = {
|
|
502
622
|
MAX_FILE_BYTES,
|
|
623
|
+
MAX_TEXT_CHARS,
|
|
624
|
+
compactList,
|
|
503
625
|
VISUAL_DNA_MAX_BYTES,
|
|
626
|
+
LOCAL_FILE_ROUTING,
|
|
627
|
+
FILE_INPUT_TOOLS,
|
|
628
|
+
attachFileInputHints,
|
|
629
|
+
DEFAULT_MAX_FILE_MB,
|
|
504
630
|
isHttpUrl,
|
|
505
631
|
assertSafeUrl,
|
|
506
632
|
safeFetch,
|
package/src/tools/artifacts.js
CHANGED
|
@@ -4,26 +4,44 @@
|
|
|
4
4
|
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
5
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
|
+
const { resolveToBuffer } = require('./_shared');
|
|
8
|
+
|
|
9
|
+
// An HTML/SVG/Mermaid document is text — cap well below the media limit.
|
|
10
|
+
const MAX_ARTIFACT_BYTES = 5 * 1024 * 1024;
|
|
7
11
|
|
|
8
12
|
function registerArtifactTools(server, client) {
|
|
9
13
|
// ─── publish_html_artifact ─────────────────────────────────────
|
|
10
14
|
server.tool(
|
|
11
15
|
'publish_html_artifact',
|
|
12
|
-
'Publish an HTML page (or SVG / Mermaid diagram) to kolbo.ai and return a public shareable URL. Use this when the user explicitly asks to share, publish, or deploy a built artifact so they can send the URL to someone. The content is hosted at https://sites.kolbo.ai/<slug>; the page is served with restrictive CSP (no fetch/XHR/form-action) so it cannot exfiltrate data. Identical content uploaded twice returns the same URL (server dedup). To update a previously-published page in place (keeping the same URL), pass the `share_token` returned from the prior publish — the old content is preserved in version history.',
|
|
16
|
+
'Publish an HTML page (or SVG / Mermaid diagram) to kolbo.ai and return a public shareable URL. Use this when the user explicitly asks to share, publish, or deploy a built artifact so they can send the URL to someone. The content is hosted at https://sites.kolbo.ai/<slug>; the page is served with restrictive CSP (no fetch/XHR/form-action) so it cannot exfiltrate data. Pass `file_path` instead of `content` whenever the artifact already exists as a file — the server reads it, which avoids re-emitting the whole document into the tool call. Identical content uploaded twice returns the same URL (server dedup). To update a previously-published page in place (keeping the same URL), pass the `share_token` returned from the prior publish — the old content is preserved in version history.',
|
|
13
17
|
{
|
|
14
18
|
title: z.string().describe('Human-friendly title for the page (also used to generate the SEO slug). Keep under ~60 chars.'),
|
|
15
|
-
content: z.string().describe('The raw artifact body. For type="html" this is a full HTML document (DOCTYPE + html/head/body). For "svg" it is an <svg> document. For "mermaid" it is the Mermaid source text.'),
|
|
19
|
+
content: z.string().optional().describe('The raw artifact body. For type="html" this is a full HTML document (DOCTYPE + html/head/body). For "svg" it is an <svg> document. For "mermaid" it is the Mermaid source text. Omit this when passing `file_path`.'),
|
|
20
|
+
file_path: z.string().optional().describe('ALTERNATIVE to `content`: an ABSOLUTE local path (or https:// URL) to the artifact file. Strongly preferred for anything sizeable — the server reads the file itself, so you do not have to re-emit the whole document into this tool call. Local paths only work when the file is reachable from where the MCP server runs (local stdio installs), not over a remote connector.'),
|
|
16
21
|
type: z.enum(['html', 'svg', 'mermaid']).optional().describe('Artifact type. Default: "html".'),
|
|
17
22
|
allow_js: z.boolean().optional().describe('Allow inline <script> execution on the published page. Default: false. Required for Tailwind JIT, Chart.js, Three.js, React-from-CDN etc.'),
|
|
18
23
|
share_token: z.string().optional().describe('Optional. Pass the `shareToken` returned from a previous publish to update that artifact in place. The public URL stays the same and the old content is moved into version history. Omit this on the first publish.'),
|
|
19
24
|
},
|
|
20
|
-
async ({ title, content, type, allow_js, share_token }) => {
|
|
25
|
+
async ({ title, content, file_path, type, allow_js, share_token }) => {
|
|
21
26
|
if (!title || !title.trim()) throw new Error('title is required');
|
|
22
|
-
|
|
27
|
+
|
|
28
|
+
const hasContent = typeof content === 'string' && content.length > 0;
|
|
29
|
+
const hasPath = typeof file_path === 'string' && file_path.trim().length > 0;
|
|
30
|
+
if (!hasContent && !hasPath) throw new Error('one of `content` or `file_path` is required');
|
|
31
|
+
if (hasContent && hasPath) throw new Error('pass either `content` or `file_path`, not both');
|
|
32
|
+
|
|
33
|
+
let body_content = content;
|
|
34
|
+
if (hasPath) {
|
|
35
|
+
// resolveToBuffer gives us the absolute-path check, the SSRF guard for
|
|
36
|
+
// https:// sources, and the remote-connector error message for free.
|
|
37
|
+
const { buffer } = await resolveToBuffer(file_path.trim(), 'html', { maxBytes: MAX_ARTIFACT_BYTES });
|
|
38
|
+
body_content = buffer.toString('utf8');
|
|
39
|
+
if (!body_content.trim()) throw new Error(`File is empty: ${file_path}`);
|
|
40
|
+
}
|
|
23
41
|
|
|
24
42
|
const body = {
|
|
25
43
|
title: title.trim(),
|
|
26
|
-
content,
|
|
44
|
+
content: body_content,
|
|
27
45
|
type: type || 'html',
|
|
28
46
|
allowJs: allow_js === true,
|
|
29
47
|
};
|
package/src/tools/chat.js
CHANGED
|
@@ -18,11 +18,11 @@ function registerChatTools(server, client) {
|
|
|
18
18
|
system_prompt: z.string().optional().describe('System prompt for the conversation. Only applied when creating a new session.'),
|
|
19
19
|
web_search: z.boolean().optional().describe('Enable web search for this message. Default: false'),
|
|
20
20
|
deep_think: z.boolean().optional().describe('Enable deep think (extended reasoning). Default: false'),
|
|
21
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
22
|
-
media_urls: z.array(z.string()).optional().describe('Public URLs of images, videos, or audio files to analyze. The model auto-routes to a vision-capable model when media is present.
|
|
21
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
22
|
+
media_urls: z.array(z.string()).optional().describe('Public URLs of images, videos, or audio files to analyze. The model auto-routes to a vision-capable model when media is present. For a local file, get a URL first via the LOCAL FILE route in this tool\'s description.'),
|
|
23
23
|
project_id: projectIdField
|
|
24
24
|
},
|
|
25
|
-
async ({ message, model, session_id, system_prompt, web_search, deep_think, enhance_prompt, media_urls, project_id }) => {
|
|
25
|
+
async ({ message, model, session_id, system_prompt, web_search, deep_think, enhance_prompt = false, media_urls, project_id }) => {
|
|
26
26
|
const gen = await client.post('/v1/chat', {
|
|
27
27
|
message,
|
|
28
28
|
model,
|
package/src/tools/generate.js
CHANGED
|
@@ -54,7 +54,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
54
54
|
prompt: z.string().describe('Text description of the image to generate'),
|
|
55
55
|
model: z.string().optional().describe('Model identifier — REQUIRED in practice: pick a specific model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). Strong current defaults: "nano-banana-2" (versatile, text rendering, multilingual) or "gpt-image-2" (photoreal, infographics). Call list_models type="text_to_img" to see all options and pick per the user\'s intent.'),
|
|
56
56
|
aspect_ratio: z.string().optional().describe('Aspect ratio (e.g., "1:1", "16:9", "9:16"). Must be a value present in the model\'s `supported_aspect_ratios` from list_models — pass an unsupported value and the API rejects. Default: "1:1"'),
|
|
57
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt for better results. Default: true'),
|
|
57
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt for better results. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
58
58
|
num_images: z.number().optional().describe('Number of images to generate in one call. Default: 1. Note: some models (Midjourney etc.) have a fixed `images_per_request` and ignore this — check list_models.'),
|
|
59
59
|
reference_images: z.array(z.string()).optional().describe('STYLE/COMPOSITION inspiration only — does NOT embed reference pixels. Array of image URLs used to guide the look-and-feel of a brand-new generation. The model interprets the references and regenerates approximations conditioned on them. It will NOT copy pixels from these images into the output. **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model — exceeding it is a deterministic 400.** To embed a specific logo, icon, watermark, or asset pixel-accurately, use generate_image_edit with the asset in source_images. To EDIT an existing image, also use generate_image_edit.'),
|
|
60
60
|
visual_dna_ids: z.array(z.string()).optional().describe('Visual DNA profile IDs (from create_visual_dna / list_visual_dnas) for character / style / product / scene consistency. **Cap: pass at most `max_visual_dna` IDs from list_models — if the field is null/0 or `supports_visual_dna: false`, the model rejects DNA entirely (silently ignored in some paths).** How DNA works: the server fetches the DNA\'s reference images AND always injects its `description` field into the prompt as plaintext (by design — independent of enhance_prompt). Practical implication: do NOT also write physical descriptors of the same subject in your own prompt — they will compete with the DNA description text. For pixel-accurate face anchoring of a specific person, prefer passing the DNA\'s reference image directly via source_images on generate_image_edit and OMIT visual_dna_ids. visual_dna_ids is best for style / scene / product DNAs and for soft consistency across a set.'),
|
|
@@ -67,7 +67,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
67
67
|
skip_color_palette: z.boolean().optional().describe('Opt this single call OUT of the account\'s active Color DNA palette (see list_color_palettes / activate_color_palette). By default, if the user has an active palette it strict-grades every generation automatically — pass true only when the user explicitly wants this one image ungraded.'),
|
|
68
68
|
project_id: projectIdField
|
|
69
69
|
},
|
|
70
|
-
async ({ prompt, model, aspect_ratio, enhance_prompt, num_images, reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, preset_id, cinematic, skip_color_palette, project_id }) => {
|
|
70
|
+
async ({ prompt, model, aspect_ratio, enhance_prompt = false, num_images, reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, preset_id, cinematic, skip_color_palette, project_id }) => {
|
|
71
71
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
72
72
|
const gen = await client.post('/v1/generate/image', {
|
|
73
73
|
prompt, model, aspect_ratio, enhance_prompt, num_images,
|
|
@@ -112,7 +112,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
112
112
|
model: z.string().optional().describe('Model identifier — REQUIRED in practice: pick a specific IMAGE-EDITING model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). Strong current defaults: "nano-banana-pro/edit" (best general prompt editor), "gpt-image/1.5-image-to-image" (photoreal), or "flux-2/edit". NOTE: text-to-image ids like "nano-banana-2"/"gpt-image-2" are NOT editors — don\'t use them here. Call list_models type="image_editing" to see all options and pick per the user\'s intent.'),
|
|
113
113
|
source_images: z.array(z.string()).describe('PIXEL-ACCURATE compositing. Array of source image URLs whose pixel content is composited into the output. **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model — exceeding it is a deterministic 400.** Three modes the model auto-detects from input shape: (1) Single image → edit/transform that image. (2) Multiple images, one base + others → composite the others into the base. (3) Multiple images with no clear base → generate a new scene that pixel-accurately embeds the supplied images at positions described in the prompt. Mode 3 is the canonical pattern for thumbnails / branded compositions where exact-pixel logo + face fidelity matter. Refer to source images in the prompt by ordinal position ("FIRST source image", "SECOND source image") or use @image1/@image2 tags. Add "composite AS-IS, do not redraw or restyle" to lock pixels.'),
|
|
114
114
|
aspect_ratio: z.string().optional().describe('Output aspect ratio (e.g., "1:1", "16:9", "9:16"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "1:1"'),
|
|
115
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt for better results. Default: true'),
|
|
115
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt for better results. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
116
116
|
num_images: z.number().optional().describe('Number of output images. Default: 1'),
|
|
117
117
|
visual_dna_ids: z.array(z.string()).optional().describe('Visual DNA profile IDs for character / style / product consistency. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model.** How DNA works: the server fetches the DNA\'s reference images AND always injects its `description` field into the prompt as plaintext (by design — independent of enhance_prompt). For pixel-accurate face anchoring of a specific person on this tool, the PREFERRED pattern is to pass the face photo directly via source_images and OMIT visual_dna_ids — that way the face pixels anchor the output and no description text competes. Do NOT pass visual_dna_ids if source_images already contains the same person\'s face (face averaging). visual_dna_ids is best here for style / product DNAs.'),
|
|
118
118
|
moodboard_id: z.string().optional().describe('Moodboard ID whose master_prompt and style_guide should be applied.'),
|
|
@@ -122,7 +122,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
122
122
|
skip_color_palette: z.boolean().optional().describe('Opt this single call OUT of the account\'s active Color DNA palette (see list_color_palettes / activate_color_palette). By default, if the user has an active palette it strict-grades every generation automatically — pass true only when the user explicitly wants this one edit ungraded.'),
|
|
123
123
|
project_id: projectIdField
|
|
124
124
|
},
|
|
125
|
-
async ({ prompt, model, source_images, aspect_ratio, enhance_prompt, num_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, cinematic, skip_color_palette, project_id }) => {
|
|
125
|
+
async ({ prompt, model, source_images, aspect_ratio, enhance_prompt = false, num_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, cinematic, skip_color_palette, project_id }) => {
|
|
126
126
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
127
127
|
const gen = await client.post('/v1/generate/image-edit', {
|
|
128
128
|
prompt, model, source_images, aspect_ratio, enhance_prompt, num_images,
|
|
@@ -173,7 +173,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
173
173
|
aspect_ratio: z.string().optional().describe('Aspect ratio applied to every scene (e.g., "1:1", "16:9", "9:16"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "1:1"'),
|
|
174
174
|
workflow_type: z.string().optional().describe('"image" (default) or "video"'),
|
|
175
175
|
duration: z.number().optional().describe('Duration in seconds per scene (video mode only). Must be a value in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. E.g., 5 or 10.'),
|
|
176
|
-
enhance_prompt: z.boolean().optional().describe('Enhance prompts per scene. Default: true'),
|
|
176
|
+
enhance_prompt: z.boolean().optional().describe('Enhance prompts per scene. Default: false — only pass true if the user explicitly asks to enhance/improve the prompts.'),
|
|
177
177
|
reference_images: z.array(z.string()).optional().describe('Array of reference image URLs to guide style/composition of every scene. **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model.**'),
|
|
178
178
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply consistently across every scene. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model.** This is the ideal way to keep a character or product looking the same in all scenes of a campaign.'),
|
|
179
179
|
moodboard_id: z.string().optional().describe('A single moodboard ID whose master_prompt and style_guide should shape every scene.'),
|
|
@@ -181,7 +181,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
181
181
|
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.'),
|
|
182
182
|
project_id: projectIdField
|
|
183
183
|
},
|
|
184
|
-
async ({ prompt, scene_count, model, aspect_ratio, workflow_type, duration, enhance_prompt, reference_images, visual_dna_ids, moodboard_id, moodboard_ids, resolution, project_id }) => {
|
|
184
|
+
async ({ prompt, scene_count, model, aspect_ratio, workflow_type, duration, enhance_prompt = false, reference_images, visual_dna_ids, moodboard_id, moodboard_ids, resolution, project_id }) => {
|
|
185
185
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
186
186
|
const gen = await client.post('/v1/generate/creative-director', {
|
|
187
187
|
prompt, scene_count, model, aspect_ratio, workflow_type, duration,
|
|
@@ -324,7 +324,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
324
324
|
model: z.string().optional().describe('Model identifier — pick a SPECIFIC model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). Strong current defaults: "seedance-2" (versatile) or "veo3" (Veo 3.1, cinematic + native audio); the Kling family (call list_models for exact ids like kling-video/v3/pro/text-to-video) is strongest for motion. Call list_models type="text_to_video" to see all options + check supported_durations / supported_aspect_ratios, and choose per the user\'s intent.'),
|
|
325
325
|
aspect_ratio: z.string().optional().describe('Aspect ratio (e.g., "16:9", "9:16", "1:1"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "16:9"'),
|
|
326
326
|
duration: z.number().optional().describe('Duration in seconds. Must be a value in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration` (whichever the model exposes). Default: 5'),
|
|
327
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
327
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
328
328
|
reference_images: z.array(z.string()).optional().describe('Array of image URLs used as visual references (style / composition / subject). **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model — exceeding it is a deterministic 400.**'),
|
|
329
329
|
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 resolution_multipliers to predict cost.'),
|
|
330
330
|
preset_id: z.string().optional().describe('Preset ID from list_presets type="video" to apply a saved motion/style preset to this generation.'),
|
|
@@ -332,7 +332,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
332
332
|
skip_color_palette: z.boolean().optional().describe('Opt this single call OUT of the account\'s active Color DNA palette (see list_color_palettes / activate_color_palette). By default, if the user has an active palette it strict-grades every generation automatically — pass true only when the user explicitly wants this one video ungraded.'),
|
|
333
333
|
project_id: projectIdField
|
|
334
334
|
},
|
|
335
|
-
async ({ prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, preset_id, sound_enabled, skip_color_palette, project_id }) => {
|
|
335
|
+
async ({ prompt, model, aspect_ratio, duration, enhance_prompt = false, reference_images, resolution, preset_id, sound_enabled, skip_color_palette, project_id }) => {
|
|
336
336
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
337
337
|
const gen = await client.post('/v1/generate/video', {
|
|
338
338
|
prompt, model, aspect_ratio, duration, enhance_prompt, reference_images, resolution, preset_id, sound_enabled, skip_color_palette, project_id
|
|
@@ -381,14 +381,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
381
381
|
model: z.string().optional().describe('Model identifier — pick a SPECIFIC model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). Strong current defaults: "seedance-2" (versatile) or "veo3" (Veo 3.1, cinematic + native audio); the Kling family (call list_models for exact ids like kling-video/v3/pro/image-to-video) is strongest for motion. Call list_models type="img_to_video" to see all options and choose per the user\'s intent.'),
|
|
382
382
|
aspect_ratio: z.string().optional().describe('Output aspect ratio (e.g., "16:9", "9:16", "1:1"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "16:9"'),
|
|
383
383
|
duration: z.number().optional().describe('Duration in seconds. Must be in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. Default: 5'),
|
|
384
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the motion prompt. Default: true'),
|
|
384
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the motion prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
385
385
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to maintain consistency with prior characters / styles. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model; if `supports_visual_dna: false` the model ignores DNA entirely.**'),
|
|
386
386
|
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.'),
|
|
387
387
|
sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Only honored by models with `sound_generation_type: "native"` from list_models (e.g. Veo 3.1 Lite, Kling V3 4K, PixVerse V6, Kling 2.6/v3). On `sound_generation_type: "none"` models the flag has no effect. Omit to use the model\'s `sound_enabled_by_default`. Pass `false` when the user says no sound / silent / mute / without audio. Enabling sound may apply `sound_credit_multiplier` to cost.'),
|
|
388
388
|
skip_color_palette: z.boolean().optional().describe('Opt this single call OUT of the account\'s active Color DNA palette (see list_color_palettes / activate_color_palette). By default, if the user has an active palette it strict-grades every generation automatically — pass true only when the user explicitly wants this one video ungraded.'),
|
|
389
389
|
project_id: projectIdField
|
|
390
390
|
},
|
|
391
|
-
async ({ image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, sound_enabled, skip_color_palette, project_id }) => {
|
|
391
|
+
async ({ image_url, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, resolution, sound_enabled, skip_color_palette, project_id }) => {
|
|
392
392
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
393
393
|
const gen = await client.post('/v1/generate/video/from-image', {
|
|
394
394
|
image_url, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, sound_enabled, skip_color_palette, project_id
|
|
@@ -439,7 +439,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
439
439
|
vocal_gender: z.string().optional().describe('Preferred vocal gender: "male" or "female". Only applies when instrumental is false.'),
|
|
440
440
|
negative_tags: z.string().optional().describe('Styles / sounds to EXCLUDE, comma-separated (e.g. "heavy metal, screaming, distortion"). Suno.'),
|
|
441
441
|
duration_seconds: z.number().optional().describe('Target song length in seconds (length-capable models like ElevenLabs Music). Clamped 5–300. Omit for the model default.'),
|
|
442
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
442
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
443
443
|
preset_id: z.string().optional().describe('Preset ID from list_presets type="music" to apply a saved music style preset.'),
|
|
444
444
|
// ── Suno fine controls ──
|
|
445
445
|
style_weight: z.number().optional().describe('Suno: how strongly the style/genre is applied, 0–1.'),
|
|
@@ -451,7 +451,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
451
451
|
singing_voice_id: z.string().optional().describe('Custom cloned singing-voice id (must be owned by the caller).'),
|
|
452
452
|
project_id: projectIdField
|
|
453
453
|
},
|
|
454
|
-
async ({ prompt, model, style, title, instrumental, lyrics, vocal_gender, negative_tags, duration_seconds, enhance_prompt, preset_id, style_weight, weirdness, audio_weight, persona_id, use_composition_plan, singing_dna_id, singing_voice_id, project_id }) => {
|
|
454
|
+
async ({ prompt, model, style, title, instrumental, lyrics, vocal_gender, negative_tags, duration_seconds, enhance_prompt = false, preset_id, style_weight, weirdness, audio_weight, persona_id, use_composition_plan, singing_dna_id, singing_voice_id, project_id }) => {
|
|
455
455
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
456
456
|
const gen = await client.post('/v1/generate/music', {
|
|
457
457
|
prompt, model, style, title, instrumental, lyrics, vocal_gender, negative_tags,
|
|
@@ -757,18 +757,19 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
757
757
|
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 elements_max_images / elements_max_videos / elements_max_audio on the model. Pick a SPECIFIC model — do NOT omit (omitting = Smart Select auto-pick, which we avoid); call list_models for this type and choose the model that best fits the user\'s intent.'),
|
|
758
758
|
reference_images: z.array(z.string()).optional().describe('Array of public image URLs used as reference elements (product shots, character references, etc.). **Cap: pass at most `elements_max_images` URLs from list_models for the chosen model — exceeding it is a deterministic 400.**'),
|
|
759
759
|
reference_videos: z.array(z.string()).optional().describe('Array of reference video URLs for models that accept video inputs. **Cap: pass at most `elements_max_videos` URLs from list_models — if the cap is 0 the model rejects videos.**'),
|
|
760
|
+
reference_audio_urls: z.array(z.string()).optional().describe('Array of reference audio URLs for models that accept audio inputs. **Cap: pass at most `elements_max_audio` URLs from list_models.** `audio_url` remains supported as the legacy single-track form.'),
|
|
760
761
|
audio_url: z.string().optional().describe('URL of a reference audio track. **Audio constraints: `elements_max_audio` from list_models gates whether audio is accepted at all; audio duration must fall within `min_audio_duration`-`max_audio_duration`; format must be in `supported_audio_formats` (if specified).**'),
|
|
761
762
|
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. **Total count across files + reference_images still capped by `elements_max_images`.**'),
|
|
762
763
|
duration: z.number().optional().describe('Output duration in seconds. Must be in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. Default: 5'),
|
|
763
764
|
aspect_ratio: z.string().optional().describe('Aspect ratio (e.g., "16:9", "9:16", "1:1"). Must be in `supported_aspect_ratios` from list_models. Default: "16:9"'),
|
|
764
765
|
motion: z.string().optional().describe('Motion style / intensity hint (optional)'),
|
|
765
766
|
preset_id: z.string().optional().describe('Preset ID from list_presets type="video" (optional)'),
|
|
766
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
767
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
767
768
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency across outputs. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model.**'),
|
|
768
769
|
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
|
|
769
770
|
project_id: projectIdField
|
|
770
771
|
},
|
|
771
|
-
async ({ prompt, model, reference_images, reference_videos, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, project_id }) => {
|
|
772
|
+
async ({ prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt = false, visual_dna_ids, resolution, project_id }) => {
|
|
772
773
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
773
774
|
if (!prompt) throw new Error('prompt is required');
|
|
774
775
|
|
|
@@ -787,6 +788,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
787
788
|
if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
|
|
788
789
|
if (reference_images) form.append('reference_images', JSON.stringify(reference_images));
|
|
789
790
|
if (reference_videos) form.append('reference_videos', JSON.stringify(reference_videos));
|
|
791
|
+
if (reference_audio_urls) form.append('reference_audio_urls', JSON.stringify(reference_audio_urls));
|
|
790
792
|
if (audio_url) form.append('audio_url', audio_url);
|
|
791
793
|
if (resolution) form.append('resolution', resolution);
|
|
792
794
|
if (project_id) form.append('project_id', project_id);
|
|
@@ -797,7 +799,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
797
799
|
} else {
|
|
798
800
|
// URL-only mode: plain JSON.
|
|
799
801
|
startResponse = await client.post('/v1/generate/elements', {
|
|
800
|
-
prompt, model, reference_images, reference_videos, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, project_id
|
|
802
|
+
prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, project_id
|
|
801
803
|
});
|
|
802
804
|
}
|
|
803
805
|
|
|
@@ -842,12 +844,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
842
844
|
model: z.string().optional().describe('Model identifier. Use list_models type="firstlastgenerations" to see options. Pick a SPECIFIC model — do NOT omit (omitting = Smart Select auto-pick, which we avoid); call list_models for this type and choose the model that best fits the user\'s intent.'),
|
|
843
845
|
duration: z.number().optional().describe('Duration in seconds. Must be in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. Default: 5'),
|
|
844
846
|
aspect_ratio: z.string().optional().describe('Aspect ratio (auto-detected from first frame if not provided). Must be in `supported_aspect_ratios` from list_models when set. Default: "16:9"'),
|
|
845
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
847
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
846
848
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model; if `supports_visual_dna: false`, DNA is silently ignored.**'),
|
|
847
849
|
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
|
|
848
850
|
project_id: projectIdField
|
|
849
851
|
},
|
|
850
|
-
async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt, visual_dna_ids, resolution, project_id }) => {
|
|
852
|
+
async ({ first_frame_url, last_frame_url, first_frame, last_frame, prompt, model, duration, aspect_ratio, enhance_prompt = false, visual_dna_ids, resolution, project_id }) => {
|
|
851
853
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
852
854
|
const urlMode = first_frame_url && last_frame_url;
|
|
853
855
|
const fileMode = first_frame && last_frame;
|
|
@@ -1032,7 +1034,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1032
1034
|
model: z.string().optional().describe('Model identifier. Use list_models type="video_to_video" to see options and check max_images / max_videos / max_elements / max_video_duration per model. Pick a SPECIFIC model — do NOT omit (omitting = Smart Select auto-pick, which we avoid); call list_models for this type and choose the model that best fits the user\'s intent.'),
|
|
1033
1035
|
aspect_ratio: z.string().optional().describe('Output aspect ratio. Must be in `supported_aspect_ratios` from list_models when set. Default: matches source'),
|
|
1034
1036
|
duration: z.number().optional().describe('Output duration in seconds. Must be in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. Default: matches source'),
|
|
1035
|
-
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
1037
|
+
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
|
|
1036
1038
|
visual_dna_ids: z.array(z.string()).optional().describe('Array of Visual DNA profile IDs to apply for character/style consistency. **Cap: pass at most `max_visual_dna` IDs from list_models for the chosen model; if `supports_visual_dna: false`, DNA is silently ignored.**'),
|
|
1037
1039
|
resolution: z.string().optional().describe('Video resolution tier (vertical pixels): "720p" / "1080p" / "1440p" / "2160p". Model-dependent — call list_models and read supported_resolutions.'),
|
|
1038
1040
|
reference_images: z.array(z.string()).optional().describe('Array of reference image URLs for models that support additional image inputs. **Cap: pass at most `max_images` URLs from list_models — if `max_images === 0` the model does not accept image refs.** Examples: character reference images for Kling O1/O3, style reference for Aleph/gen4_aleph, character image for WAN VACE video-edit.'),
|
|
@@ -1058,7 +1060,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1058
1060
|
}).optional().describe('VEED Subtitles only: style overrides. Any omitted field keeps the preset default. Best supported by Basic presets.'),
|
|
1059
1061
|
project_id: projectIdField
|
|
1060
1062
|
},
|
|
1061
|
-
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, project_id }) => {
|
|
1063
|
+
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, resolution, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, project_id }) => {
|
|
1062
1064
|
model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
1063
1065
|
if (!source_video) throw new Error('source_video is required');
|
|
1064
1066
|
|