@clipform/mcp-server 1.8.0 → 1.9.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 +3 -2
- package/dist/auth-context.js +9 -0
- package/dist/auth-context.js.map +1 -0
- package/dist/chunk-BWCZX7XY.js +3824 -0
- package/dist/chunk-BWCZX7XY.js.map +1 -0
- package/dist/chunk-MYWOSQ66.js +15 -0
- package/dist/chunk-MYWOSQ66.js.map +1 -0
- package/dist/index.js +15 -10
- package/dist/index.js.map +1 -1
- package/dist/server.js +7 -72
- package/dist/server.js.map +1 -1
- package/package.json +5 -4
- package/dist/__tests__/api-parity.test.d.ts +0 -1
- package/dist/__tests__/api-parity.test.js +0 -157
- package/dist/__tests__/api-parity.test.js.map +0 -1
- package/dist/__tests__/config-sync.test.d.ts +0 -1
- package/dist/__tests__/config-sync.test.js +0 -16
- package/dist/__tests__/config-sync.test.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/lib/api-client.d.ts +0 -36
- package/dist/lib/api-client.js +0 -129
- package/dist/lib/api-client.js.map +0 -1
- package/dist/lib/auth-context.d.ts +0 -17
- package/dist/lib/auth-context.js +0 -9
- package/dist/lib/auth-context.js.map +0 -1
- package/dist/lib/config.d.ts +0 -23
- package/dist/lib/config.js +0 -6
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/format-form.d.ts +0 -2
- package/dist/lib/format-form.js +0 -32
- package/dist/lib/format-form.js.map +0 -1
- package/dist/lib/render-jobs.d.ts +0 -13
- package/dist/lib/render-jobs.js +0 -38
- package/dist/lib/render-jobs.js.map +0 -1
- package/dist/lib/schemas.d.ts +0 -64
- package/dist/lib/schemas.js +0 -140
- package/dist/lib/schemas.js.map +0 -1
- package/dist/lib/session-context.d.ts +0 -1
- package/dist/lib/session-context.js +0 -38
- package/dist/lib/session-context.js.map +0 -1
- package/dist/prompts.d.ts +0 -2
- package/dist/prompts.js +0 -272
- package/dist/prompts.js.map +0 -1
- package/dist/resources.d.ts +0 -2
- package/dist/resources.js +0 -336
- package/dist/resources.js.map +0 -1
- package/dist/server.d.ts +0 -2
- package/dist/tools/add-node.d.ts +0 -2
- package/dist/tools/add-node.js +0 -50
- package/dist/tools/add-node.js.map +0 -1
- package/dist/tools/attach-node-audio.d.ts +0 -2
- package/dist/tools/attach-node-audio.js +0 -37
- package/dist/tools/attach-node-audio.js.map +0 -1
- package/dist/tools/check-render.d.ts +0 -2
- package/dist/tools/check-render.js +0 -47
- package/dist/tools/check-render.js.map +0 -1
- package/dist/tools/create-form.d.ts +0 -2
- package/dist/tools/create-form.js +0 -217
- package/dist/tools/create-form.js.map +0 -1
- package/dist/tools/delete-form.d.ts +0 -2
- package/dist/tools/delete-form.js +0 -28
- package/dist/tools/delete-form.js.map +0 -1
- package/dist/tools/delete-node-media.d.ts +0 -2
- package/dist/tools/delete-node-media.js +0 -29
- package/dist/tools/delete-node-media.js.map +0 -1
- package/dist/tools/delete-node.d.ts +0 -2
- package/dist/tools/delete-node.js +0 -32
- package/dist/tools/delete-node.js.map +0 -1
- package/dist/tools/generate-slideshow.d.ts +0 -2
- package/dist/tools/generate-slideshow.js +0 -155
- package/dist/tools/generate-slideshow.js.map +0 -1
- package/dist/tools/generate-tts.d.ts +0 -2
- package/dist/tools/generate-tts.js +0 -73
- package/dist/tools/generate-tts.js.map +0 -1
- package/dist/tools/generate-video.d.ts +0 -2
- package/dist/tools/generate-video.js +0 -119
- package/dist/tools/generate-video.js.map +0 -1
- package/dist/tools/get-form.d.ts +0 -2
- package/dist/tools/get-form.js +0 -31
- package/dist/tools/get-form.js.map +0 -1
- package/dist/tools/get-node-media.d.ts +0 -2
- package/dist/tools/get-node-media.js +0 -39
- package/dist/tools/get-node-media.js.map +0 -1
- package/dist/tools/list-assets.d.ts +0 -2
- package/dist/tools/list-assets.js +0 -45
- package/dist/tools/list-assets.js.map +0 -1
- package/dist/tools/list-compositions.d.ts +0 -2
- package/dist/tools/list-compositions.js +0 -34
- package/dist/tools/list-compositions.js.map +0 -1
- package/dist/tools/log-generation.d.ts +0 -2
- package/dist/tools/log-generation.js +0 -34
- package/dist/tools/log-generation.js.map +0 -1
- package/dist/tools/render-composition.d.ts +0 -2
- package/dist/tools/render-composition.js +0 -50
- package/dist/tools/render-composition.js.map +0 -1
- package/dist/tools/search-media.d.ts +0 -2
- package/dist/tools/search-media.js +0 -40
- package/dist/tools/search-media.js.map +0 -1
- package/dist/tools/search-music.d.ts +0 -2
- package/dist/tools/search-music.js +0 -40
- package/dist/tools/search-music.js.map +0 -1
- package/dist/tools/search-news.d.ts +0 -2
- package/dist/tools/search-news.js +0 -47
- package/dist/tools/search-news.js.map +0 -1
- package/dist/tools/set-node-logic.d.ts +0 -2
- package/dist/tools/set-node-logic.js +0 -56
- package/dist/tools/set-node-logic.js.map +0 -1
- package/dist/tools/update-form.d.ts +0 -2
- package/dist/tools/update-form.js +0 -140
- package/dist/tools/update-form.js.map +0 -1
- package/dist/tools/update-node.d.ts +0 -2
- package/dist/tools/update-node.js +0 -71
- package/dist/tools/update-node.js.map +0 -1
- package/dist/tools/upload-node-media.d.ts +0 -2
- package/dist/tools/upload-node-media.js +0 -109
- package/dist/tools/upload-node-media.js.map +0 -1
package/dist/resources.js
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import { getSessionContext } from "./lib/session-context.js";
|
|
2
|
-
const WRITING_PRINCIPLES = `## Writing Principles
|
|
3
|
-
|
|
4
|
-
- **Write for the ear.** Narration is spoken aloud. Short sentences. Natural rhythm.
|
|
5
|
-
- **Research before writing.** Find 2-3 genuinely interesting facts per topic. Generic content doesn't hold attention.
|
|
6
|
-
- **Conversational, not encyclopaedic.** "Here's what's wild about this..." not "The subject is characterized by..."
|
|
7
|
-
- **Cut ruthlessly.** Every word must earn its place.
|
|
8
|
-
- **Never reveal answers in narration.** The user picks from options - narration teases and builds intrigue.
|
|
9
|
-
- **Don't read answer options aloud.** The viewer can see them on screen.
|
|
10
|
-
|
|
11
|
-
## Narration Tips
|
|
12
|
-
|
|
13
|
-
- Tease the topic, don't summarise it
|
|
14
|
-
- Give one interesting fact that makes the user curious
|
|
15
|
-
- 5-15 seconds per question narration
|
|
16
|
-
- If TTS comes back too long, trim the copy and regenerate
|
|
17
|
-
|
|
18
|
-
## Research
|
|
19
|
-
|
|
20
|
-
Search for the specific subject, not generic terms ("komodo dragon habitat" not "reptile"). Cross-reference facts - quiz answers must be correct. Look for the surprising angle: what would make someone say "wait, really?"
|
|
21
|
-
|
|
22
|
-
For timeless topics (history, geography, science), write from your own knowledge. For anything recent or uncertain, use web search or clipform_search_news. If neither is available, refuse rather than fabricate.`;
|
|
23
|
-
const MEDIA_WORKFLOW = `## Media Workflow
|
|
24
|
-
|
|
25
|
-
1. **clipform_generate_tts** - narration audio (returns word-level captions)
|
|
26
|
-
2. **clipform_search_media** (kind: "image") - find 3 images per question
|
|
27
|
-
3. **clipform_generate_video** - Ken Burns video from images + audio
|
|
28
|
-
4. **clipform_upload_node_media** - attach video with captions (set show_captions: true)
|
|
29
|
-
|
|
30
|
-
**Image selection:**
|
|
31
|
-
- ONLY use URLs from clipform_search_media results
|
|
32
|
-
- Search for the specific subject, not generic terms
|
|
33
|
-
- Pick visually distinct images (different angles, colors, subjects)
|
|
34
|
-
- Landscape images work best for pan effects
|
|
35
|
-
|
|
36
|
-
**Slideshow defaults:**
|
|
37
|
-
- 3 images per question, random_effects: true
|
|
38
|
-
- transition: { type: "fade", duration: 1 }
|
|
39
|
-
- Auto focal point detection, eased motion, and cinematic vignette are built in`;
|
|
40
|
-
export function registerResources(server) {
|
|
41
|
-
server.registerResource("guide-quiz", "clipform://guides/quiz", {
|
|
42
|
-
description: "Craft knowledge for writing engaging quizzes - difficulty curves, question psychology, narration style, scoring",
|
|
43
|
-
mimeType: "text/markdown",
|
|
44
|
-
}, async () => ({
|
|
45
|
-
contents: [
|
|
46
|
-
{
|
|
47
|
-
uri: "clipform://guides/quiz",
|
|
48
|
-
mimeType: "text/markdown",
|
|
49
|
-
text: `# Quiz Writing Guide
|
|
50
|
-
|
|
51
|
-
## Psychology
|
|
52
|
-
|
|
53
|
-
Each question is a micro variable-reward event - the same dopamine loop that keeps people watching. Once someone answers 2-3 questions, sunk cost kicks in and they finish. Viewers mentally compete, then want to compare scores.
|
|
54
|
-
|
|
55
|
-
**Target 50-60% correct.** Too easy = no challenge. Too hard = people feel stupid and won't share.
|
|
56
|
-
|
|
57
|
-
## Difficulty Curve
|
|
58
|
-
|
|
59
|
-
| Position | Difficulty | Purpose |
|
|
60
|
-
|----------|-----------|---------|
|
|
61
|
-
| Q1-Q2 | Easy (80%+ get right) | Build confidence and commitment |
|
|
62
|
-
| Q3-Q5 | Medium | Peak engagement |
|
|
63
|
-
| Q6-Q8 | Hard (include one "gotcha") | The "everyone gets this wrong" moment |
|
|
64
|
-
| Q9-Q10 | One hard, one satisfying medium | End on a smart feeling, not defeat |
|
|
65
|
-
|
|
66
|
-
## Question Design
|
|
67
|
-
|
|
68
|
-
- **Myth-busters**: "Sushi means raw fish - True or False?" (False - it means seasoned rice)
|
|
69
|
-
- **Sounds fake but true**: counterintuitive correct answers make people rewatch
|
|
70
|
-
- **Common misconceptions**: "Capital of Australia?" (not Sydney - Canberra)
|
|
71
|
-
- Under 12-15 words per question for mobile readability
|
|
72
|
-
- Trigger gut reactions, not deep thinking
|
|
73
|
-
|
|
74
|
-
## Wrong Answer Generation
|
|
75
|
-
|
|
76
|
-
For numeric questions (population, speed, weight), scale the real answer by random multipliers (0.3x to 3x) rounded to the same magnitude. Makes wrong answers plausible but clearly different.
|
|
77
|
-
|
|
78
|
-
## Narration Style
|
|
79
|
-
|
|
80
|
-
You're a quiz master, not a question reader. Each question's narration should:
|
|
81
|
-
|
|
82
|
-
1. **Tease** - set the scene, build intrigue ("This one catches everyone out")
|
|
83
|
-
2. **Give context** - one interesting fact that makes the question richer
|
|
84
|
-
3. **Pose the question** - "So here's the question..."
|
|
85
|
-
|
|
86
|
-
**Don't say:**
|
|
87
|
-
- "You either know it or you don't" (meaningless filler)
|
|
88
|
-
- "This is a really hard one" on every question (loses impact)
|
|
89
|
-
- "Welcome to my quiz" / "Hey guys" (wastes time, skip to Q1)
|
|
90
|
-
|
|
91
|
-
${WRITING_PRINCIPLES}
|
|
92
|
-
|
|
93
|
-
${MEDIA_WORKFLOW}`,
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
}));
|
|
97
|
-
server.registerResource("guide-personality-quiz", "clipform://guides/personality-quiz", {
|
|
98
|
-
description: "Craft knowledge for building personality quizzes - category design, option weighting, outcome writing, no right/wrong answers",
|
|
99
|
-
mimeType: "text/markdown",
|
|
100
|
-
}, async () => ({
|
|
101
|
-
contents: [
|
|
102
|
-
{
|
|
103
|
-
uri: "clipform://guides/personality-quiz",
|
|
104
|
-
mimeType: "text/markdown",
|
|
105
|
-
text: `# Personality Quiz Guide
|
|
106
|
-
|
|
107
|
-
## How it works
|
|
108
|
-
|
|
109
|
-
Personality quizzes use **category-based scoring** (the \`scores\` field on options) instead of right/wrong scoring. Each option distributes points across outcome categories. The category with the highest total at the end determines the result.
|
|
110
|
-
|
|
111
|
-
## Category Design
|
|
112
|
-
|
|
113
|
-
- **3-5 categories** is the sweet spot. Fewer feels too binary, more feels random.
|
|
114
|
-
- Categories should be **distinct but equally appealing**. Nobody wants to get the "bad" result.
|
|
115
|
-
- Name them after the outcome, not the trait: "Explorer" not "Adventurous", "The Architect" not "Organised".
|
|
116
|
-
|
|
117
|
-
## Option Weighting
|
|
118
|
-
|
|
119
|
-
Each option scores into one or more categories:
|
|
120
|
-
|
|
121
|
-
\`\`\`json
|
|
122
|
-
{ "scores": { "Explorer": 3, "Homebody": 0, "Foodie": 1 } }
|
|
123
|
-
\`\`\`
|
|
124
|
-
|
|
125
|
-
Guidelines:
|
|
126
|
-
- **Primary category**: 2-3 points (this is the option's "home" category)
|
|
127
|
-
- **Secondary**: 1 point (slight lean towards another category)
|
|
128
|
-
- **Unrelated**: 0 (omit or set to 0)
|
|
129
|
-
- **Knockout**: -1 (use sparingly - strongly rules out a category)
|
|
130
|
-
- Every option should score positively in at least one category
|
|
131
|
-
- Avoid giving every option the same spread - it makes results feel random
|
|
132
|
-
|
|
133
|
-
## Question Design
|
|
134
|
-
|
|
135
|
-
- Questions should feel **personally revealing** but low-stakes: "Pick your ideal Saturday morning" not "What's your biggest weakness?"
|
|
136
|
-
- Scenario-based questions work better than abstract preference questions
|
|
137
|
-
- Each question should meaningfully differentiate between categories
|
|
138
|
-
- Avoid questions where all options clearly map to one obvious personality
|
|
139
|
-
|
|
140
|
-
## Outcome Screens
|
|
141
|
-
|
|
142
|
-
Each category needs a \`scoring_results\` entry on the end screen:
|
|
143
|
-
|
|
144
|
-
- **Title**: "You're a [Category]!" - celebratory, not clinical
|
|
145
|
-
- **Message**: 2-3 sentences that feel like a personalised insight. Reference specific traits the quiz measured.
|
|
146
|
-
- **Optional CTA**: link to relevant content, product, or next step
|
|
147
|
-
|
|
148
|
-
## Narration Style
|
|
149
|
-
|
|
150
|
-
Reflective and curious, not quizmaster-y:
|
|
151
|
-
- "This one says a lot about you..."
|
|
152
|
-
- "There's no wrong answer here - go with your gut"
|
|
153
|
-
- "What does your choice reveal?"
|
|
154
|
-
|
|
155
|
-
Do NOT say "let's see if you get this right" - there is no right answer.
|
|
156
|
-
|
|
157
|
-
${WRITING_PRINCIPLES}
|
|
158
|
-
|
|
159
|
-
${MEDIA_WORKFLOW}`,
|
|
160
|
-
},
|
|
161
|
-
],
|
|
162
|
-
}));
|
|
163
|
-
server.registerResource("guide-interview", "clipform://guides/interview", {
|
|
164
|
-
description: "Craft knowledge for building interview and testimonial collection forms - warm-up pacing, open questions, consent, video responses",
|
|
165
|
-
mimeType: "text/markdown",
|
|
166
|
-
}, async () => ({
|
|
167
|
-
contents: [
|
|
168
|
-
{
|
|
169
|
-
uri: "clipform://guides/interview",
|
|
170
|
-
mimeType: "text/markdown",
|
|
171
|
-
text: `# Interview & Testimonial Guide
|
|
172
|
-
|
|
173
|
-
## Purpose
|
|
174
|
-
|
|
175
|
-
Collect responses from people - testimonials, case studies, journalist callouts, async video interviews, candidate screening. The common thread: you're asking someone to respond on camera or in their own words.
|
|
176
|
-
|
|
177
|
-
## Structure
|
|
178
|
-
|
|
179
|
-
1. **Warm-up question** - something easy and low-stakes to get them comfortable. "Tell us your name and what you do" or "What's your role?"
|
|
180
|
-
2. **Core questions** - the real ask. Open-ended, one topic per question. Don't over-split - 2-3 core questions max.
|
|
181
|
-
3. **Follow-up** (optional) - "Anything else you'd like to add?" catches things you didn't think to ask.
|
|
182
|
-
4. **Contact collection** - first name + email minimum. Add phone/company if relevant.
|
|
183
|
-
5. **Consent** - "I agree that my response may be used in [context]." Always include for testimonials and media.
|
|
184
|
-
6. **End screen** - set expectations: "Thanks! We'll be in touch if we'd like to take things further."
|
|
185
|
-
|
|
186
|
-
## Question Design
|
|
187
|
-
|
|
188
|
-
- **Open-ended by default.** Use "open" type with text + audio + video response enabled. Let the respondent choose their format.
|
|
189
|
-
- **One topic per question.** "Tell us about your experience AND what you'd change" is two questions.
|
|
190
|
-
- **Prompt, don't interrogate.** "What surprised you most about working with us?" beats "Rate your satisfaction."
|
|
191
|
-
- **Keep it short.** 3-5 questions total. Every extra question loses respondents.
|
|
192
|
-
|
|
193
|
-
## Narration for Interviews
|
|
194
|
-
|
|
195
|
-
Warmer and more personal than quiz narration. You're inviting someone to share, not testing them.
|
|
196
|
-
|
|
197
|
-
- "We'd love to hear your story..."
|
|
198
|
-
- "Take your time with this one - there's no wrong answer"
|
|
199
|
-
- Keep narration under 10 seconds - the respondent's answer is the content, not yours
|
|
200
|
-
|
|
201
|
-
## Settings
|
|
202
|
-
|
|
203
|
-
- disable_back_navigation: false (let people review their answers)
|
|
204
|
-
- show_step_counter: true (so they know how much is left)
|
|
205
|
-
|
|
206
|
-
${WRITING_PRINCIPLES}
|
|
207
|
-
|
|
208
|
-
${MEDIA_WORKFLOW}`,
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
}));
|
|
212
|
-
server.registerResource("guide-survey", "clipform://guides/survey", {
|
|
213
|
-
description: "Craft knowledge for feedback surveys, NPS, and research forms - brevity, rating scales, respondent fatigue",
|
|
214
|
-
mimeType: "text/markdown",
|
|
215
|
-
}, async () => ({
|
|
216
|
-
contents: [
|
|
217
|
-
{
|
|
218
|
-
uri: "clipform://guides/survey",
|
|
219
|
-
mimeType: "text/markdown",
|
|
220
|
-
text: `# Survey & Feedback Guide
|
|
221
|
-
|
|
222
|
-
## Purpose
|
|
223
|
-
|
|
224
|
-
Collect structured feedback - NPS, customer satisfaction, product research, post-event feedback. The goal is clean, analysable data with minimal respondent fatigue.
|
|
225
|
-
|
|
226
|
-
## Structure
|
|
227
|
-
|
|
228
|
-
Surveys should be ruthlessly short. Every extra question costs you completions.
|
|
229
|
-
|
|
230
|
-
1. **Key metric** - the one number you care about (NPS, satisfaction rating, likelihood to recommend). Put it first while attention is highest.
|
|
231
|
-
2. **Follow-up** - one open-ended "why?" question. "What's the main reason for your score?" This is where the insight lives.
|
|
232
|
-
3. **Specific questions** (optional) - 2-3 targeted choice questions on specific areas. Don't fish - only ask what you'll act on.
|
|
233
|
-
4. **Contact** (optional) - only if you need to follow up. Many surveys are better anonymous.
|
|
234
|
-
5. **End screen** - "Thanks for your feedback!" Keep it simple.
|
|
235
|
-
|
|
236
|
-
## Question Design
|
|
237
|
-
|
|
238
|
-
- **Choice questions for data, open questions for insight.** Don't use open-ended where a rating scale would do, and don't use ratings where you need to understand why.
|
|
239
|
-
- **Balanced scales.** Equal positive and negative options. "Excellent / Good / Fair / Poor" not "Amazing / Great / Good / OK / Bad."
|
|
240
|
-
- **No leading questions.** "How much did you enjoy...?" assumes they enjoyed it.
|
|
241
|
-
- **5 questions max.** If you need more, you're running research, not a survey - split it up.
|
|
242
|
-
|
|
243
|
-
## Narration for Surveys
|
|
244
|
-
|
|
245
|
-
Optional - many surveys work fine as text only. If using narration:
|
|
246
|
-
|
|
247
|
-
- Keep it brief (5-8 seconds). "We'd love your quick feedback on..."
|
|
248
|
-
- Don't narrate every question - just the opener to set the tone
|
|
249
|
-
- Friendly but efficient. Respect their time.
|
|
250
|
-
|
|
251
|
-
## Settings
|
|
252
|
-
|
|
253
|
-
- disable_back_navigation: false
|
|
254
|
-
- show_step_counter: true (shows progress, reduces abandonment)
|
|
255
|
-
|
|
256
|
-
${WRITING_PRINCIPLES}`,
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
|
-
}));
|
|
260
|
-
server.registerResource("guide-funnel", "clipform://guides/funnel", {
|
|
261
|
-
description: "Craft knowledge for lead qualification funnels and product recommendation quizzes - branching logic, progressive profiling, conversion",
|
|
262
|
-
mimeType: "text/markdown",
|
|
263
|
-
}, async () => ({
|
|
264
|
-
contents: [
|
|
265
|
-
{
|
|
266
|
-
uri: "clipform://guides/funnel",
|
|
267
|
-
mimeType: "text/markdown",
|
|
268
|
-
text: `# Lead Qualification & Product Recommendation Guide
|
|
269
|
-
|
|
270
|
-
## Purpose
|
|
271
|
-
|
|
272
|
-
Route people to the right outcome based on their answers. Lead qualification scores and segments prospects. Product recommendation guides users to the right product. Both use branching logic to personalise the journey.
|
|
273
|
-
|
|
274
|
-
## Structure
|
|
275
|
-
|
|
276
|
-
1. **Hook question** - immediately relevant, low friction. "What are you looking for?" or "What best describes you?" This segments the user and determines the branch.
|
|
277
|
-
2. **Qualifying questions** - 2-3 questions that narrow down the need. Each answer can branch to a different path.
|
|
278
|
-
3. **Contact capture** - name, email, phone. Place AFTER qualifying questions so they've invested before you ask for details.
|
|
279
|
-
4. **Outcome screen** - personalised end screen based on their answers. Use score_ranges or branching logic to show different messages.
|
|
280
|
-
|
|
281
|
-
## Question Design
|
|
282
|
-
|
|
283
|
-
- **Progressive profiling.** Start broad, get specific. Don't ask for budget on question 1.
|
|
284
|
-
- **Every question must earn its place.** If the answer doesn't change the outcome or routing, cut the question.
|
|
285
|
-
- **Choice questions, not open-ended.** You need structured data to route and score. Save open-ended for "anything else?"
|
|
286
|
-
- **3-5 questions max.** Every extra step loses leads. The funnel is leaky by nature - keep it tight.
|
|
287
|
-
|
|
288
|
-
## Branching Logic
|
|
289
|
-
|
|
290
|
-
Use clipform_set_logic to route based on answers:
|
|
291
|
-
|
|
292
|
-
- **Segment early.** Q1 answer determines which Q2 they see.
|
|
293
|
-
- **Converge at capture.** All branches should reach the contact collection step.
|
|
294
|
-
- **Different outcomes for different segments.** The end screen should feel personalised: "Based on your answers, we recommend..." not a generic "Thanks!"
|
|
295
|
-
|
|
296
|
-
## Scoring for Product Recommendation
|
|
297
|
-
|
|
298
|
-
- Assign scores to each option based on which product/outcome it points to
|
|
299
|
-
- Use score_ranges on the end screen to show different recommendations
|
|
300
|
-
- Example: score 0-3 = "Basic plan", 4-6 = "Pro plan", 7+ = "Enterprise - let's talk"
|
|
301
|
-
|
|
302
|
-
## Narration for Funnels
|
|
303
|
-
|
|
304
|
-
Short and action-oriented. You're guiding, not teaching.
|
|
305
|
-
|
|
306
|
-
- "Let's find the right fit for you..."
|
|
307
|
-
- "Just a couple of quick questions..."
|
|
308
|
-
- Keep total narration under 30 seconds across the whole funnel
|
|
309
|
-
|
|
310
|
-
## Settings
|
|
311
|
-
|
|
312
|
-
- disable_back_navigation: true (prevent answer shopping that breaks scoring)
|
|
313
|
-
- show_step_counter: false (funnels feel shorter without a counter)
|
|
314
|
-
|
|
315
|
-
${WRITING_PRINCIPLES}`,
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
}));
|
|
319
|
-
server.registerResource("context-session", "clipform://context/session", {
|
|
320
|
-
description: "Current session info: auth mode, workspace, plan tier, node limits, feature flags. Read this before planning content to know your constraints.",
|
|
321
|
-
mimeType: "text/markdown",
|
|
322
|
-
annotations: { audience: ["assistant"], priority: 1.0 },
|
|
323
|
-
}, async () => {
|
|
324
|
-
const text = await getSessionContext();
|
|
325
|
-
return {
|
|
326
|
-
contents: [
|
|
327
|
-
{
|
|
328
|
-
uri: "clipform://context/session",
|
|
329
|
-
mimeType: "text/markdown",
|
|
330
|
-
text: text || "Session context unavailable - API may not be reachable.",
|
|
331
|
-
},
|
|
332
|
-
],
|
|
333
|
-
};
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
//# sourceMappingURL=resources.js.map
|
package/dist/resources.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
DELETED
package/dist/tools/add-node.d.ts
DELETED
package/dist/tools/add-node.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
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
|
-
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."),
|
|
16
|
-
question: NodeSchema.describe("The node to add"),
|
|
17
|
-
after_node_id: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe("Insert after this node ID. Omit to append before the end screen."),
|
|
21
|
-
},
|
|
22
|
-
annotations: {
|
|
23
|
-
readOnlyHint: false,
|
|
24
|
-
destructiveHint: false,
|
|
25
|
-
idempotentHint: false,
|
|
26
|
-
openWorldHint: true,
|
|
27
|
-
},
|
|
28
|
-
}, async ({ form_id, edit_token, question, after_node_id }) => {
|
|
29
|
-
const body = { question };
|
|
30
|
-
if (after_node_id)
|
|
31
|
-
body.after_node_id = after_node_id;
|
|
32
|
-
const result = await callApi(`/forms/${form_id}/nodes`, {
|
|
33
|
-
method: "POST",
|
|
34
|
-
body,
|
|
35
|
-
token: edit_token,
|
|
36
|
-
});
|
|
37
|
-
if (!result.ok) {
|
|
38
|
-
return errorResult(result.error);
|
|
39
|
-
}
|
|
40
|
-
const confirmMsg = [
|
|
41
|
-
`Node added successfully!`,
|
|
42
|
-
`Node ID: ${result.data.node_id}`,
|
|
43
|
-
`Type: ${question.type}`,
|
|
44
|
-
`Prompt: ${question.prompt}`,
|
|
45
|
-
].join("\n");
|
|
46
|
-
const formState = await fetchAndFormatFormState(form_id, edit_token);
|
|
47
|
-
return textResult(formState ? `${confirmMsg}\n\n---\n\n${formState}` : confirmMsg);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=add-node.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kIAAkI,CAAC;YAC9K,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,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE;QACzD,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;YACJ,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,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,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,37 +0,0 @@
|
|
|
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
|
-
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
|
|
11
|
-
.string()
|
|
12
|
-
.describe("The node ID (must already have media attached)"),
|
|
13
|
-
url: z
|
|
14
|
-
.string()
|
|
15
|
-
.url()
|
|
16
|
-
.describe("Public URL to the audio file (WAV, MP3, or OGG)"),
|
|
17
|
-
},
|
|
18
|
-
annotations: {
|
|
19
|
-
readOnlyHint: false,
|
|
20
|
-
destructiveHint: false,
|
|
21
|
-
idempotentHint: true,
|
|
22
|
-
openWorldHint: true,
|
|
23
|
-
},
|
|
24
|
-
}, async ({ form_id, edit_token, node_id, url }) => {
|
|
25
|
-
const result = await callApi(`/forms/${form_id}/nodes/${node_id}/audio`, {
|
|
26
|
-
method: "PUT",
|
|
27
|
-
token: edit_token,
|
|
28
|
-
body: { url },
|
|
29
|
-
});
|
|
30
|
-
if (!result.ok) {
|
|
31
|
-
return errorResult(result.error);
|
|
32
|
-
}
|
|
33
|
-
return textResult(`Audio attached to node ${node_id}.\n` +
|
|
34
|
-
`Audio path: ${result.data.audio_storage_path}`);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=attach-node-audio.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kIAAkI,CAAC;YAC9K,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,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;QAC9C,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,UAAU,OAAO,UAAU,OAAO,QAAQ,EAC1C;YACE,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,UAAU;YACjB,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,47 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { errorResult, textResult } from "../lib/api-client.js";
|
|
3
|
-
import { getJob, pruneJobs } 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 ~30 seconds 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 ~30 seconds.`,
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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,MAAM,uBAAuB,CAAC;AAE1D,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EAAE;;2HAEwG;QACrH,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,gDAAgD;aACjD,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"}
|