@clipform/mcp-server 1.5.1 → 1.8.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.
Files changed (108) hide show
  1. package/README.md +51 -26
  2. package/dist/__tests__/api-parity.test.js +31 -22
  3. package/dist/__tests__/api-parity.test.js.map +1 -1
  4. package/dist/index.js +9 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/api-client.d.ts +3 -1
  7. package/dist/lib/api-client.js +31 -5
  8. package/dist/lib/api-client.js.map +1 -1
  9. package/dist/lib/auth-context.d.ts +17 -0
  10. package/dist/lib/auth-context.js +9 -0
  11. package/dist/lib/auth-context.js.map +1 -0
  12. package/dist/lib/format-form.d.ts +2 -0
  13. package/dist/lib/format-form.js +32 -0
  14. package/dist/lib/format-form.js.map +1 -0
  15. package/dist/lib/render-jobs.d.ts +13 -0
  16. package/dist/lib/render-jobs.js +38 -0
  17. package/dist/lib/render-jobs.js.map +1 -0
  18. package/dist/lib/schemas.d.ts +4 -4
  19. package/dist/lib/schemas.js +11 -11
  20. package/dist/lib/schemas.js.map +1 -1
  21. package/dist/lib/session-context.d.ts +1 -0
  22. package/dist/lib/session-context.js +38 -0
  23. package/dist/lib/session-context.js.map +1 -0
  24. package/dist/prompts.js +262 -125
  25. package/dist/prompts.js.map +1 -1
  26. package/dist/resources.d.ts +2 -0
  27. package/dist/resources.js +336 -0
  28. package/dist/resources.js.map +1 -0
  29. package/dist/server.js +29 -23
  30. package/dist/server.js.map +1 -1
  31. package/dist/tools/add-node.d.ts +2 -0
  32. package/dist/tools/add-node.js +50 -0
  33. package/dist/tools/add-node.js.map +1 -0
  34. package/dist/tools/attach-node-audio.d.ts +2 -0
  35. package/dist/tools/attach-node-audio.js +37 -0
  36. package/dist/tools/attach-node-audio.js.map +1 -0
  37. package/dist/tools/{add-question.d.ts → check-render.d.ts} +1 -1
  38. package/dist/tools/check-render.js +47 -0
  39. package/dist/tools/check-render.js.map +1 -0
  40. package/dist/tools/create-form.js +90 -19
  41. package/dist/tools/create-form.js.map +1 -1
  42. package/dist/tools/delete-form.js +2 -2
  43. package/dist/tools/delete-form.js.map +1 -1
  44. package/dist/tools/delete-node-media.d.ts +2 -0
  45. package/dist/tools/delete-node-media.js +29 -0
  46. package/dist/tools/delete-node-media.js.map +1 -0
  47. package/dist/tools/delete-node.d.ts +2 -0
  48. package/dist/tools/delete-node.js +32 -0
  49. package/dist/tools/delete-node.js.map +1 -0
  50. package/dist/tools/generate-slideshow.js +129 -26
  51. package/dist/tools/generate-slideshow.js.map +1 -1
  52. package/dist/tools/generate-tts.js +56 -31
  53. package/dist/tools/generate-tts.js.map +1 -1
  54. package/dist/tools/generate-video.d.ts +2 -0
  55. package/dist/tools/generate-video.js +119 -0
  56. package/dist/tools/generate-video.js.map +1 -0
  57. package/dist/tools/get-form.js +5 -26
  58. package/dist/tools/get-form.js.map +1 -1
  59. package/dist/tools/get-node-media.d.ts +2 -0
  60. package/dist/tools/{get-question-media.js → get-node-media.js} +11 -11
  61. package/dist/tools/get-node-media.js.map +1 -0
  62. package/dist/tools/list-assets.js +1 -1
  63. package/dist/tools/list-assets.js.map +1 -1
  64. package/dist/tools/list-compositions.js +3 -3
  65. package/dist/tools/list-compositions.js.map +1 -1
  66. package/dist/tools/log-generation.js +3 -3
  67. package/dist/tools/log-generation.js.map +1 -1
  68. package/dist/tools/render-composition.js +22 -15
  69. package/dist/tools/render-composition.js.map +1 -1
  70. package/dist/tools/search-media.js +2 -2
  71. package/dist/tools/search-media.js.map +1 -1
  72. package/dist/tools/search-music.js +1 -1
  73. package/dist/tools/search-music.js.map +1 -1
  74. package/dist/tools/search-news.js +1 -1
  75. package/dist/tools/search-news.js.map +1 -1
  76. package/dist/tools/set-node-logic.d.ts +2 -0
  77. package/dist/tools/set-node-logic.js +56 -0
  78. package/dist/tools/set-node-logic.js.map +1 -0
  79. package/dist/tools/update-form.js +43 -4
  80. package/dist/tools/update-form.js.map +1 -1
  81. package/dist/tools/update-node.d.ts +2 -0
  82. package/dist/tools/{update-question.js → update-node.js} +16 -13
  83. package/dist/tools/update-node.js.map +1 -0
  84. package/dist/tools/upload-node-media.d.ts +2 -0
  85. package/dist/tools/upload-node-media.js +109 -0
  86. package/dist/tools/upload-node-media.js.map +1 -0
  87. package/package.json +3 -2
  88. package/dist/tools/add-question.js +0 -47
  89. package/dist/tools/add-question.js.map +0 -1
  90. package/dist/tools/attach-question-audio.d.ts +0 -2
  91. package/dist/tools/attach-question-audio.js +0 -37
  92. package/dist/tools/attach-question-audio.js.map +0 -1
  93. package/dist/tools/delete-question-media.d.ts +0 -2
  94. package/dist/tools/delete-question-media.js +0 -29
  95. package/dist/tools/delete-question-media.js.map +0 -1
  96. package/dist/tools/delete-question.d.ts +0 -2
  97. package/dist/tools/delete-question.js +0 -29
  98. package/dist/tools/delete-question.js.map +0 -1
  99. package/dist/tools/get-question-media.d.ts +0 -2
  100. package/dist/tools/get-question-media.js.map +0 -1
  101. package/dist/tools/set-question-logic.d.ts +0 -2
  102. package/dist/tools/set-question-logic.js +0 -54
  103. package/dist/tools/set-question-logic.js.map +0 -1
  104. package/dist/tools/update-question.d.ts +0 -2
  105. package/dist/tools/update-question.js.map +0 -1
  106. package/dist/tools/upload-question-media.d.ts +0 -2
  107. package/dist/tools/upload-question-media.js +0 -69
  108. package/dist/tools/upload-question-media.js.map +0 -1
