@kolbo/mcp 1.106.4 → 1.107.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.106.4",
3
+ "version": "1.107.1",
4
4
  "description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/apps/html.js CHANGED
@@ -17,7 +17,7 @@ function widgetPage({ title, body, script }) {
17
17
  <title>${title}</title>
18
18
  <link rel="preconnect" href="https://fonts.googleapis.com">
19
19
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
20
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
20
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
21
21
  <style>${KOLBO_CSS}</style>
22
22
  </head>
23
23
  <body>
package/src/apps/index.js CHANGED
@@ -779,6 +779,7 @@ const TOOL_WIDGETS = {
779
779
  list_voices: UI.mediaGrid,
780
780
  list_visual_dnas: UI.mediaGrid,
781
781
  list_moodboards: UI.mediaGrid,
782
+ list_morphious_styles: UI.mediaGrid,
782
783
  // NOTE: list_color_palettes' handler has always called uiResult(UI.mediaGrid, ...)
783
784
  // (see color_palettes.js) but was missing here — hosts that prepare the widget
784
785
  // iframe from the tool DECLARATION (claude.ai reads tools/list, not the result)
package/src/apps/theme.js CHANGED
@@ -34,7 +34,13 @@ const KOLBO_CSS = `
34
34
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
35
35
  --smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
36
36
  --specular: inset 0 1px 0 rgba(255, 255, 255, 0.18);
37
- color-scheme: dark;
37
+ /* "light dark" on the ROOT, never a fixed scheme. When an iframe's root
38
+ scheme differs from the host frame's, the browser paints an OPAQUE canvas
39
+ behind the page: a fixed "dark" here drew a dark square around every card
40
+ in hosts whose frame is light/normal (Codex). "light dark" adopts the host
41
+ frame's scheme, so the canvas stays transparent everywhere. The card
42
+ carries the real theme for its native controls below. */
43
+ color-scheme: light dark;
38
44
  }
39
45
  [data-theme="light"] {
40
46
  --bg: #f5f5f2;
@@ -50,12 +56,13 @@ const KOLBO_CSS = `
50
56
  --text-faint: rgba(10, 10, 12, 0.40);
51
57
  --brand-soft: rgba(59, 130, 246, 0.10);
52
58
  --specular: inset 0 1px 0 rgba(255, 255, 255, 0.65);
53
- color-scheme: light;
54
59
  }
55
60
  * { box-sizing: border-box; margin: 0; padding: 0; }
56
61
  html, body { background: transparent; }
62
+ .k-card { color-scheme: dark; }
63
+ [data-theme="light"] .k-card { color-scheme: light; }
57
64
  body {
58
- font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;
65
+ font-family: 'Poppins', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;
59
66
  color: var(--text);
60
67
  font-size: 14px;
61
68
  line-height: 1.5;
@@ -114,7 +121,6 @@ body {
114
121
  .k-mention {
115
122
  display: inline; padding: 1px 5px; border-radius: 5px;
116
123
  background: var(--brand-soft); color: var(--brand);
117
- font-family: 'JetBrains Mono', ui-monospace, monospace;
118
124
  font-size: 0.94em; font-weight: 500;
119
125
  }
120
126
  /* Single-line media caption (scene / batch prompt under the viewer) */
@@ -143,7 +149,7 @@ body {
143
149
  .k-chip .k-mono-icon {
144
150
  width: 14px; height: 14px; border-radius: 4px; background: var(--brand);
145
151
  color: #fff; font-size: 9px; font-weight: 700; display: inline-flex;
146
- align-items: center; justify-content: center; font-family: 'Inter', sans-serif;
152
+ align-items: center; justify-content: center; font-family: inherit;
147
153
  }
148
154
  .k-chip img.k-voice-thumb { width: 18px; height: 18px; border-radius: 999px; margin-left: -3px; }
149
155
  .k-ref-thumb { width: 26px; height: 26px; border-radius: 6px; object-fit: cover; border: 1px solid var(--border-strong); }
@@ -161,7 +167,7 @@ html.k-peek-open .k-card { min-height: 460px; }
161
167
  .k-peek-bar { flex: none; display: flex; align-items: center; gap: 8px; padding: 8px 8px 8px 14px; min-height: 44px; }
162
168
  .k-peek-cap { flex: 1; min-width: 0; font-size: 12.5px; color: rgba(255,255,255,0.78);
163
169
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
164
- .k-peek-count { flex: none; font-size: 11.5px; color: rgba(255,255,255,0.55); font-family: 'JetBrains Mono', ui-monospace, monospace; }
170
+ .k-peek-count { flex: none; font-size: 11.5px; color: rgba(255,255,255,0.55); font-variant-numeric: tabular-nums; }
165
171
  .k-peek-btn {
166
172
  flex: none; width: 32px; height: 32px; border: 1px solid rgba(255,255,255,0.14); border-radius: 999px;
167
173
  background: rgba(255,255,255,0.08); color: #fff; cursor: pointer; font-size: 15px;
@@ -303,7 +309,7 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
303
309
  animating k-sweep skeleton forced per-frame backdrop re-sampling on phones;
304
310
  0.65 black over the shimmer reads identically without it */
305
311
  border: 1px solid rgba(255, 255, 255, 0.14);
306
- font-size: 11px; font-weight: 600; color: #fff;
312
+ font-size: 11px; font-weight: 600; color: #fff; text-transform: capitalize;
307
313
  }
308
314
  .k-spin {
309
315
  width: 12px; height: 12px; border-radius: 50%;
@@ -463,7 +469,7 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
463
469
  .k-dot.on { width: 22px; opacity: 0.95; cursor: default; }
464
470
  .k-dot.ghost { cursor: default; opacity: 0.14; }
465
471
  .k-pager-label { font-size: 11px; color: var(--text-faint);
466
- font-family: 'JetBrains Mono', ui-monospace, monospace; min-width: 58px; text-align: center; }
472
+ font-variant-numeric: tabular-nums; min-width: 58px; text-align: center; }
467
473
 
468
474
  /* ---- Audio rows ---- */
469
475
  .k-audio-row { display: flex; align-items: center; gap: 10px; padding: 9px 10px;
@@ -533,7 +539,7 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
533
539
  .k-footer:not(:has(.k-credits:not(:empty))) { display: none; }
534
540
  .k-footer a { color: var(--text-faint); text-decoration: none; }
535
541
  .k-footer a:hover { color: var(--brand); }
536
- .k-credits { margin-left: auto; font-family: 'JetBrains Mono', monospace; }
542
+ .k-credits { margin-left: auto; font-variant-numeric: tabular-nums; }
537
543
  `;
538
544
 
539
545
  /**
@@ -453,11 +453,17 @@ function moodboardChipsHTML(sc, s) {
453
453
  }
454
454
  return h;
455
455
  }
456
- function chip(inner) { return '<span class="k-chip">' + inner + '</span>'; }
456
+ // Labels read as words: "Image", "Enhanced", "Web search" — the first letter
457
+ // of the visible text (after any leading icon markup) is uppercased. Digits
458
+ // first ("1080p", "16:9", "7 images") are left alone.
459
+ function capLabel(inner) {
460
+ return String(inner).replace(/(^|>)(\\s*)([a-z])/, function (m, gt, sp, ch) { return gt + sp + ch.toUpperCase(); });
461
+ }
462
+ function chip(inner) { return '<span class="k-chip">' + capLabel(inner) + '</span>'; }
457
463
  // Same chip with a hover title — used to surface the asset id behind a
458
464
  // "2 Visual DNA" / "preset" label without spending chip width on it.
459
465
  function chipT(inner, title) {
460
- return '<span class="k-chip" title="' + esc(title) + '">' + inner + '</span>';
466
+ return '<span class="k-chip" title="' + esc(title) + '">' + capLabel(inner) + '</span>';
461
467
  }
462
468
  function iconFor(kind) {
463
469
  switch (kind) {
package/src/index.js CHANGED
@@ -70,6 +70,7 @@ const { registerFontTools } = require('./tools/fonts');
70
70
  const { registerEditorTools } = require('./tools/editor');
71
71
  const { registerFlowTools } = require('./tools/flow');
72
72
  const { registerTrendTools } = require('./tools/trends');
73
+ const { registerMorphiousStyleTools } = require('./tools/morphious_styles');
73
74
  const { registerMediaTools } = require('./tools/media');
74
75
  const { registerPresetTools } = require('./tools/presets');
75
76
  const { registerArtifactTools } = require('./tools/artifacts');
@@ -196,6 +197,7 @@ function createServer(opts = {}) {
196
197
  registerEditorTools(server, client);
197
198
  registerFlowTools(server, client);
198
199
  registerTrendTools(server, client);
200
+ registerMorphiousStyleTools(server, client);
199
201
  registerAnalyzeTools(server, client, toolOptions);
200
202
  registerMediaTools(server, client, toolOptions);
201
203
  registerPresetTools(server, client, toolOptions);
@@ -11,6 +11,7 @@
11
11
  const READ_ONLY = [
12
12
  'get_flow_schema', 'list_flow_sessions', 'get_flow_session', 'validate_flow_session',
13
13
  'estimate_flow_run', 'get_flow_run', 'list_flow_runs',
14
+ 'list_morphious_styles',
14
15
  'list_trends', 'get_trend', 'estimate_trend_run', 'get_trend_run', 'list_trend_runs',
15
16
  'get_download_status',
16
17
  'get_video_editor_schema', 'list_video_editor_sessions', 'get_video_editor_session',
@@ -59,7 +60,7 @@ const PRIVATE_WRITE = [
59
60
  'import_elevenlabs_voice',
60
61
  'create_visual_dna', 'create_visual_dna_folder', 'update_visual_dna_folder',
61
62
  'move_visual_dna_to_folder',
62
- 'create_moodboard',
63
+ 'create_moodboard', 'create_morphious_style',
63
64
  'create_color_palette', 'activate_color_palette', 'deactivate_color_palette',
64
65
  'move_session', 'bulk_move_sessions', 'move_generations_to_session',
65
66
  'split_session', 'undo_session_organization',
@@ -102,7 +103,7 @@ const DESTRUCTIVE_WRITE = [
102
103
 
103
104
  // Deletes and whole-value replacement updates are conservatively destructive.
104
105
  'delete_voice', 'update_visual_dna', 'delete_visual_dna', 'delete_visual_dna_folder',
105
- 'update_moodboard', 'delete_moodboard',
106
+ 'update_moodboard', 'delete_moodboard', 'delete_morphious_style',
106
107
  'update_color_palette', 'delete_color_palette',
107
108
  'delete_media_folder', 'delete_media', 'permanently_delete_media',
108
109
  'bulk_delete_media', 'bulk_permanently_delete_media',
@@ -240,7 +240,8 @@ const MORPHIOUS_GUIDE = ' MORPHIOUS / GENJUTSU (exactly ONE source video, omit d
240
240
  + 'kolbo-morphious-motion 1-30 images, 4-30s; kolbo-morphious-swap 0-30 images, 4-30s; both 480p/720p/1080p, output keeps the source shape. '
241
241
  + 'Lite (kolbo-morphious-lite-motion 1-10 images / kolbo-morphious-lite-swap 0-10 images) caps at 15s. '
242
242
  + 'kolbo-morphious-reframe: NO images/DNA, 1-300s, aspect_ratio REQUIRED (16:9, 9:16, 1:1, 4:3, 3:4, 21:9), 540p or 720p. '
243
- + 'higgsfield-genjutsu-motion-transfer / -object-swap: 1-8 images or Visual DNA, 4-30s, 480p/720p/1080p.';
243
+ + 'higgsfield-genjutsu-motion-transfer / -object-swap: 1-8 images or Visual DNA, 4-30s, 480p/720p/1080p. '
244
+ + 'STYLES: to restyle a whole video into an animated look (2D toon, painterly 3D, gouache, comic, clay, felt...), pick an id from list_morphious_styles and pass it as style_id with model kolbo-morphious-motion; no reference image or prompt is needed.';
244
245
 
245
246
  function registerGenerateTools(server, client, options = {}) {
246
247
  // Every JSON POST from these tools rehosts local file paths into the media
@@ -1579,6 +1580,7 @@ function registerGenerateTools(server, client, options = {}) {
1579
1580
  preset_id: z.string().optional().describe('Preset ID from list_presets type="video" (optional)'),
1580
1581
  enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
1581
1582
  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.**'),
1583
+ style_id: z.string().optional().describe('Morphious Styles only (kolbo-morphious-motion / -lite-motion): id of a look from list_morphious_styles (Kolbo catalog or a custom style the user made). Restyles the WHOLE video in that look; the style counts as reference image 1, so no reference_images are needed. Prompt optional.'),
1582
1584
  draft: z.boolean().optional().describe('Special Draft mode. True forces 480p Draft even if resolution says 1080p; false selects regular video. Only supported when list_models advertises draft capabilities. For Seedance 2.5 editing use model seedance-2-5-video-to-video with generate_video_from_video; the source duration and aspect ratio are preserved. Uses ordinary credits; finalize through draft_quote then draft_enhance.'),
1583
1585
  resolution: z.string().optional().describe('Video resolution or named tier: read supported_resolutions from list_models. For Seedance 2.5 Draft use resolution="480p-draft" on the matching public model: seedance-2-5 for creation, seedance-2-5-video-to-video for editing. Plain "480p" generates regular video, NOT Draft. Finalize an actual draft with edit_video operation="draft_quote" then "draft_enhance".'),
1584
1586
  sound_enabled: z.boolean().optional().describe('Enable (`true`) or disable (`false`) AI-generated synced audio on the output video. Honored by `sound_generation_type: "native"` models (Kling O3/V3, Veo 3.1, PixVerse V6). Omit to use `sound_enabled_by_default`. Enabling sound may apply `sound_credit_multiplier` to cost.'),
@@ -1592,7 +1594,7 @@ function registerGenerateTools(server, client, options = {}) {
1592
1594
  project_id: projectIdField,
1593
1595
  session_id: sessionIdField
1594
1596
  },
1595
- 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, draft, sound_enabled, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id }) => {
1597
+ 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, style_id, resolution, draft, sound_enabled, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id }) => {
1596
1598
  validateVideoPrompt({ prompt, duration, aspect_ratio, multi_shots, multi_shot_count });
1597
1599
  if (draft === true) resolution = '480p-draft';
1598
1600
  else if (draft === false && resolution?.endsWith('-draft')) resolution = resolution.slice(0, -6);
@@ -1638,7 +1640,7 @@ function registerGenerateTools(server, client, options = {}) {
1638
1640
  reference_images: some(urlsOf('image')),
1639
1641
  reference_videos: some(urlsOf('video')),
1640
1642
  reference_audio_urls: some(urlsOf('audio')),
1641
- duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids,
1643
+ duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, style_id,
1642
1644
  resolution, draft, sound_enabled, keyframes, multi_shots, multi_shot_count,
1643
1645
  session_name, project_id, session_id
1644
1646
  };
@@ -1994,6 +1996,7 @@ function registerGenerateTools(server, client, options = {}) {
1994
1996
  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'),
1995
1997
  enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: false — only pass true if the user explicitly asks to enhance/improve the prompt.'),
1996
1998
  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.**'),
1999
+ style_id: z.string().optional().describe('Morphious Styles only (kolbo-morphious-motion / -lite-motion): id of a look from list_morphious_styles (Kolbo catalog or a custom style the user made). Restyles the WHOLE video in that look; the style counts as reference image 1, so no reference_images are needed. Prompt optional.'),
1997
2000
  draft: z.boolean().optional().describe('Special Draft mode. True forces 480p Draft even if resolution says 1080p; false selects regular video. Only supported when list_models advertises draft capabilities. For Seedance 2.5 editing use model seedance-2-5-video-to-video with generate_video_from_video; the source duration and aspect ratio are preserved. Uses ordinary credits; finalize through draft_quote then draft_enhance.'),
1998
2001
  resolution: z.string().optional().describe('Video resolution or named tier: read supported_resolutions from list_models. For Seedance 2.5 Draft use resolution="480p-draft" on the matching public model: seedance-2-5 for creation, seedance-2-5-video-to-video for editing. Plain "480p" generates regular video, NOT Draft. Finalize an actual draft with edit_video operation="draft_quote" then "draft_enhance".'),
1999
2002
  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.'),
@@ -2029,7 +2032,7 @@ function registerGenerateTools(server, client, options = {}) {
2029
2032
  project_id: projectIdField,
2030
2033
  session_id: sessionIdField
2031
2034
  },
2032
- async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, resolution, draft, sound_enabled, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, enhancement_model, target_fps, slowdown_factor, output_format, project_id, session_id }) => {
2035
+ async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, style_id, resolution, draft, sound_enabled, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, enhancement_model, target_fps, slowdown_factor, output_format, project_id, session_id }) => {
2033
2036
  if (draft === true) resolution = '480p-draft';
2034
2037
  else if (draft === false && resolution?.endsWith('-draft')) resolution = resolution.slice(0, -6);
2035
2038
  model = await canonicalModelId(client, model, 'video_to_video'); // lenient id resolution ("z-image" → "z-image/turbo")
@@ -2040,7 +2043,7 @@ function registerGenerateTools(server, client, options = {}) {
2040
2043
  let startResponse;
2041
2044
  if (isUrl) {
2042
2045
  startResponse = await client.post('/v1/generate/video-from-video', {
2043
- video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, draft, sound_enabled,
2046
+ video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, style_id, resolution, draft, sound_enabled,
2044
2047
  reference_images, reference_videos, elements, preset, source_language, translation_language,
2045
2048
  srt_content, srt_file_url, vocabulary, customization,
2046
2049
  enhancement_model, target_fps, slowdown_factor, output_format,
@@ -2063,6 +2066,7 @@ function registerGenerateTools(server, client, options = {}) {
2063
2066
  if (duration !== undefined) form.append('duration', String(duration));
2064
2067
  if (enhance_prompt !== undefined) form.append('enhance_prompt', String(enhance_prompt));
2065
2068
  if (visual_dna_ids) form.append('visual_dna_ids', JSON.stringify(visual_dna_ids));
2069
+ if (style_id) form.append('style_id', style_id);
2066
2070
  if (resolution) form.append('resolution', resolution);
2067
2071
  if (draft !== undefined) form.append('draft', String(draft));
2068
2072
  if (sound_enabled !== undefined) form.append('sound_enabled', String(sound_enabled));
@@ -0,0 +1,59 @@
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
+ // Morphious Styles: restyle a whole video into a look (Kolbo catalog or the user's own custom style).
7
+ // Apply one by passing its id as `style_id` to generate_video_from_video / generate_elements with
8
+ // model kolbo-morphious-motion. Contract: kolbo-api src/modules/morphiousStyles.
9
+
10
+ const { z } = require('zod');
11
+ const { UI, uiResult } = require('../apps');
12
+
13
+ const unwrap = r => (r && r.status === true && r.data !== undefined ? r.data : r);
14
+ const text = value => ({ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] });
15
+
16
+ function registerMorphiousStyleTools(server, client) {
17
+ server.tool(
18
+ 'list_morphious_styles',
19
+ 'List Morphious Styles: looks that restyle a WHOLE video (2D toon, painterly 3D, gouache, comic, clay, felt, yarn...). Returns the Kolbo catalog first, then the user\'s own custom styles (custom: true). To apply one, pass its id as style_id to generate_video_from_video with model "kolbo-morphious-motion" and the source video; no reference image or prompt is needed.',
20
+ {},
21
+ async () => {
22
+ const styles = unwrap(await client.get('/v1/morphious-styles')).styles || [];
23
+ return uiResult(UI.mediaGrid, JSON.stringify({ styles, count: styles.length }, null, 2), {
24
+ widget: 'media-grid',
25
+ title: 'Morphious Styles',
26
+ items: styles.map(s => ({
27
+ id: s.id,
28
+ title: s.name,
29
+ subtitle: s.custom ? 'Custom' : undefined,
30
+ thumbnail: s.thumb || s.cover,
31
+ url: s.preview || s.motion || s.cover,
32
+ media_type: s.preview ? 'video' : 'image',
33
+ use_hint: 'Restyle my video in the "{TITLE}" style (style_id: {ID}).',
34
+ })),
35
+ total: styles.length,
36
+ has_more: false,
37
+ });
38
+ }
39
+ );
40
+
41
+ server.tool(
42
+ 'create_morphious_style',
43
+ 'Create a custom Morphious Style from 1-8 image URLs that show the look (an illustration style, a painting technique, a 3D render style). The images define the LOOK only, never the subject. Returns the style id to pass as style_id. Upload local files first with upload_media / create_upload_ticket.',
44
+ {
45
+ name: z.string().min(1).max(60).describe('Style name shown to the user, e.g. "Ink Wash".'),
46
+ image_urls: z.array(z.string().url()).min(1).max(8).describe('1-8 https image URLs that show the look.'),
47
+ },
48
+ async ({ name, image_urls }) => text(unwrap(await client.post('/v1/morphious-styles/custom', { name, image_urls })))
49
+ );
50
+
51
+ server.tool(
52
+ 'delete_morphious_style',
53
+ 'Delete one of the user\'s own custom Morphious Styles. Catalog styles cannot be deleted. Past generations keep their result.',
54
+ { style_id: z.string().min(1).max(64).describe('Custom style id from list_morphious_styles (custom: true).') },
55
+ async ({ style_id }) => text(unwrap(await client.delete(`/v1/morphious-styles/custom/${encodeURIComponent(style_id)}`)))
56
+ );
57
+ }
58
+
59
+ module.exports = { registerMorphiousStyleTools };