@clipform/mcp-server 1.7.0 → 1.8.1
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 +43 -29
- package/dist/__tests__/api-parity.test.js +31 -22
- package/dist/__tests__/api-parity.test.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts +3 -2
- package/dist/lib/api-client.js +10 -3
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/format-form.d.ts +2 -0
- package/dist/lib/format-form.js +32 -0
- package/dist/lib/format-form.js.map +1 -0
- package/dist/lib/render-jobs.d.ts +17 -0
- package/dist/lib/render-jobs.js +44 -0
- package/dist/lib/render-jobs.js.map +1 -0
- package/dist/lib/schemas.d.ts +4 -4
- package/dist/lib/schemas.js +11 -11
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/session-context.js +9 -3
- package/dist/lib/session-context.js.map +1 -1
- package/dist/prompts.js +31 -10
- package/dist/prompts.js.map +1 -1
- package/dist/resources.js +3 -3
- package/dist/resources.js.map +1 -1
- package/dist/server.js +25 -21
- package/dist/server.js.map +1 -1
- package/dist/tools/add-node.d.ts +2 -0
- package/dist/tools/add-node.js +48 -0
- package/dist/tools/add-node.js.map +1 -0
- package/dist/tools/attach-node-audio.d.ts +2 -0
- package/dist/tools/attach-node-audio.js +35 -0
- package/dist/tools/attach-node-audio.js.map +1 -0
- package/dist/tools/{add-question.d.ts → check-render.d.ts} +1 -1
- package/dist/tools/check-render.js +47 -0
- package/dist/tools/check-render.js.map +1 -0
- package/dist/tools/create-form.js +46 -46
- package/dist/tools/create-form.js.map +1 -1
- package/dist/tools/delete-form.js +1 -3
- package/dist/tools/delete-form.js.map +1 -1
- package/dist/tools/delete-node-media.d.ts +2 -0
- package/dist/tools/delete-node-media.js +25 -0
- package/dist/tools/delete-node-media.js.map +1 -0
- package/dist/tools/delete-node.d.ts +2 -0
- package/dist/tools/delete-node.js +28 -0
- package/dist/tools/delete-node.js.map +1 -0
- package/dist/tools/generate-slideshow.js +16 -10
- package/dist/tools/generate-slideshow.js.map +1 -1
- package/dist/tools/generate-tts.js +55 -31
- package/dist/tools/generate-tts.js.map +1 -1
- package/dist/tools/generate-video.js +17 -11
- package/dist/tools/generate-video.js.map +1 -1
- package/dist/tools/get-form.js +4 -31
- package/dist/tools/get-form.js.map +1 -1
- package/dist/tools/get-node-media.d.ts +2 -0
- package/dist/tools/{get-question-media.js → get-node-media.js} +9 -13
- package/dist/tools/get-node-media.js.map +1 -0
- package/dist/tools/list-forms.d.ts +2 -0
- package/dist/tools/list-forms.js +86 -0
- package/dist/tools/list-forms.js.map +1 -0
- package/dist/tools/log-generation.js +1 -2
- package/dist/tools/log-generation.js.map +1 -1
- package/dist/tools/render-composition.js +18 -13
- package/dist/tools/render-composition.js.map +1 -1
- package/dist/tools/search-media.js +1 -1
- package/dist/tools/search-media.js.map +1 -1
- package/dist/tools/set-node-logic.d.ts +2 -0
- package/dist/tools/set-node-logic.js +54 -0
- package/dist/tools/set-node-logic.js.map +1 -0
- package/dist/tools/update-form.js +29 -11
- package/dist/tools/update-form.js.map +1 -1
- package/dist/tools/update-node.d.ts +2 -0
- package/dist/tools/{update-question.js → update-node.js} +14 -12
- package/dist/tools/update-node.js.map +1 -0
- package/dist/tools/upload-node-media.d.ts +2 -0
- package/dist/tools/upload-node-media.js +105 -0
- package/dist/tools/upload-node-media.js.map +1 -0
- package/package.json +1 -1
- package/dist/tools/add-question.js +0 -47
- package/dist/tools/add-question.js.map +0 -1
- package/dist/tools/attach-question-audio.d.ts +0 -2
- package/dist/tools/attach-question-audio.js +0 -37
- package/dist/tools/attach-question-audio.js.map +0 -1
- package/dist/tools/delete-question-media.d.ts +0 -2
- package/dist/tools/delete-question-media.js +0 -29
- package/dist/tools/delete-question-media.js.map +0 -1
- package/dist/tools/delete-question.d.ts +0 -2
- package/dist/tools/delete-question.js +0 -29
- package/dist/tools/delete-question.js.map +0 -1
- package/dist/tools/get-question-media.d.ts +0 -2
- package/dist/tools/get-question-media.js.map +0 -1
- package/dist/tools/set-question-logic.d.ts +0 -2
- package/dist/tools/set-question-logic.js +0 -54
- package/dist/tools/set-question-logic.js.map +0 -1
- package/dist/tools/update-question.d.ts +0 -2
- package/dist/tools/update-question.js.map +0 -1
- package/dist/tools/upload-question-media.d.ts +0 -2
- package/dist/tools/upload-question-media.js +0 -77
- package/dist/tools/upload-question-media.js.map +0 -1
package/dist/prompts.js
CHANGED
|
@@ -27,7 +27,7 @@ export function registerPrompts(server) {
|
|
|
27
27
|
3. **Create the form** with clipform_create_form:
|
|
28
28
|
- show_step_counter: true
|
|
29
29
|
- disable_back_navigation: true
|
|
30
|
-
4. **Add questions** with
|
|
30
|
+
4. **Add questions** with clipform_add_node (type: "choice"):
|
|
31
31
|
- config: { choice: { show_answer_feedback: true } }
|
|
32
32
|
- randomise_options: true in config
|
|
33
33
|
- score: 1 on correct option, score: 0 on wrong
|
|
@@ -36,10 +36,20 @@ export function registerPrompts(server) {
|
|
|
36
36
|
6. **Build video** for each question:
|
|
37
37
|
- clipform_search_media (kind: "image") - 3 images per question
|
|
38
38
|
- clipform_generate_video - creates Ken Burns video synced to audio
|
|
39
|
-
7. **Attach media** with
|
|
40
|
-
8. **Update end screen** -
|
|
39
|
+
7. **Attach media** with clipform_upload_node_media. Include captions, set show_captions: true.
|
|
40
|
+
8. **Update end screen** with clipform_update_node - EVERY quiz must have:
|
|
41
|
+
- show_score: true, icon: "trophy"
|
|
42
|
+
- show_share_button: true (drives virality)
|
|
43
|
+
- cta_type: "restart", cta_text: a short challenge like "Beat your score?" or "Try again?"
|
|
44
|
+
- score_ranges with personalised title + message per tier. Write these in the quiz's voice - short, punchy, and specific to the topic (not generic "Good job!"). Example for a geography quiz:
|
|
45
|
+
\`\`\`json
|
|
46
|
+
{ "min": 0, "max": 2, "title": "Lost Tourist", "message": "You might need a map - and a compass." },
|
|
47
|
+
{ "min": 3, "max": 5, "title": "Frequent Flyer", "message": "Not bad! You know your way around." },
|
|
48
|
+
{ "min": 6, "max": 8, "title": "World Explorer", "message": "Impressive - you really know your stuff." }
|
|
49
|
+
\`\`\`
|
|
41
50
|
9. **Publish** with clipform_update_form
|
|
42
|
-
10. **
|
|
51
|
+
10. **Tag the form** - pass tags: one format (quiz/survey/interview/feedback/lead-gen), one genre (trivia/personality/nps/poll/testimonial), and 2-3 topic words
|
|
52
|
+
11. **Log** with clipform_log_generation (sources, images, attributions)
|
|
43
53
|
|
|
44
54
|
## Before building, ask
|
|
45
55
|
|
|
@@ -82,26 +92,34 @@ There are NO correct answers. Each option maps to one or more outcome categories
|
|
|
82
92
|
3. **Create the form** with clipform_create_form:
|
|
83
93
|
- show_step_counter: true
|
|
84
94
|
- disable_back_navigation: true
|
|
85
|
-
4. **Add questions** with
|
|
95
|
+
4. **Add questions** with clipform_add_node (type: "choice"):
|
|
86
96
|
- config: { choice: { show_answer_feedback: false } } (no right/wrong!)
|
|
87
97
|
- Do NOT set randomise_options (option order matters for personality quizzes - lead with the most appealing)
|
|
88
98
|
- Each option gets \`scores: { "CategoryA": 2, "CategoryB": 1 }\` - weight towards relevant categories
|
|
89
99
|
- Every option should score in at least one category (no dead options)
|
|
90
100
|
5. **Generate narration** with clipform_generate_tts - conversational, reflective tone. "What does this say about you?" not "Do you know the answer?"
|
|
91
101
|
6. **Build video** + **attach media** (same as knowledge quiz workflow)
|
|
92
|
-
7. **Update end screen** with
|
|
102
|
+
7. **Update end screen** with clipform_update_node - EVERY personality quiz must have:
|
|
103
|
+
- show_score: false, icon: "star"
|
|
104
|
+
- show_share_button: true (personality results are inherently shareable - "I got X, what did you get?")
|
|
105
|
+
- cta_type: "restart", cta_text: "Find out again?" or "Take it again?"
|
|
106
|
+
- scoring_results (NOT score_ranges) with a result per category. Write the title as an identity reveal ("You're a Creative!") and the message as a short, flattering description that makes people want to share it. Be specific to the quiz theme, not generic. Example:
|
|
93
107
|
\`\`\`json
|
|
94
108
|
{
|
|
95
109
|
"show_score": false,
|
|
96
110
|
"icon": "star",
|
|
111
|
+
"show_share_button": true,
|
|
112
|
+
"cta_type": "restart",
|
|
113
|
+
"cta_text": "Retake quiz",
|
|
97
114
|
"scoring_results": [
|
|
98
|
-
{ "category": "Creative", "title": "You're a Creative!", "message": "You see the world
|
|
99
|
-
{ "category": "Analytical", "title": "You're an Analyst!", "message": "You
|
|
115
|
+
{ "category": "Creative", "title": "You're a Creative!", "message": "You see the world through colour and possibility. Where others see problems, you see raw material." },
|
|
116
|
+
{ "category": "Analytical", "title": "You're an Analyst!", "message": "You don't guess - you figure it out. Your superpower is turning chaos into clarity." }
|
|
100
117
|
]
|
|
101
118
|
}
|
|
102
119
|
\`\`\`
|
|
103
120
|
8. **Publish** with clipform_update_form
|
|
104
|
-
9. **
|
|
121
|
+
9. **Tag the form** - pass tags: one format (quiz/survey/interview/feedback/lead-gen), one genre (trivia/personality/nps/poll/testimonial), and 2-3 topic words
|
|
122
|
+
10. **Log** with clipform_log_generation
|
|
105
123
|
|
|
106
124
|
## Before building, ask
|
|
107
125
|
|
|
@@ -146,6 +164,7 @@ There are NO correct answers. Each option maps to one or more outcome categories
|
|
|
146
164
|
7. **Update end screen** - set expectations: "Thanks! We'll be in touch."
|
|
147
165
|
8. **Optional: add narration** - warm, inviting tone. "We'd love to hear your story..."
|
|
148
166
|
9. **Publish** with clipform_update_form
|
|
167
|
+
10. **Tag the form** - pass tags: one format (quiz/survey/interview/feedback/lead-gen), one genre (trivia/personality/nps/poll/testimonial), and 2-3 topic words
|
|
149
168
|
|
|
150
169
|
## Before building, ask
|
|
151
170
|
|
|
@@ -189,6 +208,7 @@ There are NO correct answers. Each option maps to one or more outcome categories
|
|
|
189
208
|
6. **Contact** (optional) - only if you need to follow up. Many surveys work better anonymous.
|
|
190
209
|
7. **Update end screen** - "Thanks for your feedback!"
|
|
191
210
|
8. **Publish** with clipform_update_form
|
|
211
|
+
9. **Tag the form** - pass tags: one format (quiz/survey/interview/feedback/lead-gen), one genre (trivia/personality/nps/poll/testimonial), and 2-3 topic words
|
|
192
212
|
|
|
193
213
|
## Key rule: 5 questions max. Every extra question costs completions.
|
|
194
214
|
|
|
@@ -230,10 +250,11 @@ There are NO correct answers. Each option maps to one or more outcome categories
|
|
|
230
250
|
- disable_back_navigation: true (prevents answer shopping that breaks scoring)
|
|
231
251
|
3. **Add hook question** (type: "choice") - "What best describes you?" or "What are you looking for?" This segments the user.
|
|
232
252
|
4. **Add qualifying questions** (type: "choice") - 2-3 questions that narrow down the need. Assign scores to each option.
|
|
233
|
-
5. **Set branching logic** with
|
|
253
|
+
5. **Set branching logic** with clipform_set_logic - route based on answers
|
|
234
254
|
6. **Add contact capture** (type: "contact") - name, email, phone. Place AFTER qualifying questions.
|
|
235
255
|
7. **Update end screen** with score_ranges for personalised outcomes: "Based on your answers, we recommend..."
|
|
236
256
|
8. **Publish** with clipform_update_form
|
|
257
|
+
9. **Tag the form** - pass tags: one format (quiz/survey/interview/feedback/lead-gen), one genre (trivia/personality/nps/poll/testimonial), and 2-3 topic words
|
|
237
258
|
|
|
238
259
|
## Key rule: 3-5 questions max. Every extra step loses leads.
|
|
239
260
|
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,cAAc,CACnB,aAAa,EACb;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,6DAA6D;KAC3E,EACD,KAAK,IAAI,EAAE;QACT,MAAM,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;QACjD,OAAO;YACP,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAe;oBACrB,OAAO,EAAE;wBACP,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,oDAAoD;qBAC3D;iBACF;gBACD;oBACE,IAAI,EAAE,WAAoB;oBAC1B,OAAO,EAAE;wBACP,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"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"}
|
package/dist/resources.js
CHANGED
|
@@ -25,7 +25,7 @@ const MEDIA_WORKFLOW = `## Media Workflow
|
|
|
25
25
|
1. **clipform_generate_tts** - narration audio (returns word-level captions)
|
|
26
26
|
2. **clipform_search_media** (kind: "image") - find 3 images per question
|
|
27
27
|
3. **clipform_generate_video** - Ken Burns video from images + audio
|
|
28
|
-
4. **
|
|
28
|
+
4. **clipform_upload_node_media** - attach video with captions (set show_captions: true)
|
|
29
29
|
|
|
30
30
|
**Image selection:**
|
|
31
31
|
- ONLY use URLs from clipform_search_media results
|
|
@@ -287,7 +287,7 @@ Route people to the right outcome based on their answers. Lead qualification sco
|
|
|
287
287
|
|
|
288
288
|
## Branching Logic
|
|
289
289
|
|
|
290
|
-
Use
|
|
290
|
+
Use clipform_set_logic to route based on answers:
|
|
291
291
|
|
|
292
292
|
- **Segment early.** Q1 answer determines which Q2 they see.
|
|
293
293
|
- **Converge at capture.** All branches should reach the contact collection step.
|
|
@@ -317,7 +317,7 @@ ${WRITING_PRINCIPLES}`,
|
|
|
317
317
|
],
|
|
318
318
|
}));
|
|
319
319
|
server.registerResource("context-session", "clipform://context/session", {
|
|
320
|
-
description: "Current session info: auth mode, workspace, plan tier,
|
|
320
|
+
description: "Current session info: auth mode, workspace, plan tier, node limits, feature flags. Read this before planning content to know your constraints.",
|
|
321
321
|
mimeType: "text/markdown",
|
|
322
322
|
annotations: { audience: ["assistant"], priority: 1.0 },
|
|
323
323
|
}, async () => {
|
package/dist/resources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;mNAoBwL,CAAC;AAEpN,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;gFAgByD,CAAC;AAEjF,MAAM,UAAU,iBAAiB,CAAC,MAAiB;IACjD,MAAM,CAAC,gBAAgB,CACrB,YAAY,EACZ,wBAAwB,EACxB;QACE,WAAW,EACT,iHAAiH;QACnH,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,wBAAwB;gBAC7B,QAAQ,EAAE,eAAe;gBACzB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0Cd,kBAAkB;;EAElB,cAAc,EAAE;aACT;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,wBAAwB,EACxB,oCAAoC,EACpC;QACE,WAAW,EACT,+HAA+H;QACjI,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,oCAAoC;gBACzC,QAAQ,EAAE,eAAe;gBACzB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoDd,kBAAkB;;EAElB,cAAc,EAAE;aACT;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,iBAAiB,EACjB,6BAA6B,EAC7B;QACE,WAAW,EACT,oIAAoI;QACtI,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,6BAA6B;gBAClC,QAAQ,EAAE,eAAe;gBACzB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCd,kBAAkB;;EAElB,cAAc,EAAE;aACT;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,cAAc,EACd,0BAA0B,EAC1B;QACE,WAAW,EACT,4GAA4G;QAC9G,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,0BAA0B;gBAC/B,QAAQ,EAAE,eAAe;gBACzB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCd,kBAAkB,EAAE;aACb;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,cAAc,EACd,0BAA0B,EAC1B;QACE,WAAW,EACT,wIAAwI;QAC1I,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,0BAA0B;gBAC/B,QAAQ,EAAE,eAAe;gBACzB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+Cd,kBAAkB,EAAE;aACb;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,iBAAiB,EACjB,4BAA4B,EAC5B;QACE,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
// Form CRUD tools
|
|
3
3
|
import { registerCreateFormTool } from "./tools/create-form.js";
|
|
4
|
+
import { registerListFormsTool } from "./tools/list-forms.js";
|
|
4
5
|
import { registerGetFormTool } from "./tools/get-form.js";
|
|
5
6
|
import { registerUpdateFormTool } from "./tools/update-form.js";
|
|
6
7
|
import { registerDeleteFormTool } from "./tools/delete-form.js";
|
|
7
|
-
//
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
//
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
8
|
+
// Node CRUD tools
|
|
9
|
+
import { registerAddNodeTool } from "./tools/add-node.js";
|
|
10
|
+
import { registerUpdateNodeTool } from "./tools/update-node.js";
|
|
11
|
+
import { registerDeleteNodeTool } from "./tools/delete-node.js";
|
|
12
|
+
// Node media & logic tools
|
|
13
|
+
import { registerUploadNodeMediaTool } from "./tools/upload-node-media.js";
|
|
14
|
+
import { registerGetNodeMediaTool } from "./tools/get-node-media.js";
|
|
15
|
+
import { registerDeleteNodeMediaTool } from "./tools/delete-node-media.js";
|
|
16
|
+
import { registerSetNodeLogicTool } from "./tools/set-node-logic.js";
|
|
17
|
+
import { registerAttachNodeAudioTool } from "./tools/attach-node-audio.js";
|
|
17
18
|
// Audit logging
|
|
18
19
|
import { registerLogGenerationTool } from "./tools/log-generation.js";
|
|
19
20
|
// Content research tools
|
|
@@ -27,29 +28,31 @@ import { registerSearchMusicTool } from "./tools/search-music.js";
|
|
|
27
28
|
import { registerListCompositionsTool } from "./tools/list-compositions.js";
|
|
28
29
|
import { registerListAssetsTool } from "./tools/list-assets.js";
|
|
29
30
|
import { registerGenerateVideoTool } from "./tools/generate-video.js";
|
|
31
|
+
import { registerCheckRenderTool } from "./tools/check-render.js";
|
|
30
32
|
// Prompts & Resources
|
|
31
33
|
import { registerPrompts } from "./prompts.js";
|
|
32
34
|
import { registerResources } from "./resources.js";
|
|
33
35
|
export function createServer() {
|
|
34
36
|
const server = new McpServer({
|
|
35
37
|
name: "clipform-mcp-server",
|
|
36
|
-
version: "1.
|
|
38
|
+
version: "1.8.0",
|
|
37
39
|
});
|
|
38
40
|
// Form CRUD
|
|
39
41
|
registerCreateFormTool(server);
|
|
42
|
+
registerListFormsTool(server);
|
|
40
43
|
registerGetFormTool(server);
|
|
41
44
|
registerUpdateFormTool(server);
|
|
42
45
|
registerDeleteFormTool(server);
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
// Node CRUD
|
|
47
|
+
registerAddNodeTool(server);
|
|
48
|
+
registerUpdateNodeTool(server);
|
|
49
|
+
registerDeleteNodeTool(server);
|
|
50
|
+
// Node media & logic
|
|
51
|
+
registerUploadNodeMediaTool(server);
|
|
52
|
+
registerGetNodeMediaTool(server);
|
|
53
|
+
registerDeleteNodeMediaTool(server);
|
|
54
|
+
registerSetNodeLogicTool(server);
|
|
55
|
+
registerAttachNodeAudioTool(server);
|
|
53
56
|
// Audit logging
|
|
54
57
|
registerLogGenerationTool(server);
|
|
55
58
|
// Content research
|
|
@@ -63,6 +66,7 @@ export function createServer() {
|
|
|
63
66
|
registerSearchMusicTool(server);
|
|
64
67
|
registerListCompositionsTool(server);
|
|
65
68
|
registerListAssetsTool(server);
|
|
69
|
+
registerCheckRenderTool(server);
|
|
66
70
|
// Prompts & Resources
|
|
67
71
|
registerPrompts(server);
|
|
68
72
|
registerResources(server);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,kBAAkB;AAClB,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NodeSchema, NODE_TYPES_DESCRIPTION } from "../lib/schemas.js";
|
|
3
|
+
import { callApi, errorResult, textResult } from "../lib/api-client.js";
|
|
4
|
+
import { fetchAndFormatFormState } from "../lib/format-form.js";
|
|
5
|
+
export function registerAddNodeTool(server) {
|
|
6
|
+
server.registerTool("clipform_add_node", {
|
|
7
|
+
title: "Add Node",
|
|
8
|
+
description: `Add a new node to an existing form. By default, the node is inserted before the end screen (appended to the end of the flow). Use after_node_id to insert at a specific position.
|
|
9
|
+
|
|
10
|
+
${NODE_TYPES_DESCRIPTION}
|
|
11
|
+
|
|
12
|
+
All type definitions and config schemas are derived from @vid-master/config (answer-types).`,
|
|
13
|
+
inputSchema: {
|
|
14
|
+
form_id: z.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
15
|
+
question: NodeSchema.describe("The node to add"),
|
|
16
|
+
after_node_id: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Insert after this node ID. Omit to append before the end screen."),
|
|
20
|
+
},
|
|
21
|
+
annotations: {
|
|
22
|
+
readOnlyHint: false,
|
|
23
|
+
destructiveHint: false,
|
|
24
|
+
idempotentHint: false,
|
|
25
|
+
openWorldHint: true,
|
|
26
|
+
},
|
|
27
|
+
}, async ({ form_id, question, after_node_id }) => {
|
|
28
|
+
const body = { question };
|
|
29
|
+
if (after_node_id)
|
|
30
|
+
body.after_node_id = after_node_id;
|
|
31
|
+
const result = await callApi(`/forms/${form_id}/nodes`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
body,
|
|
34
|
+
});
|
|
35
|
+
if (!result.ok) {
|
|
36
|
+
return errorResult(result.error);
|
|
37
|
+
}
|
|
38
|
+
const confirmMsg = [
|
|
39
|
+
`Node added successfully!`,
|
|
40
|
+
`Node ID: ${result.data.node_id}`,
|
|
41
|
+
`Type: ${question.type}`,
|
|
42
|
+
`Prompt: ${question.prompt}`,
|
|
43
|
+
].join("\n");
|
|
44
|
+
const formState = await fetchAndFormatFormState(form_id);
|
|
45
|
+
return textResult(formState ? `${confirmMsg}\n\n---\n\n${formState}` : confirmMsg);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=add-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-node.js","sourceRoot":"","sources":["../../src/tools/add-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE;;EAEjB,sBAAsB;;4FAEoE;QACtF,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,uFAAuF,CAAC;YAC5H,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YAChD,aAAa,EAAE,CAAC;iBACb,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,kEAAkE,CACnE;SACJ;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE;QAC7C,MAAM,IAAI,GAA4B,EAAE,QAAQ,EAAE,CAAC;QACnD,IAAI,aAAa;YAAE,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QAEtD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,OAAO,QAAQ,EAAE;YACtD,MAAM,EAAE,MAAM;YACd,IAAI;SACL,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,UAAU,GAAG;YACjB,0BAA0B;YAC1B,YAAY,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE;YACjC,SAAS,QAAQ,CAAC,IAAI,EAAE;YACxB,WAAW,QAAQ,CAAC,MAAM,EAAE;SAC7B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC,OAAO,CAAC,CAAC;QACzD,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,UAAU,cAAc,SAAS,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACrF,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { callApi, errorResult, textResult } from "../lib/api-client.js";
|
|
3
|
+
export function registerAttachNodeAudioTool(server) {
|
|
4
|
+
server.registerTool("clipform_attach_audio", {
|
|
5
|
+
title: "Attach Audio to Node",
|
|
6
|
+
description: `Attach a sound effect or audio file to a node with existing media (stills only). The audio auto-plays once when a respondent reaches this node. Provide a public URL to the audio file (WAV, MP3, or OGG). The node must already have media uploaded (use clipform_upload_node_media first).`,
|
|
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
|
+
node_id: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("The node ID (must already have media attached)"),
|
|
12
|
+
url: z
|
|
13
|
+
.string()
|
|
14
|
+
.url()
|
|
15
|
+
.describe("Public URL to the audio file (WAV, MP3, or OGG)"),
|
|
16
|
+
},
|
|
17
|
+
annotations: {
|
|
18
|
+
readOnlyHint: false,
|
|
19
|
+
destructiveHint: false,
|
|
20
|
+
idempotentHint: true,
|
|
21
|
+
openWorldHint: true,
|
|
22
|
+
},
|
|
23
|
+
}, async ({ form_id, node_id, url }) => {
|
|
24
|
+
const result = await callApi(`/forms/${form_id}/nodes/${node_id}/audio`, {
|
|
25
|
+
method: "PUT",
|
|
26
|
+
body: { url },
|
|
27
|
+
});
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
return errorResult(result.error);
|
|
30
|
+
}
|
|
31
|
+
return textResult(`Audio attached to node ${node_id}.\n` +
|
|
32
|
+
`Audio path: ${result.data.audio_storage_path}`);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=attach-node-audio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach-node-audio.js","sourceRoot":"","sources":["../../src/tools/attach-node-audio.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,uBAAuB,EACvB;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EAAE,8RAA8R;QAC3S,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,uFAAuF,CAAC;YAC5H,OAAO,EAAE,CAAC;iBACP,MAAM,EAAE;iBACR,QAAQ,CAAC,gDAAgD,CAAC;YAC7D,GAAG,EAAE,CAAC;iBACH,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,QAAQ,CAAC,iDAAiD,CAAC;SAC/D;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;QAClC,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,UAAU,OAAO,UAAU,OAAO,QAAQ,EAC1C;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,EAAE,GAAG,EAAE;SACd,CACF,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,UAAU,CACf,0BAA0B,OAAO,KAAK;YACpC,eAAe,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAClD,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function registerCheckRenderTool(server: McpServer): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { errorResult, textResult } from "../lib/api-client.js";
|
|
3
|
+
import { getJob, pruneJobs, RENDER_TIMING } from "../lib/render-jobs.js";
|
|
4
|
+
export function registerCheckRenderTool(server) {
|
|
5
|
+
server.registerTool("clipform_check_render", {
|
|
6
|
+
title: "Check Render Status",
|
|
7
|
+
description: `Check the status of a render job started by clipform_generate_video, clipform_generate_slideshow, or clipform_render_composition.
|
|
8
|
+
|
|
9
|
+
Returns the current status and, when complete, the output URL. If still rendering, wait ${RENDER_TIMING.pollDelay} before checking again.`,
|
|
10
|
+
inputSchema: {
|
|
11
|
+
job_id: z.string().uuid().describe("The job ID returned by the render tool"),
|
|
12
|
+
},
|
|
13
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
14
|
+
}, async ({ job_id }) => {
|
|
15
|
+
pruneJobs();
|
|
16
|
+
const job = getJob(job_id);
|
|
17
|
+
if (!job) {
|
|
18
|
+
return errorResult(`No render job found with ID ${job_id}. Jobs expire after 30 minutes.`);
|
|
19
|
+
}
|
|
20
|
+
if (job.status === "rendering") {
|
|
21
|
+
const elapsed = Math.round((Date.now() - job.createdAt) / 1000);
|
|
22
|
+
return textResult([
|
|
23
|
+
`Status: rendering (${elapsed}s elapsed)`,
|
|
24
|
+
`Tool: ${job.tool}`,
|
|
25
|
+
``,
|
|
26
|
+
`Still in progress. Check again in ${RENDER_TIMING.pollDelay}.`,
|
|
27
|
+
].join("\n"));
|
|
28
|
+
}
|
|
29
|
+
if (job.status === "failed") {
|
|
30
|
+
return errorResult(`Render failed: ${job.error}`);
|
|
31
|
+
}
|
|
32
|
+
const data = job.result;
|
|
33
|
+
return textResult([
|
|
34
|
+
`Status: complete`,
|
|
35
|
+
`Tool: ${job.tool}`,
|
|
36
|
+
``,
|
|
37
|
+
...(data.public_url ? [`Public URL: ${data.public_url}`] : []),
|
|
38
|
+
...(data.storage_path ? [`Storage path: ${data.storage_path}`] : []),
|
|
39
|
+
...(data.duration_seconds ? [`Duration: ${data.duration_seconds}s`] : []),
|
|
40
|
+
...(data.outputPath ? [`Output: ${data.outputPath}`] : []),
|
|
41
|
+
...(data.format ? [`Format: ${data.format}`] : []),
|
|
42
|
+
``,
|
|
43
|
+
`Use clipform_upload_node_media with the public URL to attach this video to a node.`,
|
|
44
|
+
].join("\n"));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=check-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-render.js","sourceRoot":"","sources":["../../src/tools/check-render.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEzE,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EAAE;;0FAEuE,aAAa,CAAC,SAAS,yBAAyB;QACpI,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;SAC7E;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;KACxG,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACnB,SAAS,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAE3B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,WAAW,CAAC,+BAA+B,MAAM,iCAAiC,CAAC,CAAC;QAC7F,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;YAChE,OAAO,UAAU,CACf;gBACE,sBAAsB,OAAO,YAAY;gBACzC,SAAS,GAAG,CAAC,IAAI,EAAE;gBACnB,EAAE;gBACF,qCAAqC,aAAa,CAAC,SAAS,GAAG;aAChE,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACJ,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,WAAW,CAAC,kBAAkB,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,MAAa,CAAC;QAC/B,OAAO,UAAU,CACf;YACE,kBAAkB;YAClB,SAAS,GAAG,CAAC,IAAI,EAAE;YACnB,EAAE;YACF,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,iBAAiB,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACzE,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,EAAE;YACF,oFAAoF;SACrF,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { BUSINESS } from "@vid-master/config";
|
|
3
|
-
import {
|
|
3
|
+
import { NodeSchema, NODE_TYPES_DESCRIPTION, } from "../lib/schemas.js";
|
|
4
4
|
import { callApi, errorResult, textResult } from "../lib/api-client.js";
|
|
5
5
|
export function registerCreateFormTool(server) {
|
|
6
6
|
server.registerTool("clipform_create_form", {
|
|
7
7
|
title: "Create Clipform",
|
|
8
|
-
description: `Create a new Clipform (interactive video-style form). Returns a viewer URL
|
|
8
|
+
description: `Create a new Clipform (interactive video-style form). Returns a viewer URL and form ID. When connected via an authenticated MCP client (e.g. claude.ai), the form lands directly in the user's workspace. Anonymous sessions get a claim URL to transfer ownership later.
|
|
9
9
|
|
|
10
|
-
${
|
|
10
|
+
${NODE_TYPES_DESCRIPTION}
|
|
11
11
|
|
|
12
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.
|
|
13
13
|
|
|
@@ -23,7 +23,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
23
23
|
inputSchema: {
|
|
24
24
|
title: z.string().describe("Form title"),
|
|
25
25
|
questions: z
|
|
26
|
-
.array(
|
|
26
|
+
.array(NodeSchema)
|
|
27
27
|
.min(1)
|
|
28
28
|
.describe("Ordered list of questions/steps"),
|
|
29
29
|
show_step_counter: z
|
|
@@ -50,6 +50,10 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
50
50
|
.boolean()
|
|
51
51
|
.optional()
|
|
52
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."),
|
|
53
57
|
},
|
|
54
58
|
annotations: {
|
|
55
59
|
readOnlyHint: false,
|
|
@@ -57,7 +61,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
57
61
|
idempotentHint: false,
|
|
58
62
|
openWorldHint: true,
|
|
59
63
|
},
|
|
60
|
-
}, 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 }) => {
|
|
61
65
|
// 0. Pre-flight: resolve the caller's plan and reject up-front if the
|
|
62
66
|
// requested question count exceeds it. Without this we'd build a
|
|
63
67
|
// scaffold + N-1 questions, then 403 on the Nth, leaving an orphan.
|
|
@@ -66,39 +70,41 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
66
70
|
// thin and never duplicates limit numbers.
|
|
67
71
|
let planContext = null;
|
|
68
72
|
const meResult = await callApi("/me", { method: "GET" });
|
|
69
|
-
if (meResult.ok) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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);
|
|
87
97
|
}
|
|
88
98
|
// 1. Create bare scaffold
|
|
89
99
|
const createResult = await callApi("/forms", {
|
|
90
100
|
method: "POST",
|
|
91
|
-
body: { title },
|
|
101
|
+
body: { title, workspace_id: workspaceId },
|
|
92
102
|
});
|
|
93
103
|
if (!createResult.ok) {
|
|
94
104
|
return errorResult(createResult.error);
|
|
95
105
|
}
|
|
96
106
|
const { data } = createResult;
|
|
97
107
|
const formId = data.form_id;
|
|
98
|
-
// edit_token is null when the request is OAuth-authenticated (the user
|
|
99
|
-
// owns the form via their workspace and no per-form secret is needed).
|
|
100
|
-
// For anonymous local sessions it carries the per-form capability token.
|
|
101
|
-
const editToken = data.edit_token ?? undefined;
|
|
102
108
|
const claimUrl = data.claim_url ?? undefined;
|
|
103
109
|
// 2. Apply form settings and theme if provided
|
|
104
110
|
const settingsBody = {};
|
|
@@ -118,7 +124,6 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
118
124
|
await callApi(`/forms/${formId}`, {
|
|
119
125
|
method: "PATCH",
|
|
120
126
|
body: settingsBody,
|
|
121
|
-
token: editToken,
|
|
122
127
|
});
|
|
123
128
|
}
|
|
124
129
|
// 3. Add each question via the add-question endpoint
|
|
@@ -128,26 +133,23 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
128
133
|
// The scaffold already has an end_screen — find it and update its prompt
|
|
129
134
|
const getResult = await callApi(`/forms/${formId}`, {
|
|
130
135
|
method: "GET",
|
|
131
|
-
token: editToken,
|
|
132
136
|
});
|
|
133
137
|
if (getResult.ok) {
|
|
134
138
|
const questions_list = getResult.data.questions;
|
|
135
139
|
const endScreen = questions_list?.find((qq) => qq.type === "end_screen");
|
|
136
140
|
if (endScreen) {
|
|
137
|
-
await callApi(`/forms/${formId}/
|
|
141
|
+
await callApi(`/forms/${formId}/nodes/${endScreen.id}`, {
|
|
138
142
|
method: "PATCH",
|
|
139
143
|
body: { prompt: q.prompt, ...(q.config ? { config: q.config } : {}) },
|
|
140
|
-
token: editToken,
|
|
141
144
|
});
|
|
142
145
|
continue;
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
// Fallback: add it normally if we couldn't find the default
|
|
146
149
|
}
|
|
147
|
-
const addResult = await callApi(`/forms/${formId}/
|
|
150
|
+
const addResult = await callApi(`/forms/${formId}/nodes`, {
|
|
148
151
|
method: "POST",
|
|
149
152
|
body: { question: q },
|
|
150
|
-
token: editToken,
|
|
151
153
|
});
|
|
152
154
|
if (!addResult.ok) {
|
|
153
155
|
return errorResult(`Failed to add question "${q.prompt}": ${addResult.error}`);
|
|
@@ -157,29 +159,27 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
157
159
|
await callApi(`/forms/${formId}`, {
|
|
158
160
|
method: "PATCH",
|
|
159
161
|
body: { is_published: true },
|
|
160
|
-
token: editToken,
|
|
161
162
|
});
|
|
163
|
+
// 5. Tag the form (best-effort)
|
|
164
|
+
if (tags && tags.length > 0) {
|
|
165
|
+
await callApi(`/forms/${formId}/tags`, {
|
|
166
|
+
method: "PUT",
|
|
167
|
+
body: { tags },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
162
170
|
const lines = [
|
|
163
171
|
`Form created successfully!`,
|
|
164
172
|
``,
|
|
165
173
|
`Title: ${title}`,
|
|
166
174
|
`Questions: ${questions.length}`,
|
|
167
175
|
`Form ID: ${formId}`,
|
|
176
|
+
``,
|
|
177
|
+
`FORM URL (share this with respondents): ${data.viewer_url}`,
|
|
168
178
|
];
|
|
169
|
-
if (editToken) {
|
|
170
|
-
lines.push(`Edit Token: ${editToken}`);
|
|
171
|
-
}
|
|
172
|
-
lines.push(``, `FORM URL (share this with respondents): ${data.viewer_url}`);
|
|
173
179
|
if (claimUrl) {
|
|
174
180
|
lines.push(`CLAIM URL (transfer ownership to your account): ${claimUrl}`);
|
|
175
181
|
}
|
|
176
|
-
lines.push(``, `IMPORTANT: Always show the user the exact URLs above — do not rewrite or modify them.`);
|
|
177
|
-
if (editToken) {
|
|
178
|
-
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.`);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
lines.push(`The form is owned by your workspace - pass form_id alone on follow-up tools.`);
|
|
182
|
-
}
|
|
182
|
+
lines.push(``, `IMPORTANT: Always show the user the exact URLs above — do not rewrite or modify them.`, `Pass form_id on follow-up tools (get_form, add_node, upload_node_media, etc.).`);
|
|
183
183
|
// One-line plan/auth summary so the model can mention it ONCE and not
|
|
184
184
|
// re-prompt on every follow-up call.
|
|
185
185
|
if (planContext) {
|