@clipform/mcp-server 2.16.1 → 2.17.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-IXRZG3GB.js";
9
+ } from "./chunk-2EPNC4AY.js";
10
10
  import {
11
11
  GUIDE_TYPES,
12
12
  QUIZ_VARIANTS,
@@ -15,13 +15,14 @@ import {
15
15
  getGuideUri,
16
16
  guideFallbackText,
17
17
  registerResources
18
- } from "./chunk-25LEEQMI.js";
18
+ } from "./chunk-5TQU2XJ7.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,
25
26
  KEN_BURNS_EFFECTS,
26
27
  KEN_BURNS_FIT_MODES,
27
28
  KEN_BURNS_PRESETS,
@@ -30,6 +31,7 @@ import {
30
31
  NODE_TYPES,
31
32
  NON_COUNTABLE_TYPES,
32
33
  SLIDESHOW_COMPOSITION_TRANSITIONS,
34
+ SLIDESHOW_TRANSITION_DURATION,
33
35
  VIDEO_LOOK_NAMES,
34
36
  bfsOrder,
35
37
  callApi,
@@ -37,10 +39,11 @@ import {
37
39
  defaultTarget,
38
40
  errorResult,
39
41
  exposedCompositionIds,
42
+ isHttpUrl,
40
43
  resolveFormType,
41
44
  structuredResult,
42
45
  textResult
43
- } from "./chunk-Z4ZGF6OV.js";
46
+ } from "./chunk-FIUAXSVQ.js";
44
47
  import {
45
48
  getMcpAuth,
46
49
  runWithMcpTool
@@ -18604,6 +18607,37 @@ var MediaAssetItemSchema = external_exports.object({
18604
18607
  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 this asset, resolved server-side. Preferred over captions."),
18605
18608
  ai_generated: external_exports.boolean().optional().describe("Whether this media was AI-generated. Drives the viewer's disclosure chip - set true for AI-rendered/synthesized media.")
18606
18609
  });
18610
+ var FileEntrySchema = external_exports.union([
18611
+ external_exports.string(),
18612
+ external_exports.object({
18613
+ download_url: external_exports.string().describe("Short-lived URL the server downloads the file's bytes from"),
18614
+ file_id: external_exports.string().optional().describe("Host-assigned identifier for the attached file"),
18615
+ mime_type: external_exports.string().optional().describe("Declared MIME type - determines whether the file becomes a video or still asset"),
18616
+ name: external_exports.string().optional(),
18617
+ file_name: external_exports.string().optional(),
18618
+ size: external_exports.number().optional()
18619
+ }).passthrough()
18620
+ ]);
18621
+ function mapFileEntryToItem(file) {
18622
+ if (typeof file === "string") {
18623
+ return {
18624
+ ok: false,
18625
+ error: "The attached file was not passed as a downloadable file. Attach it again or provide a direct URL."
18626
+ };
18627
+ }
18628
+ const mimeType = file.mime_type ?? "";
18629
+ const category = mimeType.split("/")[0];
18630
+ if (category === "image") {
18631
+ return { ok: true, item: { media_type: "still", media_source: "uploaded", url: file.download_url } };
18632
+ }
18633
+ if (category === "video") {
18634
+ return { ok: true, item: { media_type: "video", media_source: "uploaded", url: file.download_url } };
18635
+ }
18636
+ return {
18637
+ ok: false,
18638
+ error: `Attached file type "${mimeType || "unknown"}" is not supported. Attach an image or video file, or provide a direct URL instead.`
18639
+ };
18640
+ }
18607
18641
  function registerUploadMediaAssetTool(server) {
18608
18642
  server.registerTool(
18609
18643
  "clipform_upload_media_asset",
@@ -18611,9 +18645,12 @@ function registerUploadMediaAssetTool(server) {
18611
18645
  title: "Upload Media Asset",
18612
18646
  description: `Put one or more media files into your workspace media library (max 10, uploaded sequentially). This is step one of attaching media to a node - follow up with clipform_attach_node_media to place the returned media_asset_id on a node.
18613
18647
 
18614
- When a public URL is provided, the media is fetched and stored automatically. For video: ingested via Mux. For image: stored in Supabase. Word-level captions from clipform_generate_tts enable per-word highlighting in the viewer once attached - pass the caption_ref it returned instead of hand-copying the captions array.`,
18648
+ When a public URL is provided, the media is fetched and stored automatically. For video: ingested via Mux. For image: stored in Supabase. Word-level captions from clipform_generate_tts enable per-word highlighting in the viewer once attached - pass the caption_ref it returned instead of hand-copying the captions array.
18649
+
18650
+ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by the host client), or both. At least one is required.`,
18615
18651
  inputSchema: {
18616
- items: external_exports.array(MediaAssetItemSchema).min(1).max(10).describe("One or more media items to create as workspace library assets")
18652
+ items: external_exports.array(MediaAssetItemSchema).min(1).max(10).optional().describe("One or more media items to create as workspace library assets"),
18653
+ files: external_exports.array(FileEntrySchema).min(1).max(10).optional().describe("Files attached by the host client. Each becomes a video or still library asset based on its declared mime_type.")
18617
18654
  },
18618
18655
  outputSchema: {
18619
18656
  results: external_exports.array(
@@ -18633,9 +18670,20 @@ When a public URL is provided, the media is fetched and stored automatically. Fo
18633
18670
  destructiveHint: false,
18634
18671
  idempotentHint: false,
18635
18672
  openWorldHint: false
18636
- }
18673
+ },
18674
+ // Apps SDK convention (openai-apps-sdk-examples#185): a host client only
18675
+ // hydrates a file attachment into a TOP-LEVEL tool argument named here -
18676
+ // same mechanism create-form.ts uses for anthropic/alwaysLoad.
18677
+ _meta: { "openai/fileParams": ["files"] }
18637
18678
  },
18638
- async ({ items }) => {
18679
+ async ({ items, files }) => {
18680
+ if ((!items || items.length === 0) && (!files || files.length === 0)) {
18681
+ return errorResult("Provide at least one media item via `items` or `files`.");
18682
+ }
18683
+ const totalEntryCount = (items?.length ?? 0) + (files?.length ?? 0);
18684
+ if (totalEntryCount > 10) {
18685
+ return errorResult(`Provide at most 10 media items total across \`items\` and \`files\` (got ${totalEntryCount}).`);
18686
+ }
18639
18687
  const meResult = await callApi("/me", { method: "GET" });
18640
18688
  if (!meResult.ok) {
18641
18689
  return errorResult(`Unable to determine workspace: ${meResult.error}`);
@@ -18647,12 +18695,27 @@ When a public URL is provided, the media is fetched and stored automatically. Fo
18647
18695
  "No workspace available. This usually means authentication is misconfigured - check your API key or OAuth connection."
18648
18696
  );
18649
18697
  }
