@clipform/mcp-server 4.3.2 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,7 +60,7 @@ Your MCP client lists these automatically on connect (via `tools/list`). Full re
60
60
  | Tool | Description |
61
61
  |------|-------------|
62
62
  | `clipform_create_form` | Create a new Clipform (interactive video-style form). |
63
- | `clipform_import_form` | Convert a public Google Form, Typeform, or Tally form into a new Clipform. |
63
+ | `clipform_import_form` | Copies the questions from a public Google Form, Typeform, or Tally form into a new Clipform draft. |
64
64
  | `clipform_list_forms` | List forms in your workspace with optional filtering. |
65
65
  | `clipform_whoami` | Show the current identity: auth mode (api key, oauth, session, or anonymous), active workspace, and plan limits. |
66
66
  | `clipform_get_form` | Retrieve a form's details including all nodes in sequential order and their routing. |
@@ -6,7 +6,7 @@ import {
6
6
  recoveryHint,
7
7
  structuredResult,
8
8
  textResult
9
- } from "./chunk-NCL5EEMF.js";
9
+ } from "./chunk-K2JHO4A4.js";
10
10
  import "./chunk-BV3KA6HC.js";
11
11
  import "./chunk-PBQ5BQWD.js";
12
12
  import "./chunk-PZ5AY32C.js";
@@ -2,7 +2,7 @@ import {
2
2
  CURRENT_PLAN_VERSIONS,
3
3
  PLAN_LIMITS,
4
4
  callApi
5
- } from "./chunk-NCL5EEMF.js";
5
+ } from "./chunk-K2JHO4A4.js";
6
6
  import {
7
7
  getMcpAuth
8
8
  } from "./chunk-BV3KA6HC.js";
@@ -123,4 +123,4 @@ export {
123
123
  getSessionContextWithAuth,
124
124
  getSessionContext
125
125
  };
