@clipform/mcp-server 1.37.0 → 1.39.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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @clipform/mcp-server
1
+ # @clipform/mcp-server
2
2
 
3
3
  MCP server for [Clipform](https://clipform.io) - build and manage video-style forms from any MCP client (Claude, ChatGPT, Cursor, Windsurf, etc.).
4
4
 
@@ -10,9 +10,9 @@ There are two ways to connect: **remote** (recommended, uses your Clipform accou
10
10
 
11
11
  Use this if you have a Clipform account. Forms are created directly in your workspace and your plan tier applies (no 3-node cap on Pro).
12
12
 
13
- **Claude (claude.ai):** Settings Connectors Add custom connector enter `https://mcp.clipform.io`
13
+ **Claude (claude.ai):** Settings → Connectors → Add custom connector → enter `https://mcp.clipform.io`
14
14
 
15
- **ChatGPT:** Settings Connectors Advanced enable Developer Mode Create connector enter `https://mcp.clipform.io` (requires Pro/Team/Enterprise)
15
+ **ChatGPT:** Settings → Connectors → Advanced → enable Developer Mode → Create connector → enter `https://mcp.clipform.io` (requires Pro/Team/Enterprise)
16
16
 
17
17
  **Any MCP client with OAuth:** Point it at `https://mcp.clipform.io` - discovery, registration, and auth are handled automatically via OAuth 2.1 + Dynamic Client Registration (RFC 7591).
18
18
 
@@ -87,7 +87,7 @@ You can also pass the key as a CLI flag: `npx -y @clipform/mcp-server --api-key=
87
87
  | `clipform_render_composition` | Render a video composition to MP4 or PNG - flag reveals, emoji puzzles, grids, timelines, map motion and more. Pass `wait: false` to fire renders in parallel and poll for results |
88
88
  | `clipform_generate_tts` | Generate narration audio with word-level captions |
89
89
  | `clipform_generate_slideshow` | Create slideshow videos from images + audio (waits, returns URL) |
90
- | `clipform_generate_video` | Generate video from images, clips, or both synced to audio. Pass `wait: false` to fire renders in parallel and poll for results |
90
+ | `clipform_generate_video` | Generate video from images, clips, or both synced to audio. Supports a looping background-audio bed (crowd noise, ambience) and print-style texture overlays. Pass `wait: false` to fire renders in parallel and poll for results |
91
91
  | `clipform_check_render` | Check status of a render started with `wait: false` |
92
92
  | `clipform_search_media` | Search royalty-free images and videos, with orientation filter and alt-text descriptions |
93
93
  | `clipform_search_music` | Search royalty-free music and ambient sounds |
@@ -113,3 +113,5 @@ Forms are created with a start node and end screen automatically - you just add
113
113
 
114
114
  - [Clipform](https://clipform.io) - Create interactive video forms
115
115
  - [Documentation](https://clipform.io/docs/guides/mcp) - Full guide with node types, scoring, and more
116
+
117
+ > Craft guides (`clipform://guides/*`, `clipform_get_guide`) are fetched from the Clipform API at runtime, so the server needs a reachable `API_URL` and valid `CLIPFORM_API_KEY` to serve them.
@@ -0,0 +1,139 @@
1
+ import {
2
+ ALL_VARIANTS,
3
+ FORM_TYPE_KEYS,
4
+ callApi,
5
+ getSessionContext
6
+ } from "./chunk-MESJBHZL.js";
7
+
8
+ // src/lib/guides.ts
9
+ var guidesPromise = null;
10
+ async function loadGuides() {
11
+ const result = await callApi("/internal/mcp/guides", { method: "GET" });
12
+ if (!result.ok) {
13
+ throw new Error(`Failed to load guides: ${result.error}`);
14
+ }
15
+ const guides = result.data.guides ?? [];
16
+ return new Map(guides.map((g) => [g.uri, g]));
17
+ }
18
+ async function fetchGuideText(uri) {
19
+ if (!guidesPromise) {
20
+ guidesPromise = loadGuides();
21
+ }
22
+ try {
23
+ const guides = await guidesPromise;
24
+ return guides.get(uri)?.text ?? null;
25
+ } catch (err) {
26
+ guidesPromise = null;
27
+ console.error(`[guides] Failed to fetch ${uri}: ${err instanceof Error ? err.message : String(err)}`);
28
+ return null;
29
+ }
30
+ }
31
+ function guideFallbackText(uri) {
32
+ return [
33
+ `# Guide unavailable`,
34
+ ``,
35
+ `The craft guide (${uri}) could not be loaded from the Clipform API.`,
36
+ `Guides are served at runtime and need a reachable API with a valid key -`,
37
+ `check API_URL and CLIPFORM_API_KEY, then try again.`,
38
+ ``,
39
+ `General principles in the meantime: write narration for the ear (short,`,
40
+ `conversational), never reveal answers in narration or media, and keep`,
41
+ `forms tight - every question must earn its place.`
42
+ ].join("\n");
43
+ }
44
+
45
+ // src/resources.ts
46
+ var GUIDE_TYPES = FORM_TYPE_KEYS;
47
+ var QUIZ_VARIANTS = ALL_VARIANTS;
48
+ var GUIDE_DESCRIPTIONS = {
49
+ "quiz": "Craft knowledge for writing engaging quizzes - difficulty curves, question psychology, narration style, scoring",
50
+ "survey": "Craft knowledge for feedback surveys, NPS, and research forms - brevity, rating scales, respondent fatigue",
51
+ "interview": "Craft knowledge for building interview forms - warm-up pacing, open questions, consent, video responses",
52
+ "funnel": "Craft knowledge for lead qualification funnels - planned feature, conditional routing coming soon",
53
+ "testimonial": "Craft knowledge for collecting testimonials and customer stories on video - storytelling prompts, comfort techniques, consent",
54
+ "application": "Craft knowledge for application and evaluation forms - multi-section structure, video responses for behavioural questions, screening",
55
+ "booking": "Craft knowledge for event registration and booking forms - minimal friction, video welcome, confirmation flow"
56
+ };
57
+ var QUIZ_VARIANT_DESCRIPTIONS = {
58
+ "personality": "Addendum for personality quizzes - category design, option weighting, outcome writing, no right/wrong answers",
59
+ "comprehension": "Addendum for YouTube comprehension quizzes - extracting questions from transcripts, distractor design, audience adaptation",
60
+ "composition": "Addendum for composition quizzes - guess-and-reveal formats built from rendered compositions: mechanic selection, the clue/reveal node pair, difficulty design"
61
+ };
62
+ function getGuideUri(type, variant) {
63
+ if (type === "quiz" && variant) {
64
+ return `clipform://guides/quiz/${variant}`;
65
+ }
66
+ return `clipform://guides/${type}`;
67
+ }
68
+ async function readGuide(uri) {
69
+ return await fetchGuideText(uri) ?? guideFallbackText(uri);
70
+ }
71
+ function registerResources(server) {
72
+ for (const type of GUIDE_TYPES) {
73
+ server.registerResource(
74
+ `guide-${type}`,
75
+ getGuideUri(type),
76
+ {
77
+ description: GUIDE_DESCRIPTIONS[type],
78
+ mimeType: "text/markdown",
79
+ annotations: { audience: ["assistant"], priority: 0.8 }
80
+ },
81
+ async () => ({
82
+ contents: [{
83
+ uri: getGuideUri(type),
84
+ mimeType: "text/markdown",
85
+ text: await readGuide(getGuideUri(type))
86
+ }]
87
+ })
88
+ );
89
+ }
90
+ for (const variant of QUIZ_VARIANTS) {
91
+ server.registerResource(
92
+ `guide-quiz-${variant}`,
93
+ getGuideUri("quiz", variant),
94
+ {
95
+ description: QUIZ_VARIANT_DESCRIPTIONS[variant],
96
+ mimeType: "text/markdown",
97
+ annotations: { audience: ["assistant"], priority: 0.8 }
98
+ },
99
+ async () => ({
100
+ contents: [{
101
+ uri: getGuideUri("quiz", variant),
102
+ mimeType: "text/markdown",
103
+ text: await readGuide(getGuideUri("quiz", variant))
104
+ }]
105
+ })
106
+ );
107
+ }
108
+ server.registerResource(
109
+ "context-session",
110
+ "clipform://context/session",
111
+ {
112
+ description: "Current session info: auth mode, workspace, plan tier, node limits, feature flags. Read this before planning content to know your constraints.",
113
+ mimeType: "text/markdown",
114
+ annotations: { audience: ["assistant"], priority: 1 }
115
+ },
116
+ async () => {
117
+ const text = await getSessionContext();
118
+ return {
119
+ contents: [
120
+ {
121
+ uri: "clipform://context/session",
122
+ mimeType: "text/markdown",
123
+ text: text || "Session context unavailable - API may not be reachable."
124
+ }
125
+ ]
126
+ };
127
+ }
128
+ );
129
+ }
130
+
131
+ export {
132
+ fetchGuideText,
133
+ guideFallbackText,
134
+ GUIDE_TYPES,
135
+ QUIZ_VARIANTS,
136
+ getGuideUri,
137
+ registerResources
138
+ };
139
+ //# sourceMappingURL=chunk-4DWK5GA5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/guides.ts","../src/resources.ts"],"sourcesContent":["import { callApi } from \"./api-client.js\";\n\n/**\n * Craft guide bodies live server-side (#470) - the npm package is a shim\n * that fetches them at runtime instead of bundling them in the tarball.\n * One fetch per process (all ~30KB of guides in a single response), cached;\n * a failed fetch is NOT cached so the next read retries.\n */\n\nexport interface RemoteGuide {\n type: string;\n variant: string | null;\n uri: string;\n mimeType: string;\n text: string;\n}\n\nlet guidesPromise: Promise<Map<string, RemoteGuide>> | null = null;\n\nasync function loadGuides(): Promise<Map<string, RemoteGuide>> {\n const result = await callApi(\"/internal/mcp/guides\", { method: \"GET\" });\n if (!result.ok) {\n throw new Error(`Failed to load guides: ${result.error}`);\n }\n const guides = (result.data as { guides?: RemoteGuide[] }).guides ?? [];\n return new Map(guides.map((g) => [g.uri, g]));\n}\n\n/** Fetch a guide body by its clipform:// URI. Returns null when unavailable. */\nexport async function fetchGuideText(uri: string): Promise<string | null> {\n if (!guidesPromise) {\n guidesPromise = loadGuides();\n }\n try {\n const guides = await guidesPromise;\n return guides.get(uri)?.text ?? null;\n } catch (err) {\n guidesPromise = null; // don't cache failures - retry on the next read\n // Loud, not silent: a misconfigured API_URL/key otherwise degrades every\n // guide to the stub with nothing in the logs.\n console.error(`[guides] Failed to fetch ${uri}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n}\n\n/** Shown in place of a guide when the API is unreachable or unauthenticated. */\nexport function guideFallbackText(uri: string): string {\n return [\n `# Guide unavailable`,\n ``,\n `The craft guide (${uri}) could not be loaded from the Clipform API.`,\n `Guides are served at runtime and need a reachable API with a valid key -`,\n `check API_URL and CLIPFORM_API_KEY, then try again.`,\n ``,\n `General principles in the meantime: write narration for the ear (short,`,\n `conversational), never reveal answers in narration or media, and keep`,\n `forms tight - every question must earn its place.`,\n ].join(\"\\n\");\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getSessionContext } from \"./lib/session-context.js\";\nimport { FORM_TYPE_KEYS, FORM_TYPES, ALL_VARIANTS } from \"@vid-master/config\";\n\nexport const GUIDE_TYPES = FORM_TYPE_KEYS as readonly string[] as readonly [string, ...string[]];\nexport type GuideType = (typeof FORM_TYPE_KEYS)[number];\n\nexport const QUIZ_VARIANTS = ALL_VARIANTS as readonly string[] as readonly [string, ...string[]];\nexport type QuizVariant = (typeof ALL_VARIANTS)[number];\n// Guide BODIES are not bundled here (#470): the npm tarball ships only this\n// registration metadata. Text is fetched from the API at runtime - see\n// lib/guides.ts (cached per process, stub fallback when unreachable).\nimport { fetchGuideText, guideFallbackText } from \"./lib/guides.js\";\n\nconst GUIDE_DESCRIPTIONS: Record<GuideType, string> = {\n \"quiz\": \"Craft knowledge for writing engaging quizzes - difficulty curves, question psychology, narration style, scoring\",\n \"survey\": \"Craft knowledge for feedback surveys, NPS, and research forms - brevity, rating scales, respondent fatigue\",\n \"interview\": \"Craft knowledge for building interview forms - warm-up pacing, open questions, consent, video responses\",\n \"funnel\": \"Craft knowledge for lead qualification funnels - planned feature, conditional routing coming soon\",\n \"testimonial\": \"Craft knowledge for collecting testimonials and customer stories on video - storytelling prompts, comfort techniques, consent\",\n \"application\": \"Craft knowledge for application and evaluation forms - multi-section structure, video responses for behavioural questions, screening\",\n \"booking\": \"Craft knowledge for event registration and booking forms - minimal friction, video welcome, confirmation flow\",\n};\n\nconst QUIZ_VARIANT_DESCRIPTIONS: Record<QuizVariant, string> = {\n \"personality\": \"Addendum for personality quizzes - category design, option weighting, outcome writing, no right/wrong answers\",\n \"comprehension\": \"Addendum for YouTube comprehension quizzes - extracting questions from transcripts, distractor design, audience adaptation\",\n \"composition\": \"Addendum for composition quizzes - guess-and-reveal formats built from rendered compositions: mechanic selection, the clue/reveal node pair, difficulty design\",\n};\n\nexport function getGuideUri(type: GuideType, variant?: QuizVariant): string {\n if (type === \"quiz\" && variant) {\n return `clipform://guides/quiz/${variant}`;\n }\n return `clipform://guides/${type}`;\n}\n\nasync function readGuide(uri: string): Promise<string> {\n return (await fetchGuideText(uri)) ?? guideFallbackText(uri);\n}\n\nexport function registerResources(server: McpServer) {\n for (const type of GUIDE_TYPES) {\n server.registerResource(\n `guide-${type}`,\n getGuideUri(type),\n {\n description: GUIDE_DESCRIPTIONS[type],\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\" as const], priority: 0.8 },\n },\n async () => ({\n contents: [{\n uri: getGuideUri(type),\n mimeType: \"text/markdown\",\n text: await readGuide(getGuideUri(type)),\n }],\n }),\n );\n }\n\n for (const variant of QUIZ_VARIANTS) {\n server.registerResource(\n `guide-quiz-${variant}`,\n getGuideUri(\"quiz\", variant),\n {\n description: QUIZ_VARIANT_DESCRIPTIONS[variant],\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\" as const], priority: 0.8 },\n },\n async () => ({\n contents: [{\n uri: getGuideUri(\"quiz\", variant),\n mimeType: \"text/markdown\",\n text: await readGuide(getGuideUri(\"quiz\", variant)),\n }],\n }),\n );\n }\n\n server.registerResource(\n \"context-session\",\n \"clipform://context/session\",\n {\n description:\n \"Current session info: auth mode, workspace, plan tier, node limits, feature flags. Read this before planning content to know your constraints.\",\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\"], priority: 1.0 },\n },\n async () => {\n const text = await getSessionContext();\n return {\n contents: [\n {\n uri: \"clipform://context/session\",\n mimeType: \"text/markdown\",\n text: text || \"Session context unavailable - API may not be reachable.\",\n },\n ],\n };\n }\n );\n}\n"],"mappings":";;;;;;;;AAiBA,IAAI,gBAA0D;AAE9D,eAAe,aAAgD;AAC7D,QAAM,SAAS,MAAM,QAAQ,wBAAwB,EAAE,QAAQ,MAAM,CAAC;AACtE,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,IAAI,MAAM,0BAA0B,OAAO,KAAK,EAAE;AAAA,EAC1D;AACA,QAAM,SAAU,OAAO,KAAoC,UAAU,CAAC;AACtE,SAAO,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9C;AAGA,eAAsB,eAAe,KAAqC;AACxE,MAAI,CAAC,eAAe;AAClB,oBAAgB,WAAW;AAAA,EAC7B;AACA,MAAI;AACF,UAAM,SAAS,MAAM;AACrB,WAAO,OAAO,IAAI,GAAG,GAAG,QAAQ;AAAA,EAClC,SAAS,KAAK;AACZ,oBAAgB;AAGhB,YAAQ,MAAM,4BAA4B,GAAG,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACpG,WAAO;AAAA,EACT;AACF;AAGO,SAAS,kBAAkB,KAAqB;AACrD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,oBAAoB,GAAG;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;;;ACtDO,IAAM,cAAc;AAGpB,IAAM,gBAAgB;AAO7B,IAAM,qBAAgD;AAAA,EACpD,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,eAAe;AAAA,EACf,eAAe;AAAA,EACf,WAAW;AACb;AAEA,IAAM,4BAAyD;AAAA,EAC7D,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEO,SAAS,YAAY,MAAiB,SAA+B;AAC1E,MAAI,SAAS,UAAU,SAAS;AAC9B,WAAO,0BAA0B,OAAO;AAAA,EAC1C;AACA,SAAO,qBAAqB,IAAI;AAClC;AAEA,eAAe,UAAU,KAA8B;AACrD,SAAQ,MAAM,eAAe,GAAG,KAAM,kBAAkB,GAAG;AAC7D;AAEO,SAAS,kBAAkB,QAAmB;AACnD,aAAW,QAAQ,aAAa;AAC9B,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb,YAAY,IAAI;AAAA,MAChB;AAAA,QACE,aAAa,mBAAmB,IAAI;AAAA,QACpC,UAAU;AAAA,QACV,aAAa,EAAE,UAAU,CAAC,WAAoB,GAAG,UAAU,IAAI;AAAA,MACjE;AAAA,MACA,aAAa;AAAA,QACX,UAAU,CAAC;AAAA,UACT,KAAK,YAAY,IAAI;AAAA,UACrB,UAAU;AAAA,UACV,MAAM,MAAM,UAAU,YAAY,IAAI,CAAC;AAAA,QACzC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,aAAW,WAAW,eAAe;AACnC,WAAO;AAAA,MACL,cAAc,OAAO;AAAA,MACrB,YAAY,QAAQ,OAAO;AAAA,MAC3B;AAAA,QACE,aAAa,0BAA0B,OAAO;AAAA,QAC9C,UAAU;AAAA,QACV,aAAa,EAAE,UAAU,CAAC,WAAoB,GAAG,UAAU,IAAI;AAAA,MACjE;AAAA,MACA,aAAa;AAAA,QACX,UAAU,CAAC;AAAA,UACT,KAAK,YAAY,QAAQ,OAAO;AAAA,UAChC,UAAU;AAAA,UACV,MAAM,MAAM,UAAU,YAAY,QAAQ,OAAO,CAAC;AAAA,QACpD,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MACF,UAAU;AAAA,MACV,aAAa,EAAE,UAAU,CAAC,WAAW,GAAG,UAAU,EAAI;AAAA,IACxD;AAAA,IACA,YAAY;AACV,YAAM,OAAO,MAAM,kBAAkB;AACrC,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK;AAAA,YACL,UAAU;AAAA,YACV,MAAM,QAAQ;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -6,14 +6,15 @@ import {
6
6
  getWorkflowText,
7
7
  objectType,
8
8
  registerPrompts
9
- } from "./chunk-XOYZMNJD.js";
9
+ } from "./chunk-IXQRUFZM.js";
10
10
  import {
11
11
  GUIDE_TYPES,
12
12
  QUIZ_VARIANTS,
13
- getGuideContent,
13
+ fetchGuideText,
14
14
  getGuideUri,
15
+ guideFallbackText,
15
16
  registerResources
16
- } from "./chunk-AZ33QQ4H.js";
17
+ } from "./chunk-4DWK5GA5.js";
17
18
  import {
18
19
  BUSINESS,
19
20
  CONTACT_FIELDS,
@@ -22,6 +23,7 @@ import {
22
23
  KEN_BURNS_EFFECTS,
23
24
  KEN_BURNS_FIT_MODES,
24
25
  KEN_BURNS_PRESETS,
26
+ MCP_TOOL_TIERS,
25
27
  NODE_TYPES,
26
28
  NON_COUNTABLE_TYPES,
27
29
  SLIDESHOW_TRANSITIONS,
@@ -29,7 +31,7 @@ import {
29
31
  errorResult,
30
32
  resolveFormType,
31
33
  textResult
32
- } from "./chunk-UA3MYEUA.js";
34
+ } from "./chunk-MESJBHZL.js";
33
35
  import {
34
36
  __commonJS,
35
37
  __export,
@@ -16984,7 +16986,6 @@ var END_SCREEN_ICONS = _endScreen.icon.enum;
16984
16986
  var END_SCREEN_CTA_TYPES = _endScreen.cta_type.enum;
16985
16987
  var PUBLIC_COMPOSITIONS = [
16986
16988
  "Map",
16987
- "MapPin",
16988
16989
  "Grid",
16989
16990
  "GridList",
16990
16991
  "ObscuredReveal",
@@ -17005,9 +17006,21 @@ var PUBLIC_COMPOSITIONS = [
17005
17006
  ];
17006
17007
  var LABS_COMPOSITIONS = [
17007
17008
  "StatCounter",
17008
- "ListReveal"
17009
+ "ListReveal",
17010
+ "Scene",
17011
+ "Spin3D"
17012
+ ];
17013
+ var SOCIAL_COMPOSITIONS = [
17014
+ "ShortFormQuiz",
17015
+ "ScorecardQuiz",
17016
+ "ScorecardGK",
17017
+ "CountrySilhouetteQuiz"
17018
+ ];
17019
+ var EXPOSED_COMPOSITIONS = [
17020
+ ...PUBLIC_COMPOSITIONS,
17021
+ ...process.env.CLIPFORM_LABS === "1" ? LABS_COMPOSITIONS : [],
17022
+ ...process.env.CLIPFORM_SOCIAL === "1" ? SOCIAL_COMPOSITIONS : []
17009
17023
  ];
17010
- var EXPOSED_COMPOSITIONS = process.env.CLIPFORM_LABS === "1" ? [...PUBLIC_COMPOSITIONS, ...LABS_COMPOSITIONS] : PUBLIC_COMPOSITIONS;
17011
17024
 
17012
17025
  // src/lib/schemas.ts
17013
17026
  var ACTIVE_NODE_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.is_active && !def.is_system).map(([type]) => type);
@@ -18381,7 +18394,7 @@ For multi-render builds (e.g. composition quizzes with a clue + reveal clip per
18381
18394
  inputSchema: {
18382
18395
  compositionId: external_exports.string().describe(`The composition ID. Call clipform_list_compositions to see available options (e.g. ${EXPOSED_COMPOSITIONS.map((id) => `'${id}'`).join(", ")})`),
18383
18396
  outputFormat: external_exports.enum(["mp4", "png"]).default("mp4").describe("Output format (default: mp4)"),
18384
- inputProps: external_exports.record(external_exports.unknown()).optional().describe("Props object matching the composition's schema from clipform_list_compositions. Validated STRICTLY - unknown or missing props fail with the schema in the error, nothing renders silently with defaults. For map compositions (Map), round lat/lng to 1 decimal place (e.g. 48.9 instead of 48.8566) \u2014 this improves cache hit rates."),
18397
+ inputProps: external_exports.record(external_exports.unknown()).optional().describe("Props object matching the composition's schema from clipform_list_compositions. Validated STRICTLY - unknown or missing props fail with the schema in the error, nothing renders silently with defaults. For map compositions (Map), wide shots (camera.zoom < 9) may round lat/lng to 1 decimal place to improve cache hit rates; close shots and pin drops should keep full precision \u2014 the pin lands exactly on `target`."),
18385
18398
  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 immediately - fire all renders first, then poll clipform_check_render. Use false whenever rendering more than one clip.")
18386
18399
  },
18387
18400
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
@@ -18594,6 +18607,8 @@ For multi-question builds, pass wait: false on every render: each call returns a
18594
18607
  })
18595
18608
  ).min(1).max(20).describe("Media items (images, video clips, or a mix)"),
