@kolbo/mcp 1.70.2 → 1.70.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.70.2",
3
+ "version": "1.70.4",
4
4
  "description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  # AUTO-GENERATED — do not edit
2
2
 
3
- This tree is mirrored from kolbo-code@0fbb0ce, the single source of truth.
3
+ This tree is mirrored from kolbo-code@9e4903a, the single source of truth.
4
4
  Canonical source: packages/opencode/skills/kolbo/
5
5
  Distribution: .github/workflows/sync-skill-to-plugin.yml
6
6
 
package/skill/SKILL.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- version: 0.8.1
2
+ version: 0.8.2
3
3
  name: kolbo
4
4
  description: |
5
5
  Generate, edit, analyze, and direct creative media through Kolbo AI: images,
@@ -155,10 +155,11 @@ A user-named tool — in any language — overrides every other rule. Recognized
155
155
  **Preset contract:** if the user asks for a preset, names one, or says to use one of their/Kolbo presets, call `list_presets` with the matching type before generation and pass the selected exact `id` as `preset_id`. Use `image` for `generate_image` and `image_edit` for `generate_image_edit`. Never invent an id or silently continue without the requested preset.
156
156
 
157
157
  1. **Check credits** ONCE per conversation (Step 0). Skip if already checked.
158
- 2. **Discover models** with `list_models` using a `type` filter — but **skip when the user names a specific model**.
158
+ 2. **Discover models** with `list_models` using a `type` filter — but **skip when the user names a specific model** (this turn **or** earlier in the conversation / compaction `## Locked choices`).
159
159
  3. **Pick the model**:
160
- - User named one → use it. Model identifiers resolve leniently — shorthand like `"z-image"` or `"nano banana 2"` auto-resolves to the exact identifier, so don't over-engineer exact-id lookups (`list_models` is still authoritative for constraints, caps, and pricing).
161
- - Auto-select only from "Auto-selectable" section (models with a `summary`). Cheapest fit. Prefer `[RECOMMENDED]` when cost is similar.
160
+ - User named one → that name is a **family lock**, not a single catalog row. Use it. Identifiers resolve leniently — `"z-image"` / `"nano banana 2"` / `"grok imagine"` auto-resolve, including to the sibling for the tool you are calling (`grok-imagine-text-to-video` on `generate_video_from_image` becomes `grok-imagine-image-to-video`). `list_models` is still authoritative for constraints, caps, and pricing — not for swapping brands.
161
+ - **Never cheapest-swap a named family.** After compaction, "animate those images" is still Grok if the user said Grok. Seedance / Kling / Veo are not a "best balance" substitute. If the named family has no variant for this modality, ASK — do not silently switch.
162
+ - Auto-select → **only when no model was named on this task**. Then pick from "Auto-selectable" (models with a `summary`). Cheapest fit. Prefer `[RECOMMENDED]` when cost is similar.
162
163
  - Never auto-select from "Named-only" section.
163
164
  4. **Validate inputs** against model caps — see `references/workflows/cost-and-validation.md`.
164
165
  5. **How calls work**: each tool blocks until generation is fully complete. Images: seconds. Video: minutes. Multiple tool calls in one response run concurrently. On hosts with live widgets the tool instead returns `submitted` instantly — the card updates on its own; you only need `get_generation_status` when a follow-up step needs the output URLs.
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.1
1
+ 0.8.2
@@ -4,7 +4,7 @@ Load this file when starting a multi-step production, or before any continuation
4
4
 
5
5
  ## Why It Exists
6
6
 
7
- Every URL, id, and brief produced by a Kolbo MCP tool MUST be recorded in `.kolbo/production.md` in the user's workspace. This file — not chat history — is your source of truth for prior artifacts: URLs scattered across `tool_result` blobs are unreliable to re-scan and disappear entirely on context compaction.
7
+ Every URL, id, and brief produced by a Kolbo MCP tool MUST be recorded in `.kolbo/production.md` in the user's workspace. This file — not chat history — is your source of truth for prior artifacts: URLs scattered across `tool_result` blobs are unreliable to re-scan and disappear entirely on context compaction. If the user named a model, write that name into `## 🎯 Now` and keep using that family on every follow-up — compaction is not permission to cheapest-swap.
8
8
 