@@ -1,12 +1,13 @@
1
1
  import { z } from "zod";
2
- import { QuestionSchema, QUESTION_TYPES_DESCRIPTION, } from "../lib/schemas.js";
2
+ import { BUSINESS } from "@vid-master/config";
3
+ import { NodeSchema, NODE_TYPES_DESCRIPTION, } from "../lib/schemas.js";
3
4
  import { callApi, errorResult, textResult } from "../lib/api-client.js";
4
5
  export function registerCreateFormTool(server) {
5
6
  server.registerTool("clipform_create_form", {
6
7
  title: "Create Clipform",
7
- description: `Create a new Clipform (interactive video-style form). Returns a claim URL and an edit_token for further modifications.
8
+ description: `Create a new Clipform (interactive video-style form). Returns a viewer URL plus, for anonymous (non-OAuth) sessions, an edit_token and claim URL. When connected via an authenticated MCP client (e.g. claude.ai), the form lands directly in the user's workspace and no edit_token is issued.
8
9
 
9
- ${QUESTION_TYPES_DESCRIPTION}
10
+ ${NODE_TYPES_DESCRIPTION}
10
11
 
11
12
  All type definitions and config schemas are derived from @vid-master/config (answer-types). Refer to the config descriptions above for the correct keys and shapes.
12
13
 
@@ -22,7 +23,7 @@ Example: A form that asks a question, collects contact info, then finishes:
22
23
  inputSchema: {
23
24
  title: z.string().describe("Form title"),
24
25
  questions: z
25
- .array(QuestionSchema)
26
+ .array(NodeSchema)
26
27
  .min(1)
27
28
  .describe("Ordered list of questions/steps"),
28
29
  show_step_counter: z
@@ -49,6 +50,10 @@ Example: A form that asks a question, collects contact info, then finishes:
49
50
  .boolean()
50
51
  .optional()
51
52
  .describe("Auto-play video when embedded (default: false). When off, embeds show a thumbnail + play button."),
53
+ tags: z
54
+ .array(z.string())
55
+ .optional()
56
+ .describe("Tags for indexing (e.g. ['quiz', 'trivia', 'arsenal']). Include format, genre, and topics."),
52
57
  },
53
58
  annotations: {
54
59
  readOnlyHint: false,
@@ -56,18 +61,55 @@ Example: A form that asks a question, collects contact info, then finishes:
56
61
  idempotentHint: false,
57
62
  openWorldHint: true,
58
63
  },
59
- }, async ({ title, questions, show_step_counter, disable_back_navigation, primary_color, background_color, font_family, embed_autoplay }) => {
64
+ }, async ({ title, questions, show_step_counter, disable_back_navigation, primary_color, background_color, font_family, embed_autoplay, tags }) => {
65
+ // 0. Pre-flight: resolve the caller's plan and reject up-front if the
66
+ // requested question count exceeds it. Without this we'd build a
67
+ // scaffold + N-1 questions, then 403 on the Nth, leaving an orphan.
68
+ // /v1/me is the single source for plan info; PLAN_LIMITS lives in
69
+ // @vid-master/config and is read there. The MCP tool is intentionally
70
+ // thin and never duplicates limit numbers.
71
+ let planContext = null;
72
+ const meResult = await callApi("/me", { method: "GET" });
73
+ if (!meResult.ok) {
74
+ return errorResult(`Unable to determine workspace: ${meResult.error}`);
75
+ }
76
+ const me = meResult.data;
77
+ const workspaceId = me.workspace?.id ?? null;
78
+ if (!workspaceId) {
79
+ return errorResult("No workspace available. Connect your Clipform account in claude.ai → Settings → Connectors, or ensure MCP_WORKSPACE_ID is configured for anonymous mode.");
80
+ }
81
+ planContext = {
82
+ auth_mode: me.auth_mode,
83
+ workspace_id: workspaceId,
84
+ workspace_name: me.workspace?.name ?? null,
85
+ plan_name: me.plan?.name ?? "Free",
86
+ node_limit: me.plan?.node_limit ?? null,
87
+ };
88
+ // questions includes any end_screen the user supplied; the scaffold
89
+ // already has one so we count "real" content nodes only.
90
+ const contentCount = questions.filter((q) => q.type !== "end_screen").length;
91
+ if (planContext.node_limit !== null && contentCount > planContext.node_limit) {
92
+ const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
93
+ const message = planContext.auth_mode === "oauth"
94
+ ? `Your '${planContext.workspace_name}' workspace is on the ${planContext.plan_name} plan, capped at ${planContext.node_limit} questions per form. You asked for ${contentCount}. Either rerun with ${planContext.node_limit} questions or upgrade at ${upgradeUrl}.`
95
+ : `Anonymous sessions are capped at ${planContext.node_limit} questions per form (${planContext.plan_name} plan). You asked for ${contentCount}. Either rerun with ${planContext.node_limit} questions, or connect your Clipform account in claude.ai → Settings → Connectors so forms land in your workspace with your real plan limits.`;
96
+ return errorResult(message);
97
+ }
60
98
  // 1. Create bare scaffold
61
99
  const createResult = await callApi("/forms", {
62
100
  method: "POST",
63
- body: { title },
101
+ body: { title, workspace_id: workspaceId },
64
102
  });
65
103
  if (!createResult.ok) {
66
104
  return errorResult(createResult.error);
67
105
  }
68
106
  const { data } = createResult;
69
107
  const formId = data.form_id;
70
- const editToken = data.edit_token;
108
+ // edit_token is null when the request is OAuth-authenticated (the user
109
+ // owns the form via their workspace and no per-form secret is needed).
110
+ // For anonymous local sessions it carries the per-form capability token.
111
+ const editToken = data.edit_token ?? undefined;
112
+ const claimUrl = data.claim_url ?? undefined;
71
113
  // 2. Apply form settings and theme if provided
72
114
  const settingsBody = {};
73
115
  if (show_step_counter !== undefined)
@@ -102,7 +144,7 @@ Example: A form that asks a question, collects contact info, then finishes:
102
144
  const questions_list = getResult.data.questions;
103
145
  const endScreen = questions_list?.find((qq) => qq.type === "end_screen");
104
146
  if (endScreen) {
105
- await callApi(`/forms/${formId}/questions/${endScreen.id}`, {
147
+ await callApi(`/forms/${formId}/nodes/${endScreen.id}`, {
106
148
  method: "PATCH",
107
149
  body: { prompt: q.prompt, ...(q.config ? { config: q.config } : {}) },
108
150
  token: editToken,
@@ -112,7 +154,7 @@ Example: A form that asks a question, collects contact info, then finishes:
112
154
  }
113
155
  // Fallback: add it normally if we couldn't find the default
114
156
  }
115
- const addResult = await callApi(`/forms/${formId}/questions`, {
157
+ const addResult = await callApi(`/forms/${formId}/nodes`, {
116
158
  method: "POST",
117
159
  body: { question: q },
118
160
  token: editToken,
@@ -127,20 +169,49 @@ Example: A form that asks a question, collects contact info, then finishes:
127
169
  body: { is_published: true },
128
170
  token: editToken,
129
171
  });
130
- return textResult([
172
+ // 5. Tag the form (best-effort)
173
+ if (tags && tags.length > 0) {
174
+ await callApi(`/forms/${formId}/tags`, {
175
+ method: "PUT",
176
+ body: { tags },
177
+ token: editToken,
178
+ });
179
+ }
180
+ const lines = [
131
181
  `Form created successfully!`,
132
182
  ``,
133
183
  `Title: ${title}`,
134
184
  `Questions: ${questions.length}`,
135
- `Edit Token: ${editToken}`,
136
- ``,
137
- `FORM URL (share this with respondents): ${data.viewer_url}`,
138
- `CLAIM URL (transfer ownership to your account): ${data.claim_url}`,
139
- ``,
140
- `IMPORTANT: Always show the user the exact URLs above — do not rewrite or modify them.`,
141
- `Save the edit_token — you need it for all subsequent operations.`,
142
- `The token expires when the user claims the form via the claim URL.`,
143
- ].join("\n"));
185
+ `Form ID: ${formId}`,
186
+ ];
187
+ if (editToken) {
188
+ lines.push(`Edit Token: ${editToken}`);
189
+ }
190
+ lines.push(``, `FORM URL (share this with respondents): ${data.viewer_url}`);
191
+ if (claimUrl) {
192
+ lines.push(`CLAIM URL (transfer ownership to your account): ${claimUrl}`);
193
+ }
194
+ lines.push(``, `IMPORTANT: Always show the user the exact URLs above — do not rewrite or modify them.`);
195
+ if (editToken) {
196
+ lines.push(`Save the form_id and edit_token — you need both for all subsequent operations (get_form, add_question, upload_question_media, etc.).`, `The token expires when the user claims the form via the claim URL.`);
197
+ }
198
+ else {
199
+ lines.push(`The form is owned by your workspace - pass form_id alone on follow-up tools.`);
200
+ }
201
+ // One-line plan/auth summary so the model can mention it ONCE and not
202
+ // re-prompt on every follow-up call.
203
+ if (planContext) {
204
+ const limitNote = planContext.node_limit === null
205
+ ? "unlimited questions"
206
+ : `up to ${planContext.node_limit} questions per form`;
207
+ if (planContext.auth_mode === "oauth") {
208
+ lines.push(``, `Plan: ${planContext.plan_name} (${limitNote}) in workspace '${planContext.workspace_name}'.`);
209
+ }
210
+ else {
211
+ lines.push(``, `Plan: anonymous ${planContext.plan_name} (${limitNote}). Mention this once: the user can connect their Clipform account in claude.ai → Settings → Connectors → ${BUSINESS.urls.mcp} so future forms land directly in their workspace - do not repeat on follow-up calls.`);
212
+ }
213
+ }
214
+ return textResult(lines.join("\n"));
144
215
  });
145
216
  }
146
217
  //# sourceMappingURL=create-form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-form.js","sourceRoot":"","sources":["../../src/tools/create-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-form.js","sourceRoot":"","sources":["../../src/tools/create-form.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,8 @@ export function registerDeleteFormTool(server) {
5
5
  title: "Delete Clipform",
6
6
  description: `Permanently delete an unclaimed form and all its questions. This cannot be undone. Only works on forms that haven't been claimed yet.`,
7
7
  inputSchema: {
8
- form_id: z.string().describe("The form ID to delete"),
9
- edit_token: z.string().describe("The edit token"),
8
+ form_id: z.string().uuid().describe("The form UUID to delete (returned by clipform_create_form, not the short share_id from the URL)"),
9
+ edit_token: z.string().optional().describe("The edit token. Required for anonymous (non-OAuth) sessions; omit when connected via an authenticated MCP client like claude.ai."),
10
10
  },
11
11
  annotations: {
12
12
  readOnlyHint: false,
@@ -1 +1 @@
1
- {"version":3,"file":"delete-form.js","sourceRoot":"","sources":["../../src/tools/delete-form.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAExE,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,uIAAuI;QACpJ,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;YACrD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;SAClD;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,IAAI;YACrB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,OAAO,EAAE,EAAE;YAChD,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,UAAU,CAAC,QAAQ,OAAO,gCAAgC,CAAC,CAAC;IACrE,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"delete-form.js","sourceRoot":"","sources":["../../src/tools/delete-form.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAExE,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,uIAAuI;QACpJ,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,iGAAiG,CAAC;YACtI,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kIAAkI,CAAC;SAC/K;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,IAAI;YACrB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,OAAO,EAAE,EAAE;YAChD,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,UAAU,CAAC,QAAQ,OAAO,gCAAgC,CAAC,CAAC;IACrE,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDeleteNodeMediaTool(server: McpServer): void;
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { callApi, errorResult, textResult } from "../lib/api-client.js";
3
+ export function registerDeleteNodeMediaTool(server) {
4
+ server.registerTool("clipform_delete_node_media", {
5
+ title: "Delete Node Media",
6
+ description: `Remove media from a node. Deletes the media record and cleans up external resources (Mux video asset, storage file).`,
7
+ inputSchema: {
8
+ form_id: z.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
9
+ edit_token: z.string().optional().describe("The edit token. Required for anonymous (non-OAuth) sessions; omit when connected via an authenticated MCP client like claude.ai."),
10
+ node_id: z.string().describe("The node ID"),
11
+ },
12
+ annotations: {
13
+ readOnlyHint: false,
14
+ destructiveHint: true,
15
+ idempotentHint: false,
16
+ openWorldHint: true,
17
+ },
18
+ }, async ({ form_id, edit_token, node_id }) => {
19
+ const result = await callApi(`/forms/${form_id}/nodes/${node_id}/media`, {
20
+ method: "DELETE",
21
+ token: edit_token,
22
+ });
23
+ if (!result.ok) {
24
+ return errorResult(result.error);
25
+ }
26
+ return textResult("Media removed from node.");
27
+ });
28
+ }
29
+ //# sourceMappingURL=delete-node-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-node-media.js","sourceRoot":"","sources":["../../src/tools/delete-node-media.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAExE,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,4BAA4B,EAC5B;QACE,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,sHAAsH;QACnI,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,uFAAuF,CAAC;YAC5H,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kIAAkI,CAAC;YAC9K,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;SAC5C;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,IAAI;YACrB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE;QACzC,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,UAAU,OAAO,UAAU,OAAO,QAAQ,EAC1C;YACE,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,UAAU;SAClB,CACF,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,UAAU,CAAC,0BAA0B,CAAC,CAAC;IAChD,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDeleteNodeTool(server: McpServer): void;
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { callApi, errorResult, textResult } from "../lib/api-client.js";
3
+ import { fetchAndFormatFormState } from "../lib/format-form.js";
4
+ export function registerDeleteNodeTool(server) {
5
+ server.registerTool("clipform_delete_node", {
6
+ title: "Delete Node",
7
+ description: `Delete a node from a form. The logic chain is automatically re-linked (the previous node will point to the next one). Cannot delete the start node or the last end screen.`,
8
+ inputSchema: {
9
+ form_id: z.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
10
+ edit_token: z.string().optional().describe("The edit token. Required for anonymous (non-OAuth) sessions; omit when connected via an authenticated MCP client like claude.ai."),
11
+ node_id: z.string().describe("The node ID to delete"),
12
+ },
13
+ annotations: {
14
+ readOnlyHint: false,
15
+ destructiveHint: true,
16
+ idempotentHint: false,
17
+ openWorldHint: true,
18
+ },
19
+ }, async ({ form_id, edit_token, node_id }) => {
20
+ const result = await callApi(`/forms/${form_id}/nodes/${node_id}`, {
21
+ method: "DELETE",
22
+ token: edit_token,
23
+ });
24
+ if (!result.ok) {
25
+ return errorResult(result.error);
26
+ }
27
+ const confirmMsg = `Node ${node_id} deleted. The logic chain has been re-linked.`;
28
+ const formState = await fetchAndFormatFormState(form_id, edit_token);
29
+ return textResult(formState ? `${confirmMsg}\n\n---\n\n${formState}` : confirmMsg);
30
+ });
31
+ }
32
+ //# sourceMappingURL=delete-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-node.js","sourceRoot":"","sources":["../../src/tools/delete-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,4KAA4K;QACzL,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,uFAAuF,CAAC;YAC5H,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kIAAkI,CAAC;YAC9K,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SACtD;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,IAAI;YACrB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE;QACzC,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,UAAU,OAAO,UAAU,OAAO,EAAE,EACpC;YACE,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,UAAU;SAClB,CACF,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,UAAU,GAAG,QAAQ,OAAO,+CAA+C,CAAC;QAClF,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACrE,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,UAAU,cAAc,SAAS,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACrF,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1,51 +1,154 @@
1
1
  import { z } from "zod";
2
- import { callInternalApi, errorResult, textResult } from "../lib/api-client.js";
2
+ import { callInternalApi, textResult } from "../lib/api-client.js";
3
+ import { createJob, completeJob, failJob } from "../lib/render-jobs.js";
4
+ // Style knobs - mirror packages/remotion/src/compositions/slideshow/KenBurnsImage.tsx.
5
+ // Every field optional; unset fields fall back to library defaults (which shift
6
+ // automatically between 'cover' and 'blur-pad' fit modes).
7
+ const slideshowStyleSchema = z.object({
8
+ preset: z.enum(["cinematic", "dramatic", "calm", "documentary", "dreamy", "moody"]).optional().describe("Named shortcut applied before per-field overrides. cinematic = defaults; dramatic = big zoom, wider pan, ease-out, stronger vignette; calm = minimal motion, linear, no vignette; documentary = near-static, no pan; dreamy = bright heavy blur-pad, soft vignette, gentle zoom; moody = desaturated low-key blur-pad, strong vignette. Set any other field to override individual knobs on top of the preset."),
9
+ zoom: z.object({
10
+ from: z.number().optional().describe("Starting scale for zoom-in / ending scale for zoom-out. Default 1.0."),
11
+ to: z.number().optional().describe("Ending scale for zoom-in / starting scale for zoom-out. Default 1.3 (cover) / 1.15 (blur-pad)."),
12
+ }).optional().describe("Zoom range. Lower 'to' (e.g. 1.05) for near-static cinematography; higher (e.g. 1.4) for dramatic push-in."),
13
+ pan: z.object({
14
+ range: z.number().optional().describe("Pan drift as fraction of viewport on each side. Default 0.05 (cover) / 0.03 (blur-pad). 0.08+ feels like sweeping."),
15
+ scale: z.number().optional().describe("Baseline scale during pan effects. Must exceed 1 + 2×range so edges never show. Default 1.12 / 1.08."),
16
+ }).optional(),
17
+ zoomPan: z.object({
18
+ from: z.number().optional(),
19
+ to: z.number().optional(),
20
+ rangeFraction: z.number().optional().describe("Multiplier on pan.range for zoom-in-pan-* effects. Default 0.5."),
21
+ }).optional(),
22
+ easing: z.enum(["ease-in-out", "ease-in", "ease-out", "linear"]).optional().describe("Motion curve. 'ease-in-out' = cinematic default. 'linear' = mechanical/deliberate. 'ease-out' = fast-start slow-finish, good for reveals."),
23
+ blurPad: z.object({
24
+ blurPx: z.number().optional().describe("Blur strength on the background layer. Default 40. 60+ for heavy dreamy effect, 20 for just-barely."),
25
+ brightness: z.number().optional().describe("Background brightness multiplier. Default 0.55 (dimmed). Raise to 0.8+ for a brighter bed; lower for moodier."),
26
+ saturate: z.number().optional().describe("Background saturation. Default 1.15. Lower (0.6) for desaturated/muted bg; higher for vivid."),
27
+ overscale: z.number().optional().describe("Background overscale to hide blur edges. Default 1.12."),
28
+ foregroundScale: z.number().optional().describe("Gentle crop on the contain-fit sharp layer. Default 1.0 (pure contain). 1.2 reduces letterbox by accepting mild crop."),
29
+ }).optional().describe("Styling for the blurred-letterbox layer. Only applies when fit resolves to 'blur-pad' (landscape source in portrait viewport)."),
30
+ vignette: z.union([
31
+ z.literal(false),
32
+ z.object({
33
+ opacity: z.number().optional().describe("Edge darkness opacity, 0-1. Default 0.35 (cover) / 0.4 (blur-pad)."),
34
+ innerRadiusPercent: z.number().optional().describe("Where darkening starts, 0 = center, 100 = edge. Default 50-55."),
35
+ }),
36
+ ]).optional().describe("Edge darkening for cinematic framing. Pass false to disable."),
37
+ backgroundColor: z.string().optional().describe("Hex color behind the image frame. Default '#000'."),
38
+ autoBlurPadThreshold: z.number().optional().describe("When fit='auto', trigger blur-pad if aspectRatio > this value. Default 1.1."),
39
+ }).describe("Creative style overrides. Every field optional - unset inherits defaults.");
3
40
  export function registerGenerateSlideshowTool(server) {
4
41
  server.registerTool("clipform_generate_slideshow", {
5
- title: "Generate Ken Burns Slideshow",
6
- description: `Generate a Ken Burns slideshow video from images and audio. Uses FFmpeg to create a video with pan/zoom effects and crossfade transitions. The video is uploaded to storage and a public URL is returned.
42
+ title: "Generate Slideshow",
43
+ description: `**Deprecated: prefer clipform_generate_video** which supports both images and video clips.
7
44
 
8
- Workflow:
9
- 1. Find images (use clipform_search_media with kind: "image", or provide URLs)
10
- 2. Provide an audio track URL (e.g. TTS narration from clipform_generate_tts)
11
- 3. This tool creates a video with Ken Burns pan/zoom effects over the images, synced to the audio duration
12
- 4. Attach the resulting public URL to a question using clipform_upload_media with media_type "video"
45
+ Generate a slideshow video from images and audio. Creates a 9:16 (720x1280) video with smooth pan/zoom effects (Ken Burns style), eased motion, and crossfade transitions, synced to the audio duration. Uploaded to storage; returns a public URL.
13
46
 
14
- Effects: zoom-in, zoom-out, pan-left, pan-right, pan-up, pan-down, zoom-in-pan-left, zoom-in-pan-right, random, static.
15
- Transitions: fade, fadeblack, fadewhite, slideleft, slideright, circlecrop, circleopen, circleclose, dissolve, pixelize, radial, smoothleft, smoothright.`,
47
+ You are the director. Every stylistic choice below is yours - defaults exist for convenience but override anything that fits your creative vision.
48
+
49
+ ## Workflow
50
+
51
+ 1. Source images (use clipform_search_media with kind: "image"). Prefer portrait sources for 9:16 output; landscape works too via blur-pad fallback.
52
+ 2. Produce narration audio (clipform_generate_tts).
53
+ 3. Call this tool with images + audio_url + your creative direction.
54
+ 4. Attach the returned public URL to a question via clipform_upload_media with media_type "video".
55
+
56
+ ## Image framing (per-image)
57
+
58
+ - **aspect_ratio** (width/height): pass through from the image source so the composition can auto-pick framing. Landscape images with no aspect_ratio will cover-crop and may pixelate.
59
+ - **fit**: 'cover' (fill frame, crop), 'blur-pad' (sharp contained foreground + blurred cover background for landscape sources), 'auto' (default: cover for portrait, blur-pad for landscape > 1.1:1).
60
+ - **style**: per-image creative overrides (see Style Knobs below).
61
+
62
+ ## Effects
63
+
64
+ zoom-in, zoom-out, pan-left, pan-right, pan-up, pan-down, zoom-in-pan-left, zoom-in-pan-right, random, static. Set 'random' or pass random_effects: true to shuffle per image.
65
+
66
+ ## Transitions
67
+
68
+ fade (default), slide, wipe. Legacy names (fadeblack, slideleft, etc.) also work. duration is in seconds.
69
+
70
+ ## Style presets (fastest path)
71
+
72
+ Pick one as a starting point via \`style.preset\` (per-image) or \`default_style.preset\` (global). Override individual knobs on top.
73
+
74
+ - **cinematic** - the default feel. Smooth ease-in-out, zoom 1.0→1.3, subtle pan, subtle vignette.
75
+ - **dramatic** - zoom 1.0→1.4, ease-out, wider pan (0.07), strong vignette. Good for big-reveal content, competitions, climactic moments.
76
+ - **calm** - zoom 1.0→1.08, linear easing, no vignette. Reflective topics, wellness, educational explainers.
77
+ - **documentary** - near-static (zoom 1.0→1.05, tiny pan, no vignette). Talking-heads-style context shots, historical photos.
78
+ - **dreamy** - heavy blur-pad (blurPx 60, brightness 0.75), soft vignette. Aspirational, romantic, nostalgic.
79
+ - **moody** - desaturated blur-pad (brightness 0.35, saturate 0.7), strong vignette, dark wrapper color. Crime, mystery, somber topics.
80
+
81
+ Per-image preset wins over default_style preset. Per-field overrides (zoom, pan, etc.) merge on top of whichever preset is active.
82
+
83
+ ## Style Knobs (per-image via images[i].style, or global via default_style)
84
+
85
+ - **zoom**: { from, to } - push-in/pull-out range. Cinematic default 1.0→1.3. Drop to 1.0→1.05 for near-static; push to 1.0→1.4 for dramatic.
86
+ - **pan**: { range, scale } - drift magnitude. 0.03 subtle, 0.05 default, 0.08+ sweeping.
87
+ - **easing**: 'ease-in-out' (default, cinematic) | 'ease-in' (accelerate) | 'ease-out' (decelerate, good for reveals) | 'linear' (mechanical).
88
+ - **blurPad**: { blurPx, brightness, saturate, overscale, foregroundScale } - only applies when blur-pad fit is active. Heavier blurPx (60) + lower brightness (0.35) for moody; foregroundScale: 1.2 to reduce letterbox at the cost of mild crop.
89
+ - **vignette**: false to disable, or { opacity, innerRadiusPercent }. Stronger vignette (opacity 0.6) for dramatic focus; disable for bright, flat looks.
90
+ - **backgroundColor**: hex color shown behind the image frame (default '#000').
91
+ - **autoBlurPadThreshold**: when fit='auto', images wider than this ratio get blur-pad. Default 1.1.
92
+
93
+ ## Global options
94
+
95
+ - **default_style**: style applied to every image unless the image overrides per-field. Per-image style merges over default_style at the field level.
96
+ - **background_color**: viewport wrapper color, visible during cross-fades.
97
+ - **random_effects**: shuffles effects across images when true.
98
+
99
+ ## Creative guidance
100
+
101
+ - For the same 5-image quiz slideshow, vary the effects (one zoom-in on the opener, pan-left on a cityscape, zoom-out on the closer) rather than random_effects: true, unless you want the dice-roll.
102
+ - Quiet/reflective topics: slow easing ('linear'), tight zoom (1.0→1.08), no vignette.
103
+ - Energetic topics: punchy zoom (1.0→1.35), ease-out, wider pan.
104
+ - Mixed orientation sets: rely on fit: 'auto' + pass aspect_ratio per image. The composition will cover-crop portraits and blur-pad landscapes without more work.`,
16
105
  inputSchema: {
17
106
  images: z.array(z.object({
18
107
  url: z.string().url().describe("Image URL"),
19
- effect: z.string().optional().describe("Ken Burns effect (e.g. 'zoom-in', 'pan-left', 'random'). Default: random"),
108
+ effect: z.string().optional().describe("Ken Burns effect name (see description). Pass 'random' to shuffle this image only."),
109
+ aspect_ratio: z.number().positive().optional().describe("Source image width / height. Enables auto blur-pad for landscape images in the 9:16 viewport."),
110
+ fit: z.enum(["cover", "blur-pad", "auto"]).optional().describe("Framing mode. 'auto' (default) picks cover for portrait, blur-pad for landscape."),
111
+ style: slideshowStyleSchema.optional(),
20
112
  })).min(1).max(20).describe("Images for the slideshow (1-20)"),
21
- audio_url: z.string().url().describe("URL of the audio track (mp3/wav)"),
22
- random_effects: z.boolean().optional().default(true).describe("Randomise effects (default: true)"),
113
+ audio_url: z.string().url().describe("URL of the audio track (mp3/wav). Slideshow duration matches audio duration."),
114
+ random_effects: z.boolean().optional().default(true).describe("Shuffle effects across all images (default: true). Set false when specifying effects explicitly per-image."),
23
115
  transition: z.object({
24
- type: z.string().optional().default("fade").describe("Transition type (default: 'fade')"),
25
- duration: z.number().optional().default(1).describe("Transition duration in seconds (default: 1)"),
26
- }).optional().describe("Transition settings"),
116
+ type: z.string().optional().default("fade").describe("Transition type: fade (default), slide, wipe."),
117
+ duration: z.number().optional().default(1).describe("Transition duration in seconds (default: 1)."),
118
+ }).optional().describe("Transition between images."),
119
+ default_style: slideshowStyleSchema.optional().describe("Style applied to every image unless overridden per-image."),
120
+ background_color: z.string().optional().describe("Viewport background color behind all images (default '#000')."),
27
121
  },
28
122
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
29
- }, async ({ images, audio_url, random_effects, transition }) => {
30
- const result = await callInternalApi("/internal/media/slideshow", {
123
+ }, async ({ images, audio_url, random_effects, transition, default_style, background_color }) => {
124
+ const workspace_id = process.env.MCP_WORKSPACE_ID;
125
+ const job = createJob("clipform_generate_slideshow");
126
+ callInternalApi("/internal/slideshow", {
31
127
  body: {
32
128
  images,
33
129
  audio_url,
34
130
  random_effects: random_effects ?? true,
35
131
  transition: transition ?? { type: "fade", duration: 1 },
132
+ default_style,
133
+ background_color,
134
+ workspace_id,
36
135
  },
136
+ }).then((result) => {
137
+ if (!result.ok) {
138
+ failJob(job.id, result.error);
139
+ }
140
+ else {
141
+ completeJob(job.id, result.data);
142
+ }
143
+ }).catch((err) => {
144
+ failJob(job.id, err instanceof Error ? err.message : String(err));
37
145
  });
38
- if (!result.ok)
39
- return errorResult(result.error);
40
- const data = result.data;
41
146
  return textResult([
42
- `Slideshow generated successfully!`,
147
+ `Slideshow render started (${images.length} image${images.length > 1 ? "s" : ""}).`,
43
148
  ``,
44
- `Storage path: ${data.storage_path}`,
45
- `Public URL: ${data.public_url}`,
46
- `Duration: ${data.duration_seconds}s`,
149
+ `Job ID: ${job.id}`,
47
150
  ``,
48
- `Use clipform_upload_media with the public URL to attach this video to a question.`,
151
+ `Renders typically take 1-3 minutes. Use clipform_check_render with this job ID to check status.`,
49
152
  ].join("\n"));
50
153
  });
51
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generate-slideshow.js","sourceRoot":"","sources":["../../src/tools/generate-slideshow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEhF,MAAM,UAAU,6BAA6B,CAAC,MAAiB;IAC7D,MAAM,CAAC,YAAY,CACjB,6BAA6B,EAC7B;QACE,KAAK,EAAE,8BAA8B;QACrC,WAAW,EAAE;;;;;;;;;0JASuI;QACpJ,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,CAAC,KAAK,CACb,CAAC,CAAC,MAAM,CAAC;gBACP,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC;aACnH,CAAC,CACH,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YAC5D,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;YACxE,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,mCAAmC,CAAC;YAClG,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;gBACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,mCAAmC,CAAC;gBACzF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,6CAA6C,CAAC;aACnG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;SAC9C;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE;KACzG,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,EAAE,EAAE;QAC1D,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,2BAA2B,EAAE;YAChE,IAAI,EAAE;gBACJ,MAAM;gBACN,SAAS;gBACT,cAAc,EAAE,cAAc,IAAI,IAAI;gBACtC,UAAU,EAAE,UAAU,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE;aACxD;SACF,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEjD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAW,CAAC;QAChC,OAAO,UAAU,CACf;YACE,mCAAmC;YACnC,EAAE;YACF,iBAAiB,IAAI,CAAC,YAAY,EAAE;YACpC,eAAe,IAAI,CAAC,UAAU,EAAE;YAChC,aAAa,IAAI,CAAC,gBAAgB,GAAG;YACrC,EAAE;YACF,mFAAmF;SACpF,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"generate-slideshow.js","sourceRoot":"","sources":["../../src/tools/generate-slideshow.ts"],"names":[],"mappings":"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"}
@@ -1,48 +1,73 @@
1
1
  import { z } from "zod";
2
2
  import { callInternalApi, errorResult, textResult } from "../lib/api-client.js";
3
+ const TtsItemSchema = z.object({
4
+ text: z.string().min(1).max(5000).describe("Narration text"),
5
+ voice: z
6
+ .enum(["ryan", "sonia", "andrew", "ava", "guy"])
7
+ .optional()
8
+ .default("ryan")
9
+ .describe("TTS voice (default: ryan)"),
10
+ });
3
11
  export function registerGenerateTtsTool(server) {
4
12
  server.registerTool("clipform_generate_tts", {
5
13
  title: "Generate Text-to-Speech",
6
- description: `Generate narration audio from text using Edge TTS (free) with ElevenLabs fallback. The audio is uploaded to storage and a public URL is returned.
14
+ description: `Generate narration audio from text using Edge TTS (free) with ElevenLabs fallback. Pass one item or many (max 10) - multiple items run in parallel.
7
15
 
8
- Use this to create narration for slideshow videos. Workflow:
16
+ Workflow:
9
17
  1. Generate TTS audio with this tool
10
18
  2. Search for images with clipform_search_media (kind: "image")
11
19
  3. Create a slideshow with clipform_generate_slideshow (pass the audio URL + image URLs)
12
- 4. Attach the video to a question with clipform_upload_media
20
+ 4. Attach the video to a node with clipform_upload_node_media
13
21
 
14
- Available voices:
15
- - ryan: British male (default)
16
- - sonia: British female
17
- - andrew: American male
18
- - ava: American female
19
- - guy: American male (casual)
22
+ Available voices: ryan (British male, default), sonia (British female), andrew (American male), ava (American female), guy (American male casual).
20
23
 
21
- Returns the audio public URL and word-level captions for subtitle display.`,
24
+ Returns audio URL and word-level captions per item.`,
22
25
  inputSchema: {
23
- text: z.string().min(1).max(5000).describe("The narration text to convert to speech"),
24
- voice: z.enum(["ryan", "sonia", "andrew", "ava", "guy"]).optional().default("ryan").describe("TTS voice (default: ryan)"),
26
+ items: z
27
+ .array(TtsItemSchema)
28
+ .min(1)
29
+ .max(10)
30
+ .describe("One or more TTS items to generate"),
25
31
  },
26
32
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
27
- }, async ({ text, voice }) => {
28
- const result = await callInternalApi("/internal/formgen/generate-tts", {
29
- body: { text, voice },
30
- });
31
- if (!result.ok)
32
- return errorResult(result.error);
33
- const data = result.data;
34
- return textResult([
35
- `TTS audio generated successfully!`,
36
- ``,
37
- `Voice: ${data.voice}`,
38
- `Audio URL: ${data.audioUrl}`,
39
- `Storage path: ${data.storagePath}`,
40
- `Captions: ${data.captions.length} segments`,
41
- `Captions JSON: ${JSON.stringify(data.captions)}`,
42
- ``,
43
- `Use the Audio URL with clipform_generate_slideshow to create a video.`,
44
- `IMPORTANT: When uploading the final video with clipform_upload_question_media, pass the COMPLETE Captions JSON above as the "captions" parameter — including the "words" arrays inside each segment. Do NOT strip or simplify the JSON. The viewer needs word-level timing data to display captions. Captions must always be enabled for narrated content.`,
45
- ].join("\n"));
33
+ }, async ({ items }) => {
34
+ const workspace_id = process.env.MCP_WORKSPACE_ID;
35
+ const results = await Promise.allSettled(items.map((item) => callInternalApi("/internal/tts", {
36
+ body: { text: item.text, voice: item.voice, workspace_id },
37
+ })));
38
+ const lines = [];
39
+ let successCount = 0;
40
+ for (let i = 0; i < results.length; i++) {
41
+ const r = results[i];
42
+ if (items.length > 1)
43
+ lines.push(`--- Item ${i + 1} ---`);
44
+ if (r.status === "fulfilled" && r.value.ok) {
45
+ successCount++;
46
+ const data = r.value.data;
47
+ lines.push(`Voice: ${data.voice}`);
48
+ lines.push(`Audio URL: ${data.audioUrl}`);
49
+ lines.push(`Storage path: ${data.storagePath}`);
50
+ lines.push(`Captions: ${JSON.stringify(data.captions)}`);
51
+ }
52
+ else {
53
+ const error = r.status === "rejected"
54
+ ? r.reason?.message || String(r.reason)
55
+ : r.value.error;
56
+ const status = r.status === "fulfilled" ? r.value.status : 0;
57
+ const hint = status === 500 || status === 0
58
+ ? " (transient - retrying may help)"
59
+ : "";
60
+ lines.push(`FAILED: ${error}${hint}`);
61
+ }
62
+ lines.push("");
63
+ }
64
+ if (items.length > 1) {
65
+ lines.unshift(`TTS: ${successCount}/${items.length} succeeded\n`);
66
+ }
67
+ lines.push(`IMPORTANT: When uploading the final video with clipform_upload_node_media, pass the COMPLETE Captions JSON above as the "captions" parameter — including the "words" arrays inside each segment. Do NOT strip or simplify the JSON. The viewer needs word-level timing data to display captions.`);
68
+ if (successCount === 0)
69
+ return errorResult(lines.join("\n"));
70
+ return textResult(lines.join("\n"));
46
71
  });
47
72
  }
48
73
  //# sourceMappingURL=generate-tts.js.map