18596
18609
  audio_url: external_exports.string().url().optional().describe("Audio track URL. Video duration matches audio duration."),
18610
+ background_audio_url: external_exports.string().url().optional().describe("Ambience/music bed under the narration (crowd noise, room tone). Loops to fill the video. Find tracks with clipform_search_music."),
18611
+ background_audio_volume: external_exports.number().min(0).max(1).optional().describe("Background bed volume 0-1 (default 0.15 - sits under speech)"),
18597
18612
  duration_seconds: external_exports.number().positive().optional().describe("Video duration in seconds (required if no audio_url)"),
18598
18613
  random_effects: external_exports.boolean().optional().default(true).describe("Shuffle Ken Burns effects across image items (default: true)"),
18599
18614
  transition: external_exports.object({
@@ -18601,23 +18616,40 @@ For multi-question builds, pass wait: false on every render: each call returns a
18601
18616
  duration: external_exports.number().optional().default(1).describe("Transition duration in seconds (default: 1)")
18602
18617
  }).optional(),
18603
18618
  style_preset: STYLE_PRESET_ENUM.optional(),
18619
+ texture: external_exports.object({
18620
+ type: external_exports.enum(["checker", "halftone", "scanlines"]).describe("checker = square dither mesh, halftone = dot grid, scanlines = horizontal lines"),
18621
+ sizePx: external_exports.number().positive().optional().describe("Pattern cell size in px (default 4)"),
18622
+ opacity: external_exports.number().min(0).max(1).optional().describe("Pattern darkness 0-1 (default 0.35)")
18623
+ }).optional().describe("Print-style pattern overlay on image items - makes stock imagery read as designed (screen-print dither look)"),
18624
+ duotone: external_exports.object({
18625
+ shadow: external_exports.string().describe("Darker tone (maps to image shadows), e.g. '#1a1633'"),
18626
+ highlight: external_exports.string().describe("Lighter tone (maps to image highlights), e.g. '#f5c542'"),
18627
+ contrast: external_exports.number().positive().optional().describe("Contrast boost before mapping (default 1.1)")
18628
+ }).optional().describe("Two-tone editorial recolour on image items - desaturates then maps to a shadow->highlight palette. Pair with a halftone texture for a screen-print poster look."),
18604
18629
  background_color: external_exports.string().optional().describe("Background color (default '#000')"),