9
9
  ## When to READ it
10
10
 
package/src/apps/index.js CHANGED
@@ -321,6 +321,26 @@ function pickForType(candidates, types) {
321
321
  return pool.reduce((a, b) => (b.id.length < a.id.length ? b : a)).id;
322
322
  }
323
323
 
324
+ // Strip modality tokens so a t2v id and its i2v sibling share one family key
325
+ // (grok-imagine-text-to-video ↔ grok-imagine-image-to-video; kling …/text-to-video
326
+ // ↔ …/image-to-video). Version tokens stay (1.5 ≠ 1.0).
327
+ function fam(s) {
328
+ return normId(s).replace(
329
+ /texttovideo|imagetovideo|imgtovideo|texttoimage|imagetoimage|imageediting|imageedit|referencetovideo|videotovideo|videoedit|editvideo|firstlastframe|firstlast/g,
330
+ '',
331
+ );
332
+ }
333
+
334
+ function sibling(models, hit, types) {
335
+ if (!hit || !types.length) return hit;
336
+ const row = models.find((i) => i.id === hit);
337
+ if (row && row.types.some((t) => types.includes(t))) return hit;
338
+ const key = fam(hit);
339
+ const sibs = models.filter((i) => fam(i.id) === key && i.types.some((t) => types.includes(t)));
340
+ if (!sibs.length) return hit;
341
+ return sibs.reduce((a, b) => (b.id.length < a.id.length ? b : a)).id;
342
+ }
343
+
324
344
  /**
325
345
  * Lenient model-identifier resolution for LLM-supplied model args.
326
346
  * Users say "z-image"; the real identifier is "z-image/turbo" — the backend
@@ -334,6 +354,9 @@ function pickForType(candidates, types) {
334
354
  * without it, "Kling 2.6 Pro" from generate_video_from_image resolved to
335
355
  * kling-video/v2.6/pro/text-to-video (2026-08-10), so the image-to-video
336
356
  * pipeline submitted the TEXT-to-video endpoint and billed against it.
357
+ * An explicit t2v identifier on an i2v tool remaps to the unique same-family
358
+ * sibling (grok-imagine-text-to-video → grok-imagine-image-to-video). No
359
+ * sibling → the id is passed through unchanged (MiniMax H3).
337
360
  *
338
361
  * Still unresolved: throw with the near misses named. The API answers a bad
339
362
  * identifier with a bare INVALID_*_MODEL and no hint, which on 2026-08-09 sent
@@ -362,13 +385,13 @@ async function canonicalModelId(client, input, type) {
362
385
  const dashed = key.replace(/\s+/g, '-');
363
386
 
364
387
  // 1. exact name / identifier hit
365
- const exact = pickForType(models.filter((i) => [i.id, i.name].some(
388
+ const exact = sibling(models, pickForType(models.filter((i) => [i.id, i.name].some(
366
389
  (k) => k && (k.toLowerCase() === key || k.toLowerCase() === dashed)
367
- )), types);
390
+ )), types), types);
368
391
  if (exact) return exact;
369
392
 
370
393
  // 2. separator-insensitive exact ("flux-2-flash" → "flux-2/flash")
371
- const loose = pickForType(models.filter((i) => normId(i.id) === want || normId(i.name) === want), types);
394
+ const loose = sibling(models, pickForType(models.filter((i) => normId(i.id) === want || normId(i.name) === want), types), types);
372
395
  if (loose) return loose;
373
396
 
374
397
  // 3. unique prefix ("z-image" → "z-image/turbo") — the modality filter runs
package/src/index.js CHANGED
@@ -76,8 +76,8 @@ const { registerReviewTools } = require('./tools/review');
76
76
  const { registerVoiceTools } = require('./tools/voices');
77
77
  const { registerMusicLibraryTools } = require('./tools/music_library');
78
78
  const { registerStockLibraryTools } = require('./tools/stock_library');
79
- const { registerApps, attachToolWidgetMeta } = require('./apps');
80
- const { attachToolAnnotations } = require('./toolAnnotations');
79
+ const { registerApps, attachToolWidgetMeta } = require('./apps');
80
+ const { attachToolAnnotations } = require('./toolAnnotations');
81
81
 
82
82
  /**
83
83
  * Build a fully-configured Kolbo MCP server (all tool groups registered)
@@ -126,9 +126,9 @@ function createServer(opts = {}) {
126
126
  '6. TIMEOUT HANDLING (applies to EVERY generate_* / edit_* / chat_send_message / transcribe_audio tool): each tool blocks and polls internally, then gives up after its own window if the job is not yet terminal. A timeout is NOT a failure — it returns `_timed_out:true` with the `generation_id` (not an error), because the job is almost always STILL RUNNING on the server (or already finished). Call `get_generation_status` with that `generation_id` and `wait=true` to keep checking until state="completed". NEVER conclude the generation failed and re-run the same tool from scratch after a `_timed_out:true` result — that wastes the user\'s credits by paying twice. DIRECTOR / BATCH JOBS follow the same convention through a dedicated tool: generate_creative_director runs its scenes (image OR video) in parallel and only reports state="completed" once EVERY scene is terminal; on `_timed_out:true` call `get_creative_director_status` (not get_generation_status) with the returned generation_id and keep checking. If scenes already carry image_urls/video_urls, they are done; do not regenerate.',
127
127
  '7. SESSION CONTINUITY — one task, one session, always: every generation tool returns a `session_id`. For ANY follow-up, refinement, retry, or next step on the SAME task, pass that session_id back — never start fresh. BATCH RULE (critical): when a single user request produces multiple parallel generations (e.g. "animate these 5 images", "generate 3 variants"), do NOT launch them all at once without a session_id. Instead: (1) run the FIRST generation without session_id to create the session, (2) capture the session_id from its response, (3) pass that session_id to ALL remaining generations in the batch. This keeps the entire batch in one session. Exception: only omit session_id and start fresh when the user explicitly starts an unrelated new task.',
128
128
  '8. LOCAL FILES / REFERENCE MEDIA — HOW TO HANDLE EVERY CASE. (A) User has a LOCAL file (audio, video, image, document) on their machine. What matters is WHERE THIS SERVER RUNS, not what your client can do — your own filesystem access is irrelevant if the server is somewhere else. On a LOCAL stdio install (server and client share a machine) → call `upload_media` with the absolute path, or pass the path straight to tools like `transcribe_audio` that accept local paths. Over a REMOTE connector the server cannot see that path no matter how capable you are, so a local path will always fail: if you can run shell commands or issue HTTP requests → call `create_upload_ticket` and POST the file to the returned upload_url yourself (fastest, no user interaction); if you cannot → call `media_upload_widget` IMMEDIATELY, the user uploads, and a `media.kolbo.ai` CDN URL comes back for any follow-up call. (B) You already have a public URL (media.kolbo.ai, any CDN, any direct link) → pass it directly; all Kolbo tools accept public URLs. NEVER search for DO Spaces keys, DigitalOcean credentials, or server-side upload credentials. NEVER ask the user to put the file on Google Drive, Dropbox, or Loom. NEVER invent or guess a URL. NEVER base64 anything but a tiny file — it costs context in proportion to file size; use the ticket or the widget instead.',
129
- '9. MODEL SELECTION — ROUTE BY THE STRENGTHS SUMMARY, NEVER BY THE BADGE OR THE PRICE TAG: ALWAYS pass a specific `model` on every generation tool — do NOT omit it (omitting falls back to "Smart Select" auto-routing, which hides the choice from the user; use it ONLY if the user explicitly asks you to auto-pick). To choose: call `list_models` with the matching `type` and read each model\'s STRENGTHS SUMMARY — the "— …" clause printed after the credit cost. That summary IS the routing instruction: match it against what the user actually asked for (subject, style, motion, length, quality bar, speed), then pick the CHEAPEST model whose summary covers the task. `[NEW]` and `[RECOMMENDED]` badges, a high credit number, and "flagship"/"most intelligent" wording are NOT selection signals — never pick a model because it is newest, biggest or most expensive. Escalate to a premium/frontier model only when the user explicitly asks for maximum quality, or when no cheaper summary covers the requirement. Models printed under "Named-only" (no summary) are opt-in: use them only when the user names them. TEXT/CHAT: `chat_send_message` bills PER TOKEN, so the listed credit number is not the cost — a frontier text model (Claude Fable 5, GPT-5.6 Sol, Pro-class) costs 5-30x a mid-tier one per reply. Default ordinary chat (writing, brainstorming, Q&A, summarising) to a balanced mid-tier model and reserve the frontier tier for hard reasoning or long-form code the user asked for.',
130
- '10. IMAGE EDITING: for ANY prompt-driven / content edit of an existing image — "make it night", changing scene/lighting/colors, adding/removing/replacing objects, restyling — use `generate_image_edit`. Auto-pick only Nano Banana 2 (`nano-banana-2` / `nano-banana-2-image-editing`) or GPT Image 2 (`gpt-image-2` / `gpt-image-2/edit`) for photoreal photo edits, object removal, keep-subject/remove-others, or crowd cleanup. Do NOT auto-pick Flux 2 / flux-2/edit / Flux Klein — those are generate-from-scratch / style, named-only for editing. Do NOT use `edit_image` for content edits — `edit_image` is ONLY for mechanical enhancements (upscale, expand/outpaint, remove-background, skin retouch). Its `magic_edit` operation is deprecated in favor of `generate_image_edit`. EXPANDING AN IMAGE: to widen/extend/uncrop an image or fit it into a wider frame while KEEPING the existing artwork, use `edit_image` with operation="zoom_out" (outpainting — original pixels preserved; size it with `zoom_out_percentage` or the `expand_left/right/top/bottom` pixel args). The "reframe" operation is NOT this: it re-generates the whole picture at a new aspect ratio and the subject comes back re-imagined. Only pick "reframe" when the user wants the shot re-taken, never when they want their image extended.',
131
- '11. PRESET CONTRACT: if the user asks for a preset, names a preset, or says to use one of their/Kolbo presets, you MUST call `list_presets` with the matching type before generation, resolve the named or closest matching preset, and pass its exact returned `id` as `preset_id`. Use type="image" for generate_image and type="image_edit" for generate_image_edit. Never silently ignore a preset request, never invent an id, and never claim a preset was applied unless `preset_id` was present in the generation call.'
129
+ '9. MODEL SELECTION — NAMED MODEL WINS, THEN STRENGTHS SUMMARY: ALWAYS pass a specific `model` on every generation tool — do NOT omit it (omitting falls back to "Smart Select" auto-routing, which hides the choice from the user; use it ONLY if the user explicitly asks you to auto-pick). If the user named a model this turn OR earlier in the conversation (including a compaction "Locked choices" / summary), that name is a FAMILY LOCK: pass it (or its display name) on every follow-up, including when the tool changes (text-to-video → image-to-video). Identifier resolution remaps a t2v id to the family\'s i2v sibling automatically. NEVER substitute a different brand because it is cheaper, faster, or "best balance" (Grok Imagine named → do not fire Seedance). If the named family has no variant for this modality, ASK — do not silently switch. Cheapest-summary routing applies ONLY when no model was named on this task: call `list_models` with the matching `type` and read each model\'s STRENGTHS SUMMARY — the "— …" clause printed after the credit cost then pick the CHEAPEST model whose summary covers the task. `[NEW]` and `[RECOMMENDED]` badges, a high credit number, and "flagship"/"most intelligent" wording are NOT selection signals — never pick a model because it is newest, biggest or most expensive. Escalate to a premium/frontier model only when the user explicitly asks for maximum quality, or when no cheaper summary covers the requirement. Models printed under "Named-only" (no summary) are opt-in: use them only when the user names them. TEXT/CHAT: `chat_send_message` bills PER TOKEN, so the listed credit number is not the cost — a frontier text model (Claude Fable 5, GPT-5.6 Sol, Pro-class) costs 5-30x a mid-tier one per reply. Default ordinary chat (writing, brainstorming, Q&A, summarising) to a balanced mid-tier model and reserve the frontier tier for hard reasoning or long-form code the user asked for.',
130
+ '10. IMAGE EDITING: for ANY prompt-driven / content edit of an existing image — "make it night", changing scene/lighting/colors, adding/removing/replacing objects, restyling — use `generate_image_edit`. Auto-pick only Nano Banana 2 (`nano-banana-2` / `nano-banana-2-image-editing`) or GPT Image 2 (`gpt-image-2` / `gpt-image-2/edit`) for photoreal photo edits, object removal, keep-subject/remove-others, or crowd cleanup. Do NOT auto-pick Flux 2 / flux-2/edit / Flux Klein — those are generate-from-scratch / style, named-only for editing. Do NOT use `edit_image` for content edits — `edit_image` is ONLY for mechanical enhancements (upscale, expand/outpaint, remove-background, skin retouch). Its `magic_edit` operation is deprecated in favor of `generate_image_edit`. EXPANDING AN IMAGE: to widen/extend/uncrop an image or fit it into a wider frame while KEEPING the existing artwork, use `edit_image` with operation="zoom_out" (outpainting — original pixels preserved; size it with `zoom_out_percentage` or the `expand_left/right/top/bottom` pixel args). The "reframe" operation is NOT this: it re-generates the whole picture at a new aspect ratio and the subject comes back re-imagined. Only pick "reframe" when the user wants the shot re-taken, never when they want their image extended.',
131
+ '11. PRESET CONTRACT: if the user asks for a preset, names a preset, or says to use one of their/Kolbo presets, you MUST call `list_presets` with the matching type before generation, resolve the named or closest matching preset, and pass its exact returned `id` as `preset_id`. Use type="image" for generate_image and type="image_edit" for generate_image_edit. Never silently ignore a preset request, never invent an id, and never claim a preset was applied unless `preset_id` was present in the generation call.'
132
132
  ].join('\n')
133
133
  });
134
134
  const progress = require('./progress');
@@ -173,12 +173,12 @@ function createServer(opts = {}) {
173
173
  // transport. Without it, a remote-connector model reads "absolute local path",
174
174
  // sees no filesystem, and tells the user Kolbo cannot accept their file —
175
175
  // the single most-reported failure, despite the upload tools existing.
176
- attachFileInputHints(server, toolOptions);
177
- // OpenAI public-app review requires every exposed tool to declare the three
178
- // safety hints explicitly. The exact contract also fails closed when a tool
179
- // is added or removed without a classification.
180
- attachToolAnnotations(server);
181
- // Declaration-level `_meta['ui/resourceUri']` on every widget-carrying tool —
176
+ attachFileInputHints(server, toolOptions);
177
+ // OpenAI public-app review requires every exposed tool to declare the three
178
+ // safety hints explicitly. The exact contract also fails closed when a tool
179
+ // is added or removed without a classification.
180
+ attachToolAnnotations(server);
181
+ // Declaration-level `_meta['ui/resourceUri']` on every widget-carrying tool —
182
182
  // claude.ai prepares the widget iframe from tools/list, not from the result.
183
183
  attachToolWidgetMeta(server);
184
184
 
@@ -438,7 +438,7 @@ function registerGenerateTools(server, client, options = {}) {
438
438
  {
439
439
  prompt: z.string().optional().describe('Text description of the video to generate. Required unless `prompts` is provided.'),
440
440
  prompts: promptsField('videos'),
441
- 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.'),
441
+ model: z.string().optional().describe('Model identifier — pick a SPECIFIC model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). If the user already named a model/family (this turn or earlier), pass that name — do not substitute a cheaper default. Only when no model was named: "seedance-2" (versatile) or "veo3" (cinematic + native audio) are reasonable auto-picks; Kling is strongest for motion (list_models type="text_to_video" for exact ids). Call list_models for supported_durations / supported_aspect_ratios.'),
442
442
  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"'),
443
443
  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'),
444
444
  enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
@@ -520,7 +520,7 @@ function registerGenerateTools(server, client, options = {}) {
520
520
  })).max(MAX_BATCH_PROMPTS).optional().describe(
521
521
  `BATCH MODE — several DIFFERENT stills (2–${MAX_BATCH_PROMPTS}) animated concurrently in ONE call and rendered together in ONE combined widget. Unlike the \`prompts\` array on generate_image / generate_video, each entry pairs its OWN \`image_url\` with its OWN motion \`prompt\` — the image is what varies, and that is the point. **Hard cap: ${MAX_BATCH_PROMPTS} items per call — more than that is REJECTED with an error (never silently truncated), so split a longer sequence across several calls of at most ${MAX_BATCH_PROMPTS}.** Whenever the user wants several stills animated (a shot sequence, a storyboard, an animatic), ALWAYS pass them all here instead of making several separate calls — separate calls clutter the chat with stacked widgets. All items share the same model / duration / resolution / aspect_ratio / sound_enabled / project_id / session_id. When set, \`image_url\` and \`prompt\` are ignored.`
522
522
  ),
523
- 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.'),
523
+ model: z.string().optional().describe('Model identifier — pick a SPECIFIC model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). If the user already named a model/family (this turn or earlier), pass that name a text-to-video id remaps to the family\'s image-to-video sibling. Do not substitute a cheaper default (named Grok Imagine → not Seedance). Only when no model was named: "seedance-2" or "veo3" are reasonable auto-picks; Kling is strongest for motion (list_models type="img_to_video" for exact ids).'),
524
524
  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"'),
525
525
  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'),
526
526
  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.'),
@@ -363,7 +363,7 @@ function registerModelTools(server, client, options = {}) {
363
363
  }
364
364
 
365
365
  if (withSummary.length > 0) {
366
- sections.push(`Auto-selectable models (${withSummary.length}) — CHOOSE BY THE SUMMARY after each "—": match it to what the user asked for, then take the CHEAPEST model that fits. Credit cost, [NEW] and [RECOMMENDED] are not reasons to pick a model:\n${withSummary.map(formatModel).join('\n')}`);
366
+ sections.push(`Auto-selectable models (${withSummary.length}) — If the user already named a model/family (this turn or earlier), use that family — do not cheapest-swap. Otherwise CHOOSE BY THE SUMMARY after each "—": match it to what the user asked for, then take the CHEAPEST model that fits. Credit cost, [NEW] and [RECOMMENDED] are not reasons to pick a model:\n${withSummary.map(formatModel).join('\n')}`);
367
367
  }
368
368
  if (withoutSummary.length > 0) {
369
369
  sections.push(`Named-only models (${withoutSummary.length}) — only use if the user explicitly requests by name:\n${withoutSummary.map(formatModel).join('\n')}`);
@@ -197,7 +197,7 @@ function registerVisualDnaTools(server, client, options = {}) {
197
197
  'Generate a reference sheet for a Visual DNA from 1+ reference image URLs — the same step the in-app Visual DNA wizard offers, for EVERY DNA type via `sheet_type`: character = multi-angle turnaround, product = angles + branding/material/construction close-ups, environment = location angles + one signature detail, style = a style board (the same look applied to six varied subjects). The sheet is the single strongest consistency booster for a DNA, and it always preserves the reference\'s original art style (2D stays 2D, photo stays photo). CHARGES CREDITS, so when the user is about to create a DNA, OFFER this first ("want me to generate a reference sheet for stronger consistency? it costs a few credits") and only run it on a yes. Returns `character_sheet_url` — pass it as `character_sheet_url` to `create_visual_dna` with the matching `dna_type`.',
198
198
  {
199
199
  image_urls: z.array(z.string()).min(1).describe('Reference image URLs of the subject (for characters: front/side/varied angles work best). Use generated-image URLs or upload_media output.'),
200
- sheet_type: z.enum(['character', 'character_headless', 'product', 'environment', 'style']).optional().describe('Sheet layout. character = front/back/face turnaround. character_headless = wardrobe/body refs with a headless front panel (use when clothing must change without fighting the face sheet). product / environment / style = matching DNA types. Defaults to character.')
200
+ sheet_type: z.enum(['character', 'character_headless', 'character_bible', 'product', 'environment', 'style']).optional().describe('Sheet layout. character = front/back/face turnaround. character_headless = wardrobe/body refs with a headless front panel (use when clothing must change without fighting the face sheet). character_bible = denser production model-sheet (turnaround + faces + wardrobe + color swatches). product / environment / style = matching DNA types. Defaults to character.')
201
201
  },
202
202
  async ({ image_urls, sheet_type }) => {
203
203
  const result = await client.post('/v1/visual-dna/character-sheet', { image_urls, ...(sheet_type ? { sheet_type } : {}) });