@clipform/mcp-server 2.11.1 → 2.13.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-WXZB54NP.js";
9
+ } from "./chunk-SHOP5J2M.js";
10
10
  import {
11
11
  GUIDE_TYPES,
12
12
  QUIZ_VARIANTS,
@@ -15,7 +15,7 @@ import {
15
15
  getGuideUri,
16
16
  guideFallbackText,
17
17
  registerResources
18
- } from "./chunk-LWHXSMFA.js";
18
+ } from "./chunk-JHCN23OK.js";
19
19
  import {
20
20
  ACTIVE_NODE_TYPES,
21
21
  CONTACT_FIELDS,
@@ -39,7 +39,7 @@ import {
39
39
  resolveFormType,
40
40
  structuredResult,
41
41
  textResult
42
- } from "./chunk-NJIIT3HX.js";
42
+ } from "./chunk-YSLAOZBU.js";
43
43
  import {
44
44
  getMcpAuth,
45
45
  runWithMcpTool
@@ -18117,7 +18117,8 @@ var REDACTED_TYPES = {
18117
18117
  details: "[contact details - view in dashboard]",
18118
18118
  contact: "[contact details - view in dashboard]",
18119
18119
  file_upload: "[file upload - view in dashboard]",
18120
- payment: "[payment - view in dashboard]",
18120
+ // payment is handled by paymentSummary() before this map - it surfaces a
18121
+ // non-PII amount/status line rather than a flat placeholder.
18121
18122
  open: ""
18122
18123
  // open is redacted ONLY when the answer is a recording (handled below)
18123
18124
  };
@@ -18127,12 +18128,21 @@ function truncate(s, n = MAX_ANSWER_LENGTH) {
18127
18128
  function looksLikeMediaReference(display) {
18128
18129
  return /https?:\/\/|storage\/|\.(webm|mp4|mp3|wav|m4a|mov)\b/i.test(display);
18129
18130
  }
18131
+ function paymentSummary(value) {
18132
+ const stripe = value?.stripe;
18133
+ if (!stripe) return null;
18134
+ const amount = typeof stripe.amount === "number" ? stripe.amount : null;
18135
+ const currency = typeof stripe.currency === "string" ? stripe.currency : null;
18136
+ const status = typeof stripe.status === "string" ? stripe.status : "unknown";
18137
+ if (amount === null || !currency) return null;
18138
+ return `${(amount / 100).toFixed(2)} ${currency.toUpperCase()} - ${status}`;
18139
+ }
18130
18140
  function registerGetResponsesTool(server) {
18131
18141
  server.registerTool(
18132
18142
  "clipform_get_responses",
18133
18143
  {
18134
18144
  title: "List form responses",
18135
- description: "List a Clipform form's individual responses (submissions) with time filtering - complements clipform_get_results (aggregates). Answers questions like 'what came in during the last 24 hours', 'what's the longest answer', 'what did people say'. Text answers only: contact details, file uploads, payments, and audio/video answers are redacted to placeholders; those are viewable in the dashboard (the responses URL from clipform_get_form). Responses are sorted newest first unless sort: 'oldest'.",
18145
+ description: "List a Clipform form's individual responses (submissions) with time filtering - complements clipform_get_results (aggregates). Answers questions like 'what came in during the last 24 hours', 'what's the longest answer', 'what did people say'. Text answers plus a non-PII payment summary (amount and status); contact details, file uploads, and audio/video answers are redacted to placeholders and the customer's payment details stay in the dashboard (the responses URL from clipform_get_form). Responses are sorted newest first unless sort: 'oldest'.",
18136
18146
  inputSchema: {
18137
18147
  form_id: external_exports.string().describe("Form UUID (from clipform_list_forms / clipform_create_form)"),
18138
18148
  since: external_exports.string().datetime({ offset: true }).optional().describe("Only responses submitted at or after this ISO 8601 time (e.g. '2026-08-29T00:00:00Z')"),
@@ -18200,6 +18210,15 @@ function registerGetResponsesTool(server) {
18200
18210
  const node = nodeById.get(nodeId);
18201
18211
  const type = node?.type ?? (typeof resp?.type === "string" ? resp.type : "unknown");
18202
18212
  const question = truncate(node?.prompt || type, 120);
18213
+ if (type === "payment") {
18214
+ const summary = paymentSummary(resp?.value);
18215
+ answers.push({
18216
+ question,
18217
+ type,
18218
+ answer: summary ?? "[payment - view in dashboard]"
18219
+ });
18220
+ continue;
18221
+ }
18203
18222
  const placeholder = REDACTED_TYPES[type];
18204
18223
  if (placeholder) {
18205
18224
  answers.push({ question, type, answer: placeholder });
@@ -18242,7 +18261,7 @@ function registerGetResponsesTool(server) {
18242
18261
  ...r.answers.map((a) => ` ${a.question}: ${a.answer}`)
18243
18262
  ]),
18244
18263
  ``,
18245
- `Contact details, files, payments, and recordings are redacted here - the dashboard shows full response detail. For aggregate stats use clipform_get_results.`
18264
+ `Contact details, files, and recordings are redacted here; payments show an amount/status summary only (the customer's details stay in the dashboard, which shows full response detail). For aggregate stats use clipform_get_results.`
18246
18265
  ];
18247
18266
  return structuredResult(lines.join("\n"), {
18248
18267
  total_matching: totalMatching,
@@ -18516,6 +18535,7 @@ var MediaAssetItemSchema = external_exports.object({
18516
18535
  ).min(1).describe("Per-word timestamps within the segment. Required - copy the full array from clipform_generate_tts verbatim.")
18517
18536
  })
18518
18537
  ).optional().describe("Word-level captions from clipform_generate_tts. `words` is a required field on each caption (schema-enforced) - pass the full objects returned by clipform_generate_tts unmodified."),
18538
+ 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."),
18519
18539
  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.")
18520
18540
  });
18521
18541
  function registerUploadMediaAssetTool(server) {
@@ -18525,7 +18545,7 @@ function registerUploadMediaAssetTool(server) {
18525
18545
  title: "Upload Media Asset",
18526
18546
  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.
18527
18547
 
18528
- When a public URL is provided, the media is fetched and stored automatically. For video: ingested via Mux. For image: stored in Supabase. Captions from clipform_generate_tts enable per-word highlighting in the viewer once attached.`,
18548
+ 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.`,
18529
18549
  inputSchema: {
18530
18550
  items: external_exports.array(MediaAssetItemSchema).min(1).max(10).describe("One or more media items to create as workspace library assets")
18531
18551
  },
@@ -18573,6 +18593,7 @@ When a public URL is provided, the media is fetched and stored automatically. Fo
18573
18593
  };
18574
18594
  if (item.url) createBody.url = item.url;
18575
18595
  if (item.captions) createBody.captions = item.captions;
18596
+ if (item.caption_ref) createBody.caption_ref = item.caption_ref;
18576
18597
  if (item.ai_generated !== void 0) createBody.ai_generated = item.ai_generated;
18577
18598
  const created = await callApi(`/workspaces/${workspaceId}/media`, { method: "POST", body: createBody });
18578
18599
  if (!created.ok) {
@@ -18610,7 +18631,9 @@ When a public URL is provided, the media is fetched and stored automatically. Fo
18610
18631
  } else {
18611
18632
  resultLines.push(`Media stored in the workspace library.`);
18612
18633
  }
18613
- if (item.captions) {
18634
+ if (item.caption_ref) {
18635
+ resultLines.push(`Captions resolved from caption_ref.`);
18636
+ } else if (item.captions) {
18614
18637
  resultLines.push(`Captions: ${item.captions.length} segments saved`);
18615
18638
  }
18616
18639
  lines.push(resultLines.join("\n"));
@@ -19063,7 +19086,7 @@ Available voices: ryan (British male, clear), sonia (British female, warm), andr
19063
19086
 
19064
19087
  Use the tone parameter to direct HOW the voice speaks. Always set a tone that matches the form's mood - e.g. quizzes: "Energetic and playful, like a quiz show host teasing the audience", surveys: "Professional but warm, encouraging honest answers", personality quizzes: "Curious and reflective". This dramatically improves the narration quality.
19065
19088
 
19066
- Pass one item or many (max 10) - multiple items run in parallel. Returns audio URL and word-level captions per item.`,
19089
+ Pass one item or many (max 10) - multiple items run in parallel. Returns audio_url and caption_ref per item - pass caption_ref downstream to clipform_render_composition / clipform_render_video_template / clipform_upload_media_asset to attach this run's word-level captions instead of hand-copying the captions array.`,
19067
19090
  inputSchema: {
19068
19091
  items: external_exports.array(TtsItemSchema).min(1).max(10).describe("One or more TTS items to generate")
19069
19092
  },
@@ -19072,7 +19095,8 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
19072
19095
  external_exports.object({
19073
19096
  ok: external_exports.boolean(),
19074
19097
  audio_url: external_exports.string().optional(),
19075
- captions: external_exports.array(CaptionSchema2).optional().describe("Pass as the captions param to upload_media_asset"),
19098
+ caption_ref: external_exports.string().optional().describe("Pass this as the caption_ref param on clipform_render_composition / clipform_render_video_template / clipform_upload_media_asset to attach this run's word-level captions - never hand-copy the captions array."),
19099
+ captions: external_exports.array(CaptionSchema2).optional().describe("Word-level captions, for reference only - prefer passing caption_ref downstream instead of copying this array."),
19076
19100
  error: external_exports.string().optional()
19077
19101
  })
19078
19102
  ).describe("One result per item, in order")
@@ -19099,6 +19123,9 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
19099
19123
  successCount++;
19100
19124
  const data = r.value.data;
19101
19125
  lines.push(`Audio URL: ${data.audioUrl}`);
19126
+ if (data.captionRef) {
19127
+ lines.push(`Caption ref: ${data.captionRef} (pass as caption_ref to clipform_render_composition / clipform_render_video_template / clipform_upload_media_asset)`);
19128
+ }
19102
19129
  const rawCaptions = Array.isArray(data.captions) ? data.captions : [];
19103
19130
  const captions = rawCaptions.map((c) => ({
19104
19131
  start: c.start,
@@ -19110,6 +19137,7 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
19110
19137
  structured.push({
19111
19138
  ok: true,
19112
19139
  ...data.audioUrl ? { audio_url: String(data.audioUrl) } : {},
19140
+ ...data.captionRef ? { caption_ref: String(data.captionRef) } : {},
19113
19141
  captions
19114
19142
  });
19115
19143
  } else {
@@ -19125,7 +19153,7 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
19125
19153
  lines.unshift(`TTS: ${successCount}/${items.length} succeeded
19126
19154
  `);
19127
19155
  }
19128
- lines.push(`Pass the complete Captions JSON as the "captions" parameter when uploading.`);
19156
+ lines.push(`Pass caption_ref downstream (to clipform_render_composition / clipform_render_video_template / clipform_upload_media_asset) instead of hand-copying the captions array.`);
19129
19157
  if (successCount === 0) return errorResult(lines.join("\n"));
19130
19158
  return structuredResult(lines.join("\n"), { results: structured });
19131
19159
  }
@@ -19382,6 +19410,7 @@ async function completeViaPlaceholder(placeholder, input) {
19382
19410
  try {
19383
19411
  const body = { url: input.publicUrl };
19384
19412
  if (input.captions?.length) body.captions = input.captions;
19413
+ if (input.captionRef) body.caption_ref = input.captionRef;
19385
19414
  const completed = await callApi(`/workspaces/${placeholder.workspaceId}/media/${placeholder.assetId}/complete`, {
19386
19415
  method: "POST",
19387
19416
  body
@@ -19410,6 +19439,7 @@ async function legacyAutoAttach(input) {
19410
19439
  url: input.publicUrl
19411
19440
  };
19412
19441
  if (input.captions?.length) createBody.captions = input.captions;
19442
+ if (input.captionRef) createBody.caption_ref = input.captionRef;
19413
19443
  const created = await callApi(`/workspaces/${workspaceId}/media`, { method: "POST", body: createBody });
19414
19444
  if (!created.ok) return { attached: false, error: `Failed to create media asset: ${created.error}` };
19415
19445
  const mediaAssetId = created.data?.media_asset_id;
@@ -19455,7 +19485,8 @@ var RenderItemSchema = external_exports.object({
19455
19485
  outputFormat: external_exports.enum(["mp4", "png"]).default("mp4").describe("Output format (default: mp4)"),
19456
19486
  inputProps: external_exports.record(external_exports.unknown()).optional().describe("Props matching the composition's schema - validated strictly"),
19457
19487
  node_id: external_exports.string().optional().describe("Form node to attach this item's render to automatically once it completes. Requires the batch's top-level form_id."),
19458
- 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.")
19488
+ 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."),
19489
+ 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.")
19459
19490
  });
19460
19491
  function fireRender(item, formId) {
19461
19492
  const job = createJob("clipform_render_composition");
@@ -19487,6 +19518,7 @@ function fireRender(item, formId) {
19487
19518
  publicUrl,
19488
19519
  mediaType,
19489
19520
  captions: item.captions,
19521
+ captionRef: item.caption_ref,
19490
19522
  placeholder
19491
19523
  });
19492
19524
  } else {
@@ -19525,7 +19557,8 @@ ${RENDER_ATTACH_RULE} In batch mode, set node_id per item (see items) and form_i
19525
19557
  items: external_exports.array(RenderItemSchema).min(1).max(10).optional().describe("Batch mode: multiple renders in one call. All fire in parallel; returns one job ID per item - collect with clipform_check_render (job_ids). Use this whenever rendering more than one clip."),
19526
19558
  node_id: external_exports.string().optional().describe("Single render only: form node to attach this render to automatically once it completes. Requires form_id. For batch mode, set node_id per item instead."),
19527
19559
  form_id: external_exports.string().uuid().optional().describe("The form UUID - required when node_id is set (single render) or any item sets node_id (batch)."),
19528
- captions: external_exports.array(CaptionSchema).optional().describe("Single render only: word-level captions from clipform_generate_tts, carried onto the attached media asset. Only used when node_id is set. For batch mode, set captions per item instead.")
19560
+ captions: external_exports.array(CaptionSchema).optional().describe("Single render only: word-level captions from clipform_generate_tts, carried onto the attached media asset. Only used when node_id is set. For batch mode, set captions per item instead."),
19561
+ caption_ref: external_exports.string().optional().describe("Single render only: 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. For batch mode, set caption_ref per item instead.")
19529
19562
  },
19530
19563
  outputSchema: {
19531
19564
  jobs: external_exports.array(
@@ -19548,7 +19581,7 @@ ${RENDER_ATTACH_RULE} In batch mode, set node_id per item (see items) and form_i
19548
19581
  // node_id too (destructive/openWorld apply "even in only select modes").
19549
19582
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
19550
19583
  },
19551
- async ({ compositionId, outputFormat, inputProps, wait, items, node_id, form_id, captions }) => {
19584
+ async ({ compositionId, outputFormat, inputProps, wait, items, node_id, form_id, captions, caption_ref }) => {
19552
19585
  if (items?.length && compositionId) {
19553
19586
  return errorResult("Pass EITHER items (batch) OR compositionId (single render), not both.");
19554
19587
  }
@@ -19585,7 +19618,7 @@ ${RENDER_ATTACH_RULE} In batch mode, set node_id per item (see items) and form_i
19585
19618
  );
19586
19619
  }
19587
19620
  if (wait === false) {
19588
- const job = fireRender({ compositionId, outputFormat, inputProps, node_id, captions }, form_id);
19621
+ const job = fireRender({ compositionId, outputFormat, inputProps, node_id, captions, caption_ref }, form_id);
19589
19622
  return structuredResult(
19590
19623
  [
19591
19624
  `Render started (${compositionId}).`,
@@ -19619,6 +19652,7 @@ ${RENDER_ATTACH_RULE} In batch mode, set node_id per item (see items) and form_i
19619
19652
  publicUrl: data.public_url,
19620
19653
  mediaType: mediaTypeSingle,
19621
19654
  captions,
19655
+ captionRef: caption_ref,
19622
19656
  placeholder
19623
19657
  });
19624
19658
  } else if (placeholder) {
@@ -19874,7 +19908,8 @@ ${RENDER_ATTACH_RULE}`,
19874
19908
  wait: external_exports.boolean().optional().default(true).describe("true (default) blocks until the render is ready and returns its URL; false returns a job ID for clipform_check_render."),
19875
19909
  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."),
19876
19910
  form_id: external_exports.string().uuid().optional().describe("The form UUID (required when node_id is set)."),
19877
- 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.")
19911
+ 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."),
19912
+ 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.")
19878
19913
  },
19879
19914
  outputSchema: {
19880
19915
  status: external_exports.enum(["rendering", "complete"]),
@@ -19893,7 +19928,7 @@ ${RENDER_ATTACH_RULE}`,
19893
19928
  // node_id too (destructive/openWorld apply "even in only select modes").
19894
19929
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
19895
19930
  },
19896
- async ({ template, controls, outputFormat, wait, node_id, form_id, captions }) => {
19931
+ async ({ template, controls, outputFormat, wait, node_id, form_id, captions, caption_ref }) => {
19897
19932
  if (node_id && !form_id) {
19898
19933
  return errorResult("form_id is required when node_id is set.");
19899
19934
  }
@@ -19917,7 +19952,7 @@ ${RENDER_ATTACH_RULE}`,
19917
19952
  const publicUrl = r.data.public_url;
19918
19953
  const placeholder = await placeholderPromise2;
19919
19954
  if (publicUrl) {
19920
- attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType, captions, placeholder });
19955
+ attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType, captions, captionRef: caption_ref, placeholder });
19921
19956
  } else {
19922
19957
  const message = "Render completed without a public_url; nothing to attach.";
19923
19958
  attach2 = { attached: false, error: message };
@@ -19955,7 +19990,7 @@ ${RENDER_ATTACH_RULE}`,
19955
19990
  if (node_id && form_id) {
19956
19991
  const placeholder = await placeholderPromise;
19957
19992
  if (data.public_url) {
19958
- attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType, captions, placeholder });
19993
+ attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType, captions, captionRef: caption_ref, placeholder });
19959
19994
  } else if (placeholder) {
19960
19995
  await failPlaceholder(placeholder, "Render completed without a public_url; nothing to attach.");
19961
19996
  }
@@ -20146,7 +20181,7 @@ ${RENDER_ATTACH_RULE}`,
20146
20181
  const publicUrl = r.data.public_url;
20147
20182
  const placeholder = await placeholderPromise2;
20148
20183
  if (publicUrl) {
20149
- attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType: "video", captions, placeholder });
20184
+ attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType: "video", captions, captionRef: audio_url, placeholder });
20150
20185
  } else {
20151
20186
  const message = "Render completed without a public_url; nothing to attach.";
20152
20187
  attach2 = { attached: false, error: message };
@@ -20181,7 +20216,7 @@ ${RENDER_ATTACH_RULE}`,
20181
20216
  if (node_id && form_id) {
20182
20217
  const placeholder = await placeholderPromise;
20183
20218
  if (data.public_url) {
20184
- attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType: "video", captions, placeholder });
20219
+ attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType: "video", captions, captionRef: audio_url, placeholder });
20185
20220
  } else if (placeholder) {
20186
20221
  await failPlaceholder(placeholder, "Render completed without a public_url; nothing to attach.");
20187
20222
  }
@@ -20685,4 +20720,4 @@ export {
20685
20720
  buildInstructions,
20686
20721
  createServer
20687
20722
  };
20688
- //# sourceMappingURL=chunk-E46OAHEX.js.map
20723
+ //# sourceMappingURL=chunk-66KZWBI7.js.map