18605
18630
  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.")
18606
18631
  },
18607
18632
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
18608
18633
  },
18609
- async ({ items, audio_url, duration_seconds, random_effects, transition, style_preset, background_color, wait }) => {
18634
+ async ({ items, audio_url, background_audio_url, background_audio_volume, duration_seconds, random_effects, transition, style_preset, texture, duotone, background_color, wait }) => {
18610
18635
  const workspace_id = getMcpAuth()?.workspace_id;
18636
+ const default_style = style_preset || texture || duotone ? {
18637
+ ...style_preset ? { preset: style_preset } : {},
18638
+ ...texture ? { texture } : {},
18639
+ ...duotone ? { duotone } : {}
18640
+ } : void 0;
18611
18641
  const apiCall = () => callApi("/internal/generate-video", {
18612
18642
  timeoutMs: 3e5,
18613
18643
  // local renders pay a cold webpack bundle; Lambda is fast but bursty
18614
18644
  body: {
18615
18645
  items,
18616
18646
  audio_url,
18647
+ background_audio_url,
18648
+ background_audio_volume,
18617
18649
  duration_seconds,
18618
18650
  random_effects: random_effects ?? true,
18619
18651
  transition: transition ?? { type: "fade", duration: 1 },
18620
- default_style: style_preset ? { preset: style_preset } : void 0,
18652
+ default_style,
18621
18653
  background_color,
18622
18654
  workspace_id
18623
18655
  }
@@ -18845,8 +18877,8 @@ Quiz variants (optional): ${QUIZ_VARIANTS.join(", ")} - appends variant-specific
18845
18877
  }
18846
18878
  const type = resolved;
18847
18879
  const validVariant = type === "quiz" ? variant : void 0;
18848
- const content = getGuideContent(type, validVariant);
18849
18880
  const uri = getGuideUri(type, validVariant);
18881
+ const content = await fetchGuideText(uri) ?? guideFallbackText(uri);
18850
18882
  return {
18851
18883
  content: [{
18852
18884
  type: "resource",
@@ -18954,6 +18986,10 @@ function createServer(options) {
18954
18986
  );
18955
18987
  const _registerTool = server.registerTool.bind(server);
18956
18988
  server.registerTool = (name, config2, handler) => {
18989
+ const requiredTier = MCP_TOOL_TIERS[name] ?? 0;
18990
+ if (options?.planTier !== void 0 && requiredTier > options.planTier) {
18991
+ return void 0;
18992
+ }
18957
18993
  return _registerTool(
18958
18994
  name,
18959
18995
  config2,
@@ -19004,4 +19040,4 @@ export {
19004
19040
  JSONRPCMessageSchema,
19005
19041
  createServer
19006
19042
  };
19007
- //# sourceMappingURL=chunk-5L2KPKOE.js.map
19043
+ //# sourceMappingURL=chunk-IBTTDNN6.js.map