@clipform/mcp-server 2.17.1 → 3.0.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.
@@ -6,7 +6,7 @@ import {
6
6
  getWorkflowText,
7
7
  objectType,
8
8
  registerPrompts
9
- } from "./chunk-2EPNC4AY.js";
9
+ } from "./chunk-KCF7ZVDI.js";
10
10
  import {
11
11
  GUIDE_TYPES,
12
12
  QUIZ_VARIANTS,
@@ -15,35 +15,27 @@ import {
15
15
  getGuideUri,
16
16
  guideFallbackText,
17
17
  registerResources
18
- } from "./chunk-5TQU2XJ7.js";
18
+ } from "./chunk-EXV5M7YV.js";
19
19
  import {
20
20
  ACTIVE_NODE_TYPES,
21
21
  CONTACT_FIELDS,
22
22
  FEATURE_FLAGS,
23
23
  FORM_TYPE_ALIASES,
24
24
  FULL_CAPS,
25
- HTTP_URL_MESSAGE,
26
- KEN_BURNS_EFFECTS,
27
- KEN_BURNS_FIT_MODES,
28
- KEN_BURNS_PRESETS,
29
25
  MCP_TOOL_TIERS,
30
26
  MCP_TOOL_TOOLSETS,
31
27
  NODE_TYPES,
32
28
  NON_COUNTABLE_TYPES,
33
- SLIDESHOW_COMPOSITION_TRANSITIONS,
34
- SLIDESHOW_TRANSITION_DURATION,
35
- VIDEO_LOOK_NAMES,
36
29
  bfsOrder,
37
30
  callApi,
38
31
  capsFromToolsets,
39
32
  defaultTarget,
40
33
  errorResult,
41
34
  exposedCompositionIds,
42
- isHttpUrl,
43
35
  resolveFormType,
44
36
  structuredResult,
45
37
  textResult
46
- } from "./chunk-FIUAXSVQ.js";
38
+ } from "./chunk-KNOCYZHG.js";
47
39
  import {
48
40
  getMcpAuth,
49
41
  runWithMcpTool
@@ -17111,7 +17103,6 @@ var NODE_TYPES_DESCRIPTION = (() => {
17111
17103
  return lines.join("\n");
17112
17104
  })();
17113
17105
  var CONFIG_FIELD_DESCRIPTION = "Type-specific configuration keyed by node type. Omit to use defaults. Per-type keys, shapes, and defaults are listed in the clipform_create_form description; craft guidance via clipform_get_guide.";
17114
- var STYLE_PRESET_ENUM = external_exports.enum(KEN_BURNS_PRESETS).describe(`Ken Burns style preset: ${KEN_BURNS_PRESETS.join(", ")}`);
17115
17106
  var CaptionSchema = external_exports.object({
17116
17107
  start: external_exports.number().describe("Segment start time in seconds"),
17117
17108
  end: external_exports.number().describe("Segment end time in seconds"),
@@ -18832,7 +18823,7 @@ var MEDIA_SUPPORTED_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.s
18832
18823
  var AttachMediaItemSchema = external_exports.object({
18833
18824
  node_id: external_exports.string().describe("The node ID"),
18834
18825
  media_asset_id: external_exports.string().describe("A media asset ID returned by clipform_upload_media_asset"),
18835
- fit_media: external_exports.boolean().optional().describe("Show the whole frame (contain) instead of cover-cropping. ALWAYS set true when attaching renders from clipform_render_composition or clipform_generate_video - they are composed 9:16 frames that must never be cropped. Leave unset for user-supplied media (arbitrary aspect ratios want the cover default)."),
18826
+ fit_media: external_exports.boolean().optional().describe("Show the whole frame (contain) instead of cover-cropping. ALWAYS set true when attaching renders from clipform_render_composition or clipform_render_video_template - they are composed 9:16 frames that must never be cropped. Leave unset for user-supplied media (arbitrary aspect ratios want the cover default)."),
18836
18827
  show_captions: external_exports.boolean().optional().describe("Display captions/subtitles on the node. Defaults to on; pass false to hide them.")
18837
18828
  });
18838
18829
  function registerAttachNodeMediaTool(server) {
@@ -19301,7 +19292,7 @@ function registerSearchMediaTool(server) {
19301
19292
  "clipform_search_media",
19302
19293
  {
19303
19294
  title: "Search Media",
19304
- description: `Search images or stock video clips. Pass one query or many (max 10) - multiple queries run in one call instead of separate tool calls. Use results to feed into clipform_generate_video for narrated slideshow videos, or upload directly as still images via clipform_upload_media_asset then clipform_attach_node_media. All results are pre-cleared for commercial use. Results include a description (alt text where the provider has it) - use it to pick visually distinct images.
19295
+ description: `Search images or stock video clips. Pass one query or many (max 10) - multiple queries run in one call instead of separate tool calls. Use results to feed into clipform_render_video_template (slideshow) for narrated slideshow videos, or upload directly as still images via clipform_upload_media_asset then clipform_attach_node_media. All results are pre-cleared for commercial use. Results include a description (alt text where the provider has it) - use it to pick visually distinct images.
19305
19296
 
19306
19297
  Example: { queries: [{ query: "saturn rings" }, { query: "mars surface", count: 3 }] } returns portrait images for both.`,
19307
19298
  inputSchema: {
@@ -19395,8 +19386,7 @@ Example: { queries: [{ query: "saturn rings" }, { query: "mars surface", count:
19395
19386
  }
19396
19387
 
19397
19388
  // src/lib/render-routing.ts
19398
- var RENDER_ROUTING_RULE = `Choosing a render tool: for a recognisable form/quiz beat (guess-the-city, this-or-that, mystery reveal, multiple choice, photo montage...) reach for a video template first (clipform_list_video_templates + clipform_render_video_template) - it is a one-call recipe. Use clipform_generate_video for a narrated or audio-synced media montage (images/clips timed to a voice track). Use clipform_render_composition only when neither fits and you need a custom layer stack.`;
19399
- var MONTAGE_NARRATION_RULE = `Montage disambiguation: choose clipform_generate_video when the montage is narrated or synced to an audio track; choose the slideshow video template when it is silent (motion + transitions only, no voice-over).`;
19389
+ var RENDER_ROUTING_RULE = `Choosing a render tool: for a recognisable form/quiz beat (guess-the-city, this-or-that, mystery reveal, multiple choice, photo montage...) reach for a video template first (clipform_list_video_templates + clipform_render_video_template) - it is a one-call recipe. For a photo/clip montage, narrated or silent, use the slideshow template (pass audio_url to sync it to a voice track). Use clipform_render_composition only when neither fits and you need a custom layer stack.`;
19400
19390
  var RENDER_ATTACH_RULE = `A render for a form node is not done until it is attached to that node. Pass node_id (and form_id) so the completed render attaches itself automatically - do not poll clipform_check_render to completion or manually chain clipform_upload_media_asset + clipform_attach_node_media; fire the render and move on.`;
19401
19391
 
19402
19392
  // ../../node_modules/.pnpm/uuid@11.1.1/node_modules/uuid/dist/esm/stringify.js
@@ -19498,6 +19488,10 @@ function pruneJobs() {
19498
19488
  }
19499
19489
 
19500
19490
  // src/lib/auto-attach.ts
19491
+ function resolveCaptionRef(explicit, audioUrl) {
19492
+ if (explicit) return explicit;
19493
+ return typeof audioUrl === "string" && audioUrl ? audioUrl : void 0;
19494
+ }
19501
19495
  async function createRenderPlaceholder(input) {
19502
19496
  try {
19503
19497
  const me = await callApi("/me", { method: "GET" });
@@ -19676,7 +19670,7 @@ function registerRenderCompositionTool(server) {
19676
19670
  "clipform_render_composition",
19677
19671
  {
19678
19672
  title: "Render Composition",
19679
- description: `Render a specialised video composition to MP4 or PNG - custom animated visuals that clipform_generate_video can't provide, such as geography animations or designed motion graphics. Available composition names and their required input props schemas come from clipform_list_compositions.
19673
+ description: `Render a specialised video composition to MP4 or PNG - custom animated visuals that a video template can't provide, such as geography animations or designed motion graphics. Available composition names and their required input props schemas come from clipform_list_compositions.
19680
19674
 
19681
19675
  Output formats: mp4 (H.264, best for social media) or png (single frame). Returns a public URL when complete.
19682
19676
 
@@ -19821,7 +19815,7 @@ function registerSearchMusicTool(server) {
19821
19815
  "clipform_search_music",
19822
19816
  {
19823
19817
  title: "Search Music",
19824
- description: `Search for royalty-free music tracks and ambient sounds. Quiz forms especially benefit from background music - it keeps energy up between questions. Also consider it for multi-step surveys and longer forms. Pass the returned URL as the audio track to clipform_generate_video, or as the audio for the slideshow video template via clipform_render_video_template. Returns download URLs, duration, artist, and license info.`,
19818
+ description: `Search for royalty-free music tracks and ambient sounds. Quiz forms especially benefit from background music - it keeps energy up between questions. Also consider it for multi-step surveys and longer forms. Pass the returned URL as the audio for the slideshow video template via clipform_render_video_template. Returns download URLs, duration, artist, and license info.`,
19825
19819
  inputSchema: {
19826
19820
  query: external_exports.string().min(1).describe("What to search for (e.g. 'upbeat quiz background', 'calm ambient', 'playful pizzicato')"),
19827
19821
  count: external_exports.number().int().min(1).max(10).default(5).optional().describe("Max results (default: 5)"),
@@ -19841,7 +19835,7 @@ function registerSearchMusicTool(server) {
19841
19835
  source: external_exports.string().optional(),
19842
19836
  license: external_exports.string().optional()
19843
19837
  })
19844
- ).describe("Matching tracks (empty if none); pass a url as the audio track to generate_video")
19838
+ ).describe("Matching tracks (empty if none); pass a url as the audio for the slideshow template")
19845
19839
  },
19846
19840
  // Fetches from an external royalty-free music provider - openWorldHint:
19847
19841
  // true (#2976).
@@ -19911,7 +19905,7 @@ function registerListCompositionsTool(server) {
19911
19905
  "clipform_list_compositions",
19912
19906
  {
19913
19907
  title: "List Compositions",
19914
- description: `Browse available video compositions and their expected props schemas. Call this before using clipform_render_composition to discover visual styles and their input props. For an image or photo montage, reach for a video template (clipform_list_video_templates + clipform_render_video_template) or clipform_generate_video instead of a composition.`,
19908
+ description: `Browse available video compositions and their expected props schemas. Call this before using clipform_render_composition to discover visual styles and their input props. For an image or photo montage, reach for a video template (clipform_list_video_templates + clipform_render_video_template) instead of a composition.`,
19915
19909
  inputSchema: {},
19916
19910
  outputSchema: {
19917
19911
  compositions: external_exports.array(
@@ -20044,13 +20038,14 @@ ${RENDER_ATTACH_RULE}`,
20044
20038
  node_id: external_exports.string().optional().describe("Form node to attach this render to automatically once it completes - skips the manual clipform_upload_media_asset + clipform_attach_node_media steps and republishes the form if it's currently live. Requires form_id."),
20045
20039
  form_id: external_exports.string().uuid().optional().describe("The form UUID (required when node_id is set)."),
20046
20040
  captions: external_exports.array(CaptionSchema).optional().describe("Word-level captions from clipform_generate_tts - carried onto the attached media asset. Only used when node_id is set."),
20047
- caption_ref: external_exports.string().optional().describe("caption_ref from clipform_generate_tts - an opaque handle that attaches that TTS run's word-level captions to the media asset, resolved server-side. Preferred over captions. Only used when node_id is set.")
20041
+ caption_ref: external_exports.string().optional().describe("caption_ref from clipform_generate_tts - an opaque handle that attaches that TTS run's word-level captions to the media asset, resolved server-side. Preferred over captions. Only used when node_id is set. Defaults to controls.audio_url when that is a clipform_generate_tts track (slideshow narration), so narrated montages keep their captions.")
20048
20042
  },
20049
20043
  outputSchema: {
20050
20044
  status: external_exports.enum(["rendering", "complete"]),
20051
20045
  job_id: external_exports.string().optional().describe("Present when status is 'rendering' - pass to clipform_check_render"),
20052
20046
  public_url: external_exports.string().optional().describe("Present when status is 'complete' - attach via upload_media_asset then attach_node_media (fit_media: true), unless node_id was set (auto-attached)"),
20053
20047
  template: external_exports.string().optional(),
20048
+ duration_seconds: external_exports.number().optional().describe("Final video length in seconds - the narration's real length when audio_url drove it."),
20054
20049
  output_format: external_exports.string().optional(),
20055
20050
  attached: external_exports.boolean().optional().describe("True when node_id was provided and the render was attached to the node automatically (present once the attach outcome is known)."),
20056
20051
  media_asset_id: external_exports.string().optional().describe("The workspace media asset created from this render, when attached."),
@@ -20067,6 +20062,7 @@ ${RENDER_ATTACH_RULE}`,
20067
20062
  if (node_id && !form_id) {
20068
20063
  return errorResult("form_id is required when node_id is set.");
20069
20064
  }
20065
+ const captionRef = resolveCaptionRef(caption_ref, controls?.audio_url);
20070
20066
  const renderBody = { template, controls: controls ?? {}, outputFormat };
20071
20067
  const mediaType = outputFormat === "png" ? "still" : "video";
20072
20068
  if (wait === false) {
@@ -20087,7 +20083,7 @@ ${RENDER_ATTACH_RULE}`,
20087
20083
  const publicUrl = r.data.public_url;
20088
20084
  const placeholder = await placeholderPromise2;
20089
20085
  if (publicUrl) {
20090
- attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType, captions, captionRef: caption_ref, placeholder });
20086
+ attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType, captions, captionRef, placeholder });
20091
20087
  } else {
20092
20088
  const message = "Render completed without a public_url; nothing to attach.";
20093
20089
  attach2 = { attached: false, error: message };
@@ -20125,7 +20121,7 @@ ${RENDER_ATTACH_RULE}`,
20125
20121
  if (node_id && form_id) {
20126
20122
  const placeholder = await placeholderPromise;
20127
20123
  if (data.public_url) {
20128
- attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType, captions, captionRef: caption_ref, placeholder });
20124
+ attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType, captions, captionRef, placeholder });
20129
20125
  } else if (placeholder) {
20130
20126
  await failPlaceholder(placeholder, "Render completed without a public_url; nothing to attach.");
20131
20127
  }
@@ -20133,11 +20129,13 @@ ${RENDER_ATTACH_RULE}`,
20133
20129
  return structuredResult(
20134
20130
  [
20135
20131
  `Render complete. Public URL: ${data.public_url}`,
20132
+ typeof data.duration_seconds === "number" ? `Duration: ${data.duration_seconds}s` : "",
20136
20133
  attach ? attachSummaryLine(node_id, attach) : `Attach via clipform_upload_media_asset then clipform_attach_node_media with fit_media: true (composed 9:16 frame - contain, never crop).`
20137
- ].join("\n"),
20134
+ ].filter(Boolean).join("\n"),
20138
20135
  {
20139
20136
  status: "complete",
20140
20137
  ...data.public_url ? { public_url: data.public_url } : {},
20138
+ ...typeof data.duration_seconds === "number" ? { duration_seconds: data.duration_seconds } : {},
20141
20139
  template,
20142
20140
  output_format: outputFormat,
20143
20141
  ...attachStructuredFields(attach)
@@ -20206,182 +20204,6 @@ function registerListAssetsTool(server) {
20206
20204
  );
20207
20205
  }
20208
20206
 
20209
- // src/tools/generate-video.ts
20210
- var httpUrl = () => external_exports.string().url().refine(isHttpUrl, { message: HTTP_URL_MESSAGE });
20211
- function registerGenerateVideoTool(server) {
20212
- server.registerTool(
20213
- "clipform_generate_video",
20214
- {
20215
- title: "Generate Video",
20216
- description: `Generate a video from images, video clips, or both, synced to an audio track. Use this for narrated question backgrounds, topic visualisations, or any form node that benefits from video. Combine with clipform_generate_tts for narrated audio and clipform_search_media for royalty-free images. Creates 9:16 (720x1280) with Ken Burns pan/zoom effects and transitions. Returns a public URL when complete.
20217
-
20218
- Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by default). Duration matches audio_url or set duration_seconds explicitly.
20219
-
20220
- For multi-question builds, pass wait: false on every render: each call returns a job ID immediately, so all renders run in parallel - then collect URLs with clipform_check_render. Sequential waiting renders take ${RENDER_TIMING.expectedRange} EACH.
20221
-
20222
- ${RENDER_ROUTING_RULE}
20223
- ${MONTAGE_NARRATION_RULE}
20224
- ${RENDER_ATTACH_RULE}`,
20225
- inputSchema: {
20226
- items: external_exports.array(
20227
- external_exports.object({
20228
- type: external_exports.enum(["image", "video"]).describe("'image' for still images with Ken Burns effects, 'video' for video clips"),
20229
- url: httpUrl().describe("Media URL"),
20230
- effect: external_exports.enum([...KEN_BURNS_EFFECTS, "random"]).optional().describe("Advanced override: a specific Ken Burns effect (image only), overriding the preset look's own effect choice"),
20231
- aspect_ratio: external_exports.number().positive().optional().describe("Image width/height ratio (image only). Enables auto blur-pad for landscape images."),
20232
- fit: external_exports.enum(KEN_BURNS_FIT_MODES).optional().describe("Framing mode (image only). auto = cover for portrait, blur-pad for landscape."),
20233
- start_from: external_exports.number().min(0).optional().describe("Start time in seconds (video only). Trims the clip."),
20234
- volume: external_exports.number().min(0).max(1).optional().describe("Clip audio volume 0-1 (video only, default 0 = muted)")
20235
- })
20236
- ).min(1).max(20).describe("Media items (images, video clips, or a mix)"),
20237
- audio_url: httpUrl().optional().describe("Audio track URL. Video duration matches audio duration."),
20238
- background_audio_url: httpUrl().optional().describe("Ambience/music bed under the narration (crowd noise, room tone). Loops to fill the video. Find tracks with clipform_search_music."),
20239
- background_audio_volume: external_exports.number().min(0).max(1).optional().describe("Background bed volume 0-1 (default 0.15 - sits under speech)"),
20240
- duration_seconds: external_exports.number().positive().optional().describe("Video duration in seconds (required if no audio_url)"),
20241
- preset: external_exports.enum(VIDEO_LOOK_NAMES).optional().default("clean").describe(`Named look driving transition + motion + colour treatment together (default: 'clean'). One of: ${VIDEO_LOOK_NAMES.join(", ")}. The advanced fields below (transition, style_preset, texture, duotone, background_color, per-item effect) override individual pieces of the chosen look when set.`),
20242
- random_effects: external_exports.boolean().optional().default(true).describe("Shuffle Ken Burns effects across image items (default: true)"),
20243
- transition: external_exports.object({
20244
- type: external_exports.enum(SLIDESHOW_COMPOSITION_TRANSITIONS).optional().describe(`Advanced override: ${SLIDESHOW_COMPOSITION_TRANSITIONS.join(", ")} - overrides the preset look's own transition`),
20245
- duration: external_exports.number().min(SLIDESHOW_TRANSITION_DURATION.min).max(SLIDESHOW_TRANSITION_DURATION.max).optional().describe(`Advanced override: transition duration in seconds (${SLIDESHOW_TRANSITION_DURATION.min}-${SLIDESHOW_TRANSITION_DURATION.max}). For a hard cut use type: 'none' instead - duration is ignored then.`)
20246
- }).optional().describe("Advanced: overrides the preset look's transition"),
20247
- style_preset: STYLE_PRESET_ENUM.optional().describe(`Advanced override: a raw Ken Burns motion preset, overriding the preset look's own motion.`),
20248
- texture: external_exports.object({
20249
- type: external_exports.enum(["checker", "halftone", "scanlines"]).describe("checker = square dither mesh, halftone = dot grid, scanlines = horizontal lines"),
20250
- sizePx: external_exports.number().positive().optional().describe("Pattern cell size in px (default 4)"),
20251
- opacity: external_exports.number().min(0).max(1).optional().describe("Pattern darkness 0-1 (default 0.35)")
20252
- }).optional().describe("Advanced override: print-style pattern overlay on image items, overriding the preset look's own texture - makes stock imagery read as designed (screen-print dither look)"),
20253
- duotone: external_exports.object({
20254
- shadow: external_exports.string().describe("Darker tone (maps to image shadows), e.g. '#1a1633'"),
20255
- highlight: external_exports.string().describe("Lighter tone (maps to image highlights), e.g. '#f5c542'"),
20256
- contrast: external_exports.number().positive().optional().describe("Contrast boost before mapping (default 1.1)")
20257
- }).optional().describe("Advanced override: two-tone editorial recolour on image items, overriding the preset look's own duotone - desaturates then maps to a shadow->highlight palette. Pair with a halftone texture for a screen-print poster look."),
20258
- background_color: external_exports.string().optional().describe("Advanced override: background color, overriding the preset look's own background (default '#000')"),
20259
- wait: external_exports.boolean().optional().default(true).describe("true (default) blocks until the video is ready and returns its URL. false returns a job ID immediately - fire all renders first, then poll clipform_check_render. Use false whenever rendering more than one video."),
20260
- node_id: external_exports.string().optional().describe("Form node to attach this render to automatically once it completes - skips the manual clipform_upload_media_asset + clipform_attach_node_media steps and republishes the form if it's currently live. Requires form_id."),
20261
- form_id: external_exports.string().uuid().optional().describe("The form UUID (required when node_id is set)."),
20262
- captions: external_exports.array(CaptionSchema).optional().describe("Word-level captions from clipform_generate_tts - carried onto the attached media asset. Only used when node_id is set.")
20263
- },
20264
- outputSchema: {
20265
- status: external_exports.enum(["rendering", "complete"]).describe("'rendering' when wait:false (poll check_render); 'complete' with a public_url when wait:true"),
20266
- job_id: external_exports.string().optional().describe("Present when status is 'rendering' - pass to check_render"),
20267
- public_url: external_exports.string().optional().describe("Present when status is 'complete' - attach via upload_media_asset then attach_node_media (fit_media: true), unless node_id was set (auto-attached)"),
20268
- duration_seconds: external_exports.number().optional(),
20269
- attached: external_exports.boolean().optional().describe("True when node_id was provided and the render was attached to the node automatically (present once the attach outcome is known)."),
20270
- media_asset_id: external_exports.string().optional().describe("The workspace media asset created from this render, when attached."),
20271
- republished: external_exports.boolean().optional().describe("True when the form was live and was republished to include this media."),
20272
- attach_error: external_exports.string().optional().describe("Present when node_id was provided but auto-attach failed - the render itself still succeeded.")
20273
- },
20274
- // node_id auto-attach replaces the node's existing media placement and
20275
- // republishes an already-live form - an indirect but real overwrite +
20276
- // publish (#2976 research pass 2), even though this tool works with no
20277
- // node_id too (destructive/openWorld apply "even in only select modes").
20278
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
20279
- },
20280
- async ({ items, audio_url, background_audio_url, background_audio_volume, duration_seconds, preset, random_effects, transition, style_preset, texture, duotone, background_color, wait, node_id, form_id, captions }) => {
20281
- if (node_id && !form_id) {
20282
- return errorResult("form_id is required when node_id is set.");
20283
- }
20284
- const workspace_id = getMcpAuth()?.workspace_id;
20285
- const default_style = style_preset || texture || duotone ? {
20286
- ...style_preset ? { preset: style_preset } : {},
20287
- ...texture ? { texture } : {},
20288
- ...duotone ? { duotone } : {}
20289
- } : void 0;
20290
- const apiCall = () => callApi("/internal/generate-video", {
20291
- timeoutMs: 3e5,
20292
- // local renders pay a cold webpack bundle; Lambda is fast but bursty
20293
- body: {
20294
- items,
20295
- audio_url,
20296
- background_audio_url,
20297
- background_audio_volume,
20298
- duration_seconds,
20299
- // preset expands to its base transition/style/background on the
20300
- // API side (expandVideoLook); transition/default_style/background_color
20301
- // below are only sent when the caller actually set the advanced
20302
- // override, so the API can tell "explicit" apart from "unset" and
20303
- // apply the look's own base value instead (#4038).
20304
- preset,
20305
- random_effects: random_effects ?? true,
20306
- transition,
20307
- default_style,
20308
- background_color,
20309
- workspace_id
20310
- }
20311
- });
20312
- if (wait === false) {
20313
- const job = createJob("clipform_generate_video");
20314
- const placeholderPromise2 = node_id && form_id ? createRenderPlaceholder({ formId: form_id, nodeId: node_id, mediaType: "video" }) : Promise.resolve(void 0);
20315
- apiCall().then(async (r) => {
20316
- if (!r.ok) {
20317
- failJob(job.id, r.error);
20318
- const placeholder = await placeholderPromise2;
20319
- if (placeholder) await failPlaceholder(placeholder, r.error);
20320
- return;
20321
- }
20322
- let attach2;
20323
- if (node_id && form_id) {
20324
- const publicUrl = r.data.public_url;
20325
- const placeholder = await placeholderPromise2;
20326
- if (publicUrl) {
20327
- attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType: "video", captions, captionRef: audio_url, placeholder });
20328
- } else {
20329
- const message = "Render completed without a public_url; nothing to attach.";
20330
- attach2 = { attached: false, error: message };
20331
- if (placeholder) await failPlaceholder(placeholder, message);
20332
- }
20333
- }
20334
- completeJob(job.id, r.data, attach2);
20335
- }).catch(async (err) => {
20336
- const message = err instanceof Error ? err.message : String(err);
20337
- failJob(job.id, message);
20338
- const placeholder = await placeholderPromise2;
20339
- if (placeholder) await failPlaceholder(placeholder, message);
20340
- });
20341
- return structuredResult(
20342
- [
20343
- `Render started (${items.length} item${items.length > 1 ? "s" : ""}).`,
20344
- `Job ID: ${job.id}`,
20345
- node_id ? `Will attach to node ${node_id} automatically once it completes - no need to poll to completion.` : `Fire any remaining renders now, then poll clipform_check_render. Renders typically take ${RENDER_TIMING.expectedRange}.`
20346
- ].join("\n"),
20347
- { status: "rendering", job_id: job.id }
20348
- );
20349
- }
20350
- const placeholderPromise = node_id && form_id ? createRenderPlaceholder({ formId: form_id, nodeId: node_id, mediaType: "video" }) : Promise.resolve(void 0);
20351
- const result = await apiCall();
20352
- if (!result.ok) {
20353
- const placeholder = await placeholderPromise;
20354
- if (placeholder) await failPlaceholder(placeholder, result.error);
20355
- return errorResult(result.error);
20356
- }
20357
- const data = result.data;
20358
- let attach;
20359
- if (node_id && form_id) {
20360
- const placeholder = await placeholderPromise;
20361
- if (data.public_url) {
20362
- attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType: "video", captions, captionRef: audio_url, placeholder });
20363
- } else if (placeholder) {
20364
- await failPlaceholder(placeholder, "Render completed without a public_url; nothing to attach.");
20365
- }
20366
- }
20367
- return structuredResult(
20368
- [
20369
- `Video rendered (${items.length} item${items.length > 1 ? "s" : ""}).`,
20370
- `Public URL: ${data.public_url}`,
20371
- data.duration_seconds ? `Duration: ${data.duration_seconds}s` : "",
20372
- attach ? attachSummaryLine(node_id, attach) : `Attach via clipform_upload_media_asset then clipform_attach_node_media with fit_media: true (composed 9:16 frame - contain, never crop).`
20373
- ].filter(Boolean).join("\n"),
20374
- {
20375
- status: "complete",
20376
- ...data.public_url ? { public_url: data.public_url } : {},
20377
- ...typeof data.duration_seconds === "number" ? { duration_seconds: data.duration_seconds } : {},
20378
- ...attachStructuredFields(attach)
20379
- }
20380
- );
20381
- }
20382
- );
20383
- }
20384
-
20385
20207
  // src/tools/check-render.ts