18698
+ const entries = [
18699
+ ...(items ?? []).map((item) => ({ ok: true, item })),
18700
+ ...(files ?? []).map((file) => {
18701
+ const mapped = mapFileEntryToItem(file);
18702
+ return mapped.ok ? { ok: true, item: mapped.item } : { ok: false, media_type: "unknown", error: mapped.error };
18703
+ })
18704
+ ];
18650
18705
  const lines = [];
18651
18706
  const results = [];
18652
18707
  let successCount = 0;
18653
- for (let i = 0; i < items.length; i++) {
18654
- const item = items[i];
18655
- if (items.length > 1) lines.push(`--- Item ${i + 1} (${item.media_type}) ---`);
18708
+ for (let i = 0; i < entries.length; i++) {
18709
+ const entry = entries[i];
18710
+ const entryMediaType = entry.ok ? entry.item.media_type : entry.media_type;
18711
+ if (entries.length > 1) lines.push(`--- Item ${i + 1} (${entryMediaType}) ---`);
18712
+ if (!entry.ok) {
18713
+ lines.push(`FAILED: ${entry.error}`);
18714
+ lines.push("");
18715
+ results.push({ media_type: entry.media_type, stored: false, ok: false, error: entry.error });
18716
+ continue;
18717
+ }
18718
+ const item = entry.item;
18656
18719
  const createBody = {
18657
18720
  media_type: item.media_type,
18658
18721
  media_source: item.media_source
@@ -18705,8 +18768,8 @@ When a public URL is provided, the media is fetched and stored automatically. Fo
18705
18768
  lines.push(resultLines.join("\n"));
18706
18769
  lines.push("");
18707
18770
  }
18708
- if (items.length > 1) {
18709
- lines.unshift(`Media asset upload: ${successCount}/${items.length} succeeded
18771
+ if (entries.length > 1) {
18772
+ lines.unshift(`Media asset upload: ${successCount}/${entries.length} succeeded
18710
18773
  `);
18711
18774
  }
18712
18775
  lines.push(`Attach these to nodes with clipform_attach_node_media.`);
@@ -19760,12 +19823,12 @@ function registerSearchMusicTool(server) {
19760
19823
  title: "Search Music",
19761
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.`,
19762
19825
  inputSchema: {
19763
- query: external_exports.string().describe("What to search for (e.g. 'upbeat quiz background', 'calm ambient', 'playful pizzicato')"),
19764
- count: external_exports.number().min(1).max(10).default(5).optional().describe("Max results (default: 5)"),
19826
+ query: external_exports.string().min(1).describe("What to search for (e.g. 'upbeat quiz background', 'calm ambient', 'playful pizzicato')"),
19827
+ count: external_exports.number().int().min(1).max(10).default(5).optional().describe("Max results (default: 5)"),
19765
19828
  instrumentalOnly: external_exports.boolean().optional().default(true).describe("Only instrumental tracks (default: true)"),
19766
- minDuration: external_exports.number().optional().describe("Minimum duration in seconds"),
19767
- maxDuration: external_exports.number().optional().describe("Maximum duration in seconds"),
19768
- tags: external_exports.array(external_exports.string()).optional().describe("Genre/mood tags to filter by")
19829
+ minDuration: external_exports.number().positive().optional().describe("Minimum duration in seconds"),
19830
+ maxDuration: external_exports.number().positive().optional().describe("Maximum duration in seconds"),
19831
+ tags: external_exports.array(external_exports.string()).optional().describe("Genre/mood tags to filter by. Multiple tags are ANDed together (e.g. ['ambient', 'chill'] matches tracks tagged both).")
19769
19832
  },
19770
19833
  outputSchema: {
19771
19834
  query: external_exports.string(),
@@ -20144,9 +20207,7 @@ function registerListAssetsTool(server) {
20144
20207
  }
20145
20208
 
20146
20209
  // src/tools/generate-video.ts
20147
- var httpUrl = () => external_exports.string().url().refine((v) => /^https?:\/\//i.test(v), {
20148
- message: "URL must start with http:// or https://"
20149
- });
20210
+ var httpUrl = () => external_exports.string().url().refine(isHttpUrl, { message: HTTP_URL_MESSAGE });
20150
20211
  function registerGenerateVideoTool(server) {
20151
20212
  server.registerTool(
20152
20213
  "clipform_generate_video",
@@ -20181,7 +20242,7 @@ ${RENDER_ATTACH_RULE}`,
20181
20242
  random_effects: external_exports.boolean().optional().default(true).describe("Shuffle Ken Burns effects across image items (default: true)"),
20182
20243
  transition: external_exports.object({
20183
20244
  type: external_exports.enum(SLIDESHOW_COMPOSITION_TRANSITIONS).optional().describe(`Advanced override: ${SLIDESHOW_COMPOSITION_TRANSITIONS.join(", ")} - overrides the preset look's own transition`),
20184
- duration: external_exports.number().optional().describe("Advanced override: transition duration in seconds")
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.`)
20185
20246
  }).optional().describe("Advanced: overrides the preset look's transition"),
20186
20247
  style_preset: STYLE_PRESET_ENUM.optional().describe(`Advanced override: a raw Ken Burns motion preset, overriding the preset look's own motion.`),
20187
20248
  texture: external_exports.object({
@@ -20803,4 +20864,4 @@ export {
20803
20864
  buildInstructions,
20804
20865
  createServer
20805
20866
  };
20806
- //# sourceMappingURL=chunk-TJFD7DVF.js.map
20867
+ //# sourceMappingURL=chunk-HBKYE4JQ.js.map