126
- //# sourceMappingURL=chunk-ZAPU4K7I.js.map
126
+ //# sourceMappingURL=chunk-5JFU4OEI.js.map
@@ -29,7 +29,7 @@ import {
29
29
  union,
30
30
  unknown,
31
31
  url
32
- } from "./chunk-UOLHW53C.js";
32
+ } from "./chunk-HWOV2WRK.js";
33
33
  import {
34
34
  GUIDE_TYPES,
35
35
  QUIZ_VARIANTS,
@@ -38,7 +38,7 @@ import {
38
38
  getGuideUri,
39
39
  guideFallbackText,
40
40
  registerResources
41
- } from "./chunk-VOR3EFCP.js";
41
+ } from "./chunk-F4AFULRJ.js";
42
42
  import {
43
43
  ANON_PREVIEW_LINK_LABEL,
44
44
  ANON_PREVIEW_NOTICE,
@@ -50,11 +50,12 @@ import {
50
50
  capsFromToolsets,
51
51
  isPreviewViewerUrl,
52
52
  withAdoptedNotice
53
- } from "./chunk-ZAPU4K7I.js";
53
+ } from "./chunk-5JFU4OEI.js";
54
54
  import {
55
55
  ACTIVE_NODE_TYPES,
56
56
  CONTACT_FIELDS,
57
57
  FEATURE_FLAGS,
58
+ FORM_TYPES_LIST,
58
59
  FORM_TYPE_ALIASES,
59
60
  MCP_ANON_PUBLISH_GATE,
60
61
  MCP_TOOL_SECURITY,
@@ -74,7 +75,7 @@ import {
74
75
  stripGatedConfigKeys,
75
76
  structuredResult,
76
77
  textResult
77
- } from "./chunk-NCL5EEMF.js";
78
+ } from "./chunk-K2JHO4A4.js";
78
79
  import {
79
80
  getMcpAuth,
80
81
  runWithMcpAuth,
@@ -16080,7 +16081,20 @@ import { readFileSync } from "fs";
16080
16081
  import { dirname, join } from "path";
16081
16082
  import { fileURLToPath } from "url";
16082
16083
 
16084
+ // src/lib/job-copy.ts
16085
+ var ACTIVE_JOB_LABELS = FORM_TYPES_LIST.filter(
16086
+ (t) => t.is_active
16087
+ ).map((t) => t.label.toLowerCase());
16088
+ function joinJobs(labels, conjunction = "and") {
16089
+ if (labels.length <= 2) return labels.join(` ${conjunction} `);
16090
+ return `${labels.slice(0, -1).join(", ")}, ${conjunction} ${labels[labels.length - 1]}`;
16091
+ }
16092
+ var ACTIVE_JOBS_PHRASE = joinJobs(ACTIVE_JOB_LABELS);
16093
+ var IMPORT_FORM_LEAD = "Copies the questions from a public Google Form, Typeform, or Tally form into a new Clipform draft. The source form and its responses stay where they are.";
16094
+ var INSTRUCTIONS_LEAD = `Clipform builds interactive video-style forms that other people fill in, with their answers (video, audio, or text) stored for the owner to read back: ${ACTIVE_JOBS_PHRASE}. Drafting questions as text to review or paste is a chat task, not a Clipform build.`;
16095
+
16083
16096
  // src/lib/onboarding-copy.ts
16097
+ var STARTERS = ACTIVE_JOB_LABELS.join(", ");
16084
16098
  var ONBOARDING_OFFER_CONTROL_VARIANT = "control";
16085
16099
  var ONBOARDING_OFFER_COPY = {
16086
16100
  // #4089 review: this cohort has already completed OAuth to connect - they
@@ -16089,12 +16103,12 @@ var ONBOARDING_OFFER_COPY = {
16089
16103
  // did and reads as broken. Acknowledge the connection instead, then offer
16090
16104
  // to build. (The anonymous/no-auth entry point is a different surface,
16091
16105
  // #4088 - not this cohort.)
16092
- [ONBOARDING_OFFER_CONTROL_VARIANT]: "When this is the start of a new conversation and the user has not yet said what they want to build, welcome them as someone who is now connected to Clipform and offer to build their first form together, then offer these starting points as plain text: lead-gen, feedback, quiz, or something else - ask which one fits, or invite them to describe what they need instead. If the user's first message already states what they want to build, even loosely, skip that offer entirely and go straight into the brief-driven flow below."
16106
+ [ONBOARDING_OFFER_CONTROL_VARIANT]: `When this is the start of a new conversation and the user has not yet said what they want to build, welcome them as someone who is now connected to Clipform and offer to build their first form together, then offer these starting points as plain text: ${STARTERS}, or something else - ask which one fits, or invite them to describe what they need instead. If the user's first message already states what they want to build, even loosely, skip that offer entirely and go straight into the brief-driven flow below.`
16093
16107
  };
16094
16108
  function buildOnboardingOffer(variant = ONBOARDING_OFFER_CONTROL_VARIANT) {
16095
16109
  return ONBOARDING_OFFER_COPY[variant] ?? ONBOARDING_OFFER_COPY[ONBOARDING_OFFER_CONTROL_VARIANT];
16096
16110
  }
16097
- var ONBOARDING_OFFER_TOOL_CUE = "If the user hasn't said what to build yet, offer to build their first form and suggest starting points: lead-gen, feedback, quiz, or something else.";
16111
+ var ONBOARDING_OFFER_TOOL_CUE = `If the user hasn't said what to build yet, offer to build their first form and suggest starting points: ${STARTERS}, or something else.`;
16098
16112
 
16099
16113
  // src/lib/config.ts
16100
16114
  var _endScreen = NODE_TYPES.end_screen.config_schema.properties;
@@ -16336,6 +16350,18 @@ var formIdTargetInput = external_exports.string().uuid().optional().describe(
16336
16350
  "The form this is for (from clipform_create_form) - a routing hint only, to pick the right workspace. The result still lands in the workspace library, not on a node. Required on an anonymous session, or this lands in a different workspace and clipform_attach_node_media will not find it."
16337
16351
  );
16338
16352
 
16353
+ // src/lib/links.ts
16354
+ var LINK_LABELS = {
16355
+ preview: "Preview your form",
16356
+ claim: "Sign up to keep this form (one-time link)",
16357
+ share: "Share link",
16358
+ edit: "Edit in Clipform",
16359
+ responses: "View responses"
16360
+ };
16361
+ function mdLink(label, url2) {
16362
+ return `[${label}](${url2})`;
16363
+ }
16364
+
16339
16365
  // src/tools/create-form.ts
16340
16366
  function registerCreateFormTool(server) {
16341
16367
  server.registerTool(
@@ -16513,11 +16539,11 @@ Example: A form that asks a question, collects contact info, then finishes:
16513
16539
  `Node IDs (use directly with attach_node_media / update_node - no get_form round-trip needed):`,
16514
16540
  ...createdNodes.map((n) => `- [${n.type}] "${truncate2(n.prompt)}": ${n.id}`),
16515
16541
  ``,
16516
- isPreview ? `PREVIEW URL (draft - answers are not saved, only you can open this, not for respondents): ${data.viewer_url}` : `FORM URL (live - share this with respondents): ${data.viewer_url}`
16542
+ isPreview ? `PREVIEW URL (draft - answers are not saved, only you can open this, not for respondents): ${mdLink(LINK_LABELS.preview, String(data.viewer_url))}` : `FORM URL (live - share this with respondents): ${mdLink(LINK_LABELS.share, String(data.viewer_url))}`
16517
16543
  ];
16518
16544
  if (formUrl) {
16519
16545
  lines.push(
16520
- isPreview ? `CLAIM URL (fallback - or open this one-time link to sign up in the browser): ${formUrl}` : `DASHBOARD URL (sign in to edit and manage): ${formUrl}`
16546
+ isPreview ? `CLAIM URL (fallback - or open this one-time link to sign up in the browser): ${mdLink(LINK_LABELS.claim, formUrl)}` : `DASHBOARD URL (sign in to edit and manage): ${mdLink(LINK_LABELS.edit, formUrl)}`
16521
16547
  );
16522
16548
  }
16523
16549
  if (ignoredNotes.length > 0) lines.push(``, ...ignoredNotes);
@@ -16526,7 +16552,7 @@ Example: A form that asks a question, collects contact info, then finishes:
16526
16552
  lines.push(
16527
16553
  ``,
16528
16554
  isPreview ? `This form is a preview, not live. When the user says "publish it", call clipform_update_form (is_live: true); it prompts them to sign in. The PREVIEW URL never becomes public on its own.` : `The form is live and accessible at the FORM URL above. Edits and media attached AFTER this point are NOT visible to respondents until you re-send is_live: true via clipform_update_form - republishing is the expected final step of a build, never skip it after attaching media/narration.`,
16529
- formUrl ? `The URLs above are exact values; constructed or guessed URLs return 404. Use the ${firstUrlLabel} URL and ${secondUrlLabel} URL as shown.` : `The ${firstUrlLabel} URL above is an exact value; constructed or guessed URLs return 404.`,
16555
+ formUrl ? `The URLs above are exact values; constructed or guessed URLs return 404. Use the ${firstUrlLabel} URL and ${secondUrlLabel} URL as shown, keeping each link's label rather than pasting the bare URL.` : `The ${firstUrlLabel} URL above is an exact value; constructed or guessed URLs return 404. Keep the link's label rather than pasting the bare URL.`,
16530
16556
  `Pass form_id on follow-up tools (get_form, add_node, attach_node_media, etc.).`
16531
16557
  );
16532
16558
  const anonNotice = isPreview ? anonPreviewNotice() : null;
@@ -16694,7 +16720,7 @@ function registerImportFormTool(server) {
16694
16720
  "clipform_import_form",
16695
16721
  {
16696
16722
  title: "Import Form",
16697
- description: `Convert a public Google Form, Typeform, or Tally form into a new Clipform. Supported URLs: Google Forms (docs.google.com or forms.gle - must be shared as "Anyone with the link"), Typeform (form.typeform.com/to/...), and Tally (tally.so - must be public). The source form must be publicly accessible; a private form's questions cannot be read. The new Clipform is created as a DRAFT - review it, then republish with clipform_update_form (is_live: true) once it's ready. Some question types don't map cleanly to a Clipform node and are skipped rather than guessed at; the result always reports exactly what was imported vs skipped, and why.`,
16723
+ description: `${IMPORT_FORM_LEAD} Supported URLs: Google Forms (docs.google.com or forms.gle - must be shared as "Anyone with the link"), Typeform (form.typeform.com/to/...), and Tally (tally.so - must be public). The source form must be publicly accessible; a private form's questions cannot be read. The new Clipform is created as a DRAFT - review it, then republish with clipform_update_form (is_live: true) once it's ready. Some question types don't map cleanly to a Clipform node and are skipped rather than guessed at; the result always reports exactly what was imported vs skipped, and why.`,
16698
16724
  inputSchema: external_exports.object({
16699
16725
  workspace_id: workspaceIdInput,
16700
16726
  url: external_exports.string().url().describe("Public URL of the Google Form, Typeform, or Tally form to import")
@@ -17006,12 +17032,12 @@ function formatFormState(data) {
17006
17032
  lines.push(``, `Links (exact values - constructed or guessed URLs return 404):`);
17007
17033
  if (urls.viewer) {
17008
17034
  lines.push(
17009
- isPreviewViewerUrl(urls.viewer) ? ` ${ANON_PREVIEW_LINK_LABEL}: ${urls.viewer}` : ` Viewer (respondent view): ${urls.viewer}`
17035
+ isPreviewViewerUrl(urls.viewer) ? ` ${ANON_PREVIEW_LINK_LABEL}: ${mdLink(LINK_LABELS.preview, urls.viewer)}` : ` Viewer (respondent view): ${mdLink(LINK_LABELS.share, urls.viewer)}`
17010
17036
  );
17011
17037
  }
17012
- if (urls.builder) lines.push(` Builder: ${urls.builder}`);
17038
+ if (urls.builder) lines.push(` Builder: ${mdLink(LINK_LABELS.edit, urls.builder)}`);
17013
17039
  if (urls.summary) lines.push(` Summary: ${urls.summary}`);
17014
- if (urls.responses) lines.push(` Responses: ${urls.responses}`);
17040
+ if (urls.responses) lines.push(` Responses: ${mdLink(LINK_LABELS.responses, urls.responses)}`);
17015
17041
  if (urls.analytics) lines.push(` Analytics: ${urls.analytics}`);
17016
17042
  if (urls.share) lines.push(` Share: ${urls.share}`);
17017
17043
  if (urls.embed) lines.push(` Embed: ${urls.embed}`);
@@ -17256,8 +17282,8 @@ function registerUpdateFormTool(server) {
17256
17282
  if (viewerUrl) {
17257
17283
  resultText += `
17258
17284
 
17259
- Your form is live at: ${viewerUrl}
17260
- Share this URL with respondents. View responses any time with clipform_get_results (form_id: ${form_id})${responsesUrl ? `, or in the dashboard: ${responsesUrl}` : ""}`;
17285
+ Your form is live at: ${mdLink(LINK_LABELS.share, viewerUrl)}
17286
+ Share this URL with respondents. View responses any time with clipform_get_results (form_id: ${form_id})${responsesUrl ? `, or in the dashboard: ${mdLink(LINK_LABELS.responses, responsesUrl)}` : ""}`;
17261
17287
  }
17262
17288
  }
17263
17289
  const anonNotice = anonPreviewNotice();
@@ -20083,13 +20109,14 @@ var MCP_SERVER_CARD_IDENTITY = Object.freeze({
20083
20109
  });
20084
20110
  function buildInstructions(caps) {
20085
20111
  return [
20112
+ INSTRUCTIONS_LEAD,
20086
20113
  buildOnboardingOffer(),
20087
- "Clipform builds interactive video-style forms. When the brief is clear, build the form directly; when it's vague, ask one or two short questions first.",
20114
+ "When the brief is clear, build the form directly; when it's vague, ask one or two short questions first.",
20088
20115
  caps.media && "Prefer rich media: video over images over plain text.",
20089
20116
  caps.media ? "Before building, derive a register from the topic and audience (e.g. hype, playful, warm, considered, elegant, sombre); if the user states a mood explicitly, that wins. State the register once, then apply it to every rendering choice - narration tone, motion preset, music, pacing. Never mix registers in one form." : "Before building, derive a register from the topic and audience (e.g. hype, playful, warm, considered, elegant, sombre); if the user states a mood explicitly, that wins. State the register once, then apply it to every question, option, and end-screen line. Never mix registers in one form.",
20090
20117
  "At the start of a build, call clipform_get_workflow to load the recipe for the form type, and clipform_get_guide for craft detail.",
20091
20118
  "Every *_id argument is a UUID returned by the tools - not the short share_id from a form URL.",
20092
- "For an account-scoped request, call clipform_whoami first to confirm the active workspace and plan.",
20119
+ "Use clipform_whoami when the request is about the account itself (the active workspace, plan, or limits); building a form does not need it first.",
20093
20120
  caps.media ? "Deleting a form moves it to the trash (restorable from the dashboard); deleting a node cannot be undone (a node deleted on a live form stays on the live form until you publish); removing media from a node only unlinks it, and the media asset stays in the library. Those tools ask the user to confirm before running." : "Deleting a form moves it to the trash (restorable from the dashboard); deleting a node cannot be undone (on a live form it stays on the live form until you publish). Those tools ask the user to confirm before running."
20094
20121
  ].filter((line) => typeof line === "string").join(" ");
20095
20122
  }
@@ -20263,4 +20290,4 @@ export {
20263
20290
  * MIT Licensed
20264
20291
  *)
20265
20292
  */
20266
- //# sourceMappingURL=chunk-CRLRAJOY.js.map
20293
+ //# sourceMappingURL=chunk-E2T3UTF3.js.map