20386
20208
  function projectJob(id, job) {
20387
20209
  if (!job) return { job_id: id, status: "not_found" };
@@ -20404,7 +20226,7 @@ var LOST_JOB_RECOVERY = [
20404
20226
  `Jobs expire after 30 minutes and are lost if the server restarts - the render itself may still have completed.`,
20405
20227
  `To recover, re-run the original render call with identical arguments:`,
20406
20228
  `- clipform_render_composition results are content-cached: a finished render returns its URL immediately without re-rendering.`,
20407
- `- clipform_generate_video is not cached and will re-render.`
20229
+ `- clipform_render_video_template results are content-cached too: re-running with identical controls returns the finished URL.`
20408
20230
  ].join("\n");
20409
20231
  function describeJob(id, job) {
20410
20232
  if (!job) {
@@ -20430,7 +20252,7 @@ function registerCheckRenderTool(server) {
20430
20252
  "clipform_check_render",
20431
20253
  {
20432
20254
  title: "Check Render Status",
20433
- description: `Check the status of render jobs started by clipform_generate_video, clipform_render_video_template, or clipform_render_composition.
20255
+ description: `Check the status of render jobs started by clipform_render_video_template or clipform_render_composition.
20434
20256
 
20435
20257
  Pass job_ids to check a whole batch in ONE call - one line of status per job. Pass job_id for a single job. Returns the output URL for each completed render. Typical render time: 10-60 seconds. Attach is automatic when node_id was provided to the render tool - no need to poll to completion, and the attach outcome is reported here once known.`,
20436
20258
  inputSchema: {
@@ -20840,7 +20662,6 @@ function createServer(options) {
20840
20662
  registerLogGenerationTool(server);
20841
20663
  registerSearchNewsTool(server);
20842
20664
  registerGenerateTtsTool(server);
20843
- registerGenerateVideoTool(server);
20844
20665
  registerSearchMediaTool(server);
20845
20666
  registerRenderCompositionTool(server);
20846
20667
  registerSearchMusicTool(server);
@@ -20864,4 +20685,4 @@ export {
20864
20685
  buildInstructions,
20865
20686
  createServer
20866
20687
  };
20867
- //# sourceMappingURL=chunk-HBKYE4JQ.js.map
20688
+ //# sourceMappingURL=chunk-QBPE32AV.js.map