@clipform/mcp-server 1.13.0 → 1.15.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.
@@ -12,8 +12,10 @@ interface McpAuthContext {
12
12
  user_id: string;
13
13
  workspace_id: string;
14
14
  scopes: string[];
15
+ tool_name?: string;
15
16
  }
16
17
  declare function runWithMcpAuth<T>(ctx: McpAuthContext, fn: () => Promise<T> | T): Promise<T> | T;
17
18
  declare function getMcpAuth(): McpAuthContext | undefined;
19
+ declare function runWithMcpTool<T>(toolName: string, fn: () => Promise<T> | T): Promise<T> | T;
18
20
 
19
- export { type McpAuthContext, getMcpAuth, runWithMcpAuth };
21
+ export { type McpAuthContext, getMcpAuth, runWithMcpAuth, runWithMcpTool };
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  getMcpAuth,
3
- runWithMcpAuth
4
- } from "./chunk-YEDU3G7I.js";
3
+ runWithMcpAuth,
4
+ runWithMcpTool
5
+ } from "./chunk-VWGIVJMQ.js";
5
6
  export {
6
7
  getMcpAuth,
7
- runWithMcpAuth
8
+ runWithMcpAuth,
9
+ runWithMcpTool
8
10
  };
9
11
  //# sourceMappingURL=auth-context.js.map
@@ -2,8 +2,9 @@ import {
2
2
  __commonJS,
3
3
  __export,
4
4
  __toESM,
5
- getMcpAuth
6
- } from "./chunk-YEDU3G7I.js";
5
+ getMcpAuth,
6
+ runWithMcpTool
7
+ } from "./chunk-VWGIVJMQ.js";
7
8
 
8
9
  // ../../node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js
9
10
  var require_code = __commonJS({
@@ -20906,7 +20907,7 @@ var NODE_TYPES = {
20906
20907
  shorthand: "Start",
20907
20908
  icon: "ArrowRight",
20908
20909
  color: "#D1FAE5",
20909
- description: "Entry point for the form - connects to the first question",
20910
+ description: "Entry point for the form - connects to the first node",
20910
20911
  category: "start",
20911
20912
  sort_order: 0,
20912
20913
  has_options: false,
@@ -20927,11 +20928,12 @@ var NODE_TYPES = {
20927
20928
  shorthand: "Multi",
20928
20929
  icon: "CheckSquare",
20929
20930
  color: "#DBEAFE",
20930
- description: "Single or multiple choice questions with predefined options",
20931
- category: "question",
20931
+ description: "Single or multiple choice node with predefined options",
20932
+ category: "input",
20932
20933
  sort_order: 1,
20933
20934
  has_options: true,
20934
20935
  min_options: 1,
20936
+ max_options: 6,
20935
20937
  max_option_length: 36,
20936
20938
  is_terminal: false,
20937
20939
  show_nav_bar: true,
@@ -21005,7 +21007,7 @@ var NODE_TYPES = {
21005
21007
  enum: ["upload", "recorded"],
21006
21008
  type: "string",
21007
21009
  label: "Content media type",
21008
- description: "How the question media was provided"
21010
+ description: "How the node media was provided"
21009
21011
  }
21010
21012
  }
21011
21013
  },
@@ -21029,7 +21031,7 @@ var NODE_TYPES = {
21029
21031
  icon: "Type",
21030
21032
  color: "#DBEAFE",
21031
21033
  description: "Free-form text responses from users",
21032
- category: "question",
21034
+ category: "input",
21033
21035
  sort_order: 2,
21034
21036
  has_options: false,
21035
21037
  is_terminal: false,
@@ -21065,7 +21067,7 @@ var NODE_TYPES = {
21065
21067
  enum: ["upload", "recorded"],
21066
21068
  type: "string",
21067
21069
  label: "Content media type",
21068
- description: "How the question media was provided"
21070
+ description: "How the node media was provided"
21069
21071
  }
21070
21072
  }
21071
21073
  },
@@ -21129,8 +21131,8 @@ var NODE_TYPES = {
21129
21131
  shorthand: "Scale",
21130
21132
  icon: "BarChart3",
21131
21133
  color: "#DBEAFE",
21132
- description: "Numerical rating or scale questions (1-10, etc.)",
21133
- category: "question",
21134
+ description: "Numerical rating or scale node (1-10, etc.)",
21135
+ category: "input",
21134
21136
  sort_order: 3,
21135
21137
  has_options: false,
21136
21138
  is_terminal: false,
@@ -21205,7 +21207,7 @@ var NODE_TYPES = {
21205
21207
  }
21206
21208
  }
21207
21209
  ],
21208
- description: "Booking provider configuration (one provider per question)"
21210
+ description: "Booking provider configuration (one provider per node)"
21209
21211
  },
21210
21212
  response_schema: {
21211
21213
  type: "object",
@@ -21399,7 +21401,7 @@ var NODE_TYPES = {
21399
21401
  size: { type: "integer", minimum: 1, description: "File size in bytes" },
21400
21402
  mime_type: { type: "string", description: "File MIME type" },
21401
21403
  uploaded_at: { type: "string", format: "date-time", description: "ISO timestamp of upload completion" },
21402
- storage_path: { type: "string", description: "Full storage path: workspace_id/form_id/question_id/uuid.ext" }
21404
+ storage_path: { type: "string", description: "Full storage path: workspace_id/form_id/node_id/uuid.ext" }
21403
21405
  }
21404
21406
  },
21405
21407
  minItems: 1,
@@ -21496,7 +21498,7 @@ var NODE_TYPES = {
21496
21498
  icon: "CircleCheck",
21497
21499
  color: "#D1FAE5",
21498
21500
  description: "Two-option choice - yes/no, true/false, or this vs that",
21499
- category: "question",
21501
+ category: "input",
21500
21502
  sort_order: 1.5,
21501
21503
  has_options: true,
21502
21504
  min_options: 2,
@@ -21534,7 +21536,7 @@ var NODE_TYPES = {
21534
21536
  enum: ["upload", "recorded"],
21535
21537
  type: "string",
21536
21538
  label: "Content media type",
21537
- description: "How the question media was provided"
21539
+ description: "How the node media was provided"
21538
21540
  }
21539
21541
  }
21540
21542
  },
@@ -21558,7 +21560,7 @@ var NODE_TYPES = {
21558
21560
  icon: "RectangleHorizontal",
21559
21561
  color: "#DBEAFE",
21560
21562
  description: "Simple button for acknowledgment or navigation",
21561
- category: "question",
21563
+ category: "input",
21562
21564
  sort_order: 3,
21563
21565
  has_options: true,
21564
21566
  min_options: 1,
@@ -21795,7 +21797,7 @@ var NODE_TYPES = {
21795
21797
  output_schema: null
21796
21798
  },
21797
21799
  end_screen: {
21798
- label: "End Screen",
21800
+ label: "Ending",
21799
21801
  shorthand: "End",
21800
21802
  icon: "Flag",
21801
21803
  color: "#FEE2E2",
@@ -21807,7 +21809,7 @@ var NODE_TYPES = {
21807
21809
  show_nav_bar: false,
21808
21810
  loading: "eager",
21809
21811
  supports_prompt: false,
21810
- supports_media: true,
21812
+ supports_media: false,
21811
21813
  is_system: false,
21812
21814
  is_active: true,
21813
21815
  default_config: null,
@@ -22235,6 +22237,7 @@ async function callApi(path, options = {}) {
22235
22237
  headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
22236
22238
  headers["X-Mcp-User"] = mcpAuth.user_id;
22237
22239
  headers["X-Mcp-Workspace"] = mcpAuth.workspace_id;
22240
+ if (mcpAuth.tool_name) headers["X-Mcp-Tool"] = mcpAuth.tool_name;
22238
22241
  } else if (_apiKey) {
22239
22242
  headers["Authorization"] = `Bearer ${_apiKey}`;
22240
22243
  }
@@ -22277,7 +22280,13 @@ async function callInternalApi(path, options = {}) {
22277
22280
  const headers = {
22278
22281
  "Content-Type": "application/json"
22279
22282
  };
22280
- if (_apiKey) {
22283
+ const mcpAuth = getMcpAuth();
22284
+ if (mcpAuth && INTERNAL_SECRET) {
22285
+ headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
22286
+ headers["X-Mcp-User"] = mcpAuth.user_id;
22287
+ headers["X-Mcp-Workspace"] = mcpAuth.workspace_id;
22288
+ if (mcpAuth.tool_name) headers["X-Mcp-Tool"] = mcpAuth.tool_name;
22289
+ } else if (_apiKey) {
22281
22290
  headers["Authorization"] = `Bearer ${_apiKey}`;
22282
22291
  } else if (INTERNAL_SECRET) {
22283
22292
  headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
@@ -22338,7 +22347,7 @@ All type definitions and config schemas are derived from @vid-master/config (ans
22338
22347
  Example: A form that asks a question, collects contact info, then finishes:
22339
22348
  {
22340
22349
  title: "Quick Survey",
22341
- questions: [
22350
+ nodes: [
22342
22351
  { type: "open", prompt: "What's your biggest challenge?" },
22343
22352
  { type: "contact", prompt: "Leave your details", config: { fields: [{ id: "first_name", required: true }, { id: "email", required: true }] } },
22344
22353
  { type: "end_screen", prompt: "Thanks for your response!" }
@@ -22346,11 +22355,11 @@ Example: A form that asks a question, collects contact info, then finishes:
22346
22355
  }`,
22347
22356
  inputSchema: {
22348
22357
  title: external_exports.string().describe("Form title"),
22349
- questions: external_exports.array(NodeSchema).min(1).describe("Ordered list of questions/steps"),
22358
+ nodes: external_exports.array(NodeSchema).min(1).describe("Ordered list of nodes/steps for the form"),
22350
22359
  show_step_counter: external_exports.boolean().optional().describe("Show step counter (e.g. '1/5'). Set true for quizzes."),
22351
22360
  disable_back_navigation: external_exports.boolean().optional().describe("Prevent going back. Set true for quizzes."),
22352
- primary_color: external_exports.string().optional().describe("Primary/brand color (hex or CSS color). Used for buttons and accents."),
22353
- background_color: external_exports.string().optional().describe("Background color (hex, rgba, or CSS color)."),
22361
+ primary_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Primary/brand color as 6-digit hex (e.g. '#FF5500'). Used for buttons and accents."),
22362
+ background_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Background color as 6-digit hex (e.g. '#1A1A2E')."),
22354
22363
  font_family: external_exports.string().optional().describe("Font family name (e.g. 'Inter', 'Roboto', 'Playfair Display')."),
22355
22364
  embed_autoplay: external_exports.boolean().optional().describe("Auto-play video when embedded (default: false). When off, embeds show a thumbnail + play button."),
22356
22365
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for indexing (e.g. ['quiz', 'trivia', 'arsenal']). Include format, genre, and topics.")
@@ -22362,7 +22371,7 @@ Example: A form that asks a question, collects contact info, then finishes:
22362
22371
  openWorldHint: true
22363
22372
  }
22364
22373
  },
22365
- async ({ title, questions, show_step_counter, disable_back_navigation, primary_color, background_color, font_family, embed_autoplay, tags }) => {
22374
+ async ({ title, nodes, show_step_counter, disable_back_navigation, primary_color, background_color, font_family, embed_autoplay, tags }) => {
22366
22375
  let planContext = null;
22367
22376
  const meResult = await callApi("/me", { method: "GET" });
22368
22377
  if (!meResult.ok) {
@@ -22382,10 +22391,10 @@ Example: A form that asks a question, collects contact info, then finishes:
22382
22391
  plan_name: me.plan?.name ?? "Free",
22383
22392
  node_limit: me.plan?.node_limit ?? null
22384
22393
  };
22385
- const contentCount = questions.filter((q) => q.type !== "end_screen").length;
22394
+ const contentCount = nodes.filter((q) => q.type !== "end_screen").length;
22386
22395
  if (planContext.node_limit !== null && contentCount > planContext.node_limit) {
22387
22396
  const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
22388
- const message = planContext.auth_mode === "oauth" ? `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}.` : `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 \u2192 Settings \u2192 Connectors so forms land in your workspace with your real plan limits.`;
22397
+ const message = planContext.auth_mode === "oauth" ? `Your '${planContext.workspace_name}' workspace is on the ${planContext.plan_name} plan, capped at ${planContext.node_limit} nodes per form. You asked for ${contentCount}. Either rerun with ${planContext.node_limit} nodes or upgrade at ${upgradeUrl}.` : `Anonymous sessions are capped at ${planContext.node_limit} nodes per form (${planContext.plan_name} plan). You asked for ${contentCount}. Either rerun with ${planContext.node_limit} nodes, or connect your Clipform account in claude.ai \u2192 Settings \u2192 Connectors so forms land in your workspace with your real plan limits.`;
22389
22398
  return errorResult(message);
22390
22399
  }
22391
22400
  const createResult = await callApi("/forms", {
@@ -22411,29 +22420,13 @@ Example: A form that asks a question, collects contact info, then finishes:
22411
22420
  body: settingsBody
22412
22421
  });
22413
22422
  }
22414
- for (const q of questions) {
22415
- if (q.type === "end_screen") {
22416
- const getResult = await callApi(`/forms/${formId}`, {
22417
- method: "GET"
22418
- });
22419
- if (getResult.ok) {
22420
- const questions_list = getResult.data.questions;
22421
- const endScreen = questions_list?.find((qq) => qq.type === "end_screen");
22422
- if (endScreen) {
22423
- await callApi(`/forms/${formId}/nodes/${endScreen.id}`, {
22424
- method: "PATCH",
22425
- body: { prompt: q.prompt, ...q.config ? { config: q.config } : {} }
22426
- });
22427
- continue;
22428
- }
22429
- }
22430
- }
22423
+ for (const q of nodes) {
22431
22424
  const addResult = await callApi(`/forms/${formId}/nodes`, {
22432
22425
  method: "POST",
22433
- body: { question: q }
22426
+ body: { node: q }
22434
22427
  });
22435
22428
  if (!addResult.ok) {
22436
- return errorResult(`Failed to add question "${q.prompt}": ${addResult.error}`);
22429
+ return errorResult(`Failed to add node "${q.prompt}": ${addResult.error}`);
22437
22430
  }
22438
22431
  }
22439
22432
  await callApi(`/forms/${formId}`, {
@@ -22450,7 +22443,7 @@ Example: A form that asks a question, collects contact info, then finishes:
22450
22443
  `Form created successfully!`,
22451
22444
  ``,
22452
22445
  `Title: ${title}`,
22453
- `Questions: ${questions.length}`,
22446
+ `Nodes: ${nodes.length}`,
22454
22447
  `Form ID: ${formId}`,
22455
22448
  ``,
22456
22449
  `FORM URL (share this with respondents): ${data.viewer_url}`
@@ -22464,7 +22457,7 @@ Example: A form that asks a question, collects contact info, then finishes:
22464
22457
  `Pass form_id on follow-up tools (get_form, add_node, upload_node_media, etc.).`
22465
22458
  );
22466
22459
  if (planContext) {
22467
- const limitNote = planContext.node_limit === null ? "unlimited questions" : `up to ${planContext.node_limit} questions per form`;
22460
+ const limitNote = planContext.node_limit === null ? "unlimited nodes" : `up to ${planContext.node_limit} nodes per form`;
22468
22461
  if (planContext.auth_mode === "oauth") {
22469
22462
  lines.push(
22470
22463
  ``,
@@ -22549,7 +22542,7 @@ function registerListFormsTool(server) {
22549
22542
 
22550
22543
  // src/lib/format-form.ts
22551
22544
  function formatFormState(data) {
22552
- const questions = data.questions;
22545
+ const nodes = data.nodes;
22553
22546
  const lines = [
22554
22547
  `Form: ${data.title}`,
22555
22548
  `Form ID: ${data.form_id}`,
@@ -22557,8 +22550,8 @@ function formatFormState(data) {
22557
22550
  ``,
22558
22551
  `Nodes (in order):`
22559
22552
  ];
22560
- for (let i = 0; i < questions.length; i++) {
22561
- const q = questions[i];
22553
+ for (let i = 0; i < nodes.length; i++) {
22554
+ const q = nodes[i];
22562
22555
  lines.push(` ${i + 1}. [${q.type}] ${q.prompt || "(no prompt)"}`);
22563
22556
  lines.push(` Node ID: ${q.id}`);
22564
22557
  if (q.required) lines.push(` Required: yes`);
@@ -22585,7 +22578,7 @@ function registerGetFormTool(server) {
22585
22578
  "clipform_get_form",
22586
22579
  {
22587
22580
  title: "Get Clipform",
22588
- description: `Retrieve a form's details including all questions in sequential order. Use this to see the current state of a form before making changes.`,
22581
+ description: `Retrieve a form's details including all nodes in sequential order. Use this to see the current state of a form before making changes.`,
22589
22582
  inputSchema: {
22590
22583
  form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)")
22591
22584
  },
@@ -22620,8 +22613,8 @@ function registerUpdateFormTool(server) {
22620
22613
  show_step_counter: external_exports.boolean().optional().describe("Show step counter (e.g. '1/5'). Recommended for quizzes."),
22621
22614
  disable_back_navigation: external_exports.boolean().optional().describe("Prevent respondents from going back. Recommended for quizzes."),
22622
22615
  total_steps: external_exports.number().nullable().optional().describe("Override the total step count shown in the step counter. Set null to auto-calculate."),
22623
- primary_color: external_exports.string().optional().describe("Primary/brand color (hex or CSS color). Used for buttons and accents."),
22624
- background_color: external_exports.string().optional().describe("Background color (hex, rgba, or CSS color)."),
22616
+ primary_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Primary/brand color as 6-digit hex (e.g. '#FF5500'). Used for buttons and accents."),
22617
+ background_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Background color as 6-digit hex (e.g. '#1A1A2E')."),
22625
22618
  font_family: external_exports.string().optional().describe("Font family name (e.g. 'Inter', 'Roboto', 'Playfair Display')."),
22626
22619
  embed_autoplay: external_exports.boolean().optional().describe("Auto-play video when embedded (default: false). When off, embeds show a thumbnail + play button."),
22627
22620
  description: external_exports.string().nullable().optional().describe("SEO description (meta description, og:description). Set null to clear."),
@@ -22716,7 +22709,7 @@ function registerDeleteFormTool(server) {
22716
22709
  "clipform_delete_form",
22717
22710
  {
22718
22711
  title: "Delete Clipform",
22719
- description: `Permanently delete an unclaimed form and all its questions. This cannot be undone. Only works on forms that haven't been claimed yet.`,
22712
+ description: `Permanently delete an unclaimed form and all its nodes. This cannot be undone. Only works on forms that haven't been claimed yet.`,
22720
22713
  inputSchema: {
22721
22714
  form_id: external_exports.string().uuid().describe("The form UUID to delete (returned by clipform_create_form, not the short share_id from the URL)")
22722
22715
  },
@@ -22752,7 +22745,7 @@ ${NODE_TYPES_DESCRIPTION}
22752
22745
  All type definitions and config schemas are derived from @vid-master/config (answer-types).`,
22753
22746
  inputSchema: {
22754
22747
  form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
22755
- question: NodeSchema.describe("The node to add"),
22748
+ node: NodeSchema.describe("The node to add"),
22756
22749
  after_node_id: external_exports.string().optional().describe(
22757
22750
  "Insert after this node ID. Omit to append before the end screen."
22758
22751
  )
@@ -22764,8 +22757,8 @@ All type definitions and config schemas are derived from @vid-master/config (ans
22764
22757
  openWorldHint: true
22765
22758
  }
22766
22759
  },
22767
- async ({ form_id, question, after_node_id }) => {
22768
- const body = { question };
22760
+ async ({ form_id, node, after_node_id }) => {
22761
+ const body = { node };
22769
22762
  if (after_node_id) body.after_node_id = after_node_id;
22770
22763
  const result = await callApi(`/forms/${form_id}/nodes`, {
22771
22764
  method: "POST",
@@ -22777,8 +22770,8 @@ All type definitions and config schemas are derived from @vid-master/config (ans
22777
22770
  const confirmMsg = [
22778
22771
  `Node added successfully!`,
22779
22772
  `Node ID: ${result.data.node_id}`,
22780
- `Type: ${question.type}`,
22781
- `Prompt: ${question.prompt}`
22773
+ `Type: ${node.type}`,
22774
+ `Prompt: ${node.prompt}`
22782
22775
  ].join("\n");
22783
22776
  const formState = await fetchAndFormatFormState(form_id);
22784
22777
  return textResult(formState ? `${confirmMsg}
@@ -22800,13 +22793,13 @@ function registerUpdateNodeTool(server) {
22800
22793
  inputSchema: {
22801
22794
  form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
22802
22795
  node_id: external_exports.string().describe("The node ID to update"),
22803
- prompt: external_exports.string().optional().describe("New question text"),
22804
- label: external_exports.string().optional().describe("Short label for the question (used in logic builder)"),
22805
- type: external_exports.enum(ACTIVE_NODE_TYPES).optional().describe("Change the question type"),
22796
+ prompt: external_exports.string().optional().describe("New node prompt text"),
22797
+ label: external_exports.string().optional().describe("Short label for the node (used in logic builder)"),
22798
+ type: external_exports.enum(ACTIVE_NODE_TYPES).optional().describe("Change the node type"),
22806
22799
  required: external_exports.boolean().optional().describe("Whether an answer is required"),
22807
22800
  config: external_exports.record(external_exports.unknown()).optional().describe(CONFIG_DESCRIPTION),
22808
22801
  options: external_exports.array(OptionSchema).optional().describe(
22809
- "Replace all options (for choice questions). Omit to keep existing options."
22802
+ "Replace all options (for choice nodes). Omit to keep existing options."
22810
22803
  )
22811
22804
  },
22812
22805
  annotations: {
@@ -22917,7 +22910,7 @@ var MediaItemSchema = external_exports.object({
22917
22910
  ).optional().describe("Per-word timestamps within the segment")
22918
22911
  })
22919
22912
  ).optional().describe("Word-level captions from clipform_generate_tts. Always include when uploading narrated video - pass the full objects including 'words' so per-word highlighting works."),
22920
- show_captions: external_exports.boolean().optional().default(true).describe("Display captions/subtitles on the question")
22913
+ show_captions: external_exports.boolean().optional().default(true).describe("Display captions/subtitles on the node")
22921
22914
  });
22922
22915
  function registerUploadNodeMediaTool(server) {
22923
22916
  server.registerTool(
@@ -23416,8 +23409,8 @@ var v4_default = v4;
23416
23409
  var jobs = /* @__PURE__ */ new Map();
23417
23410
  var MAX_AGE_MS = 30 * 60 * 1e3;
23418
23411
  var RENDER_TIMING = {
23419
- expectedRange: "15-45 seconds",
23420
- pollDelay: "~10 seconds"
23412
+ expectedRange: "15-120 seconds",
23413
+ pollDelay: "~15 seconds"
23421
23414
  };
23422
23415
  function createJob(tool) {
23423
23416
  const job = {
@@ -23503,7 +23496,7 @@ You are the director. Every stylistic choice below is yours - defaults exist for
23503
23496
  1. Source images (use clipform_search_media with kind: "image"). Prefer portrait sources for 9:16 output; landscape works too via blur-pad fallback.
23504
23497
  2. Produce narration audio (clipform_generate_tts).
23505
23498
  3. Call this tool with images + audio_url + your creative direction.
23506
- 4. Attach the returned public URL to a question via clipform_upload_media with media_type "video".
23499
+ 4. Attach the returned public URL to a node via clipform_upload_media with media_type "video".
23507
23500
 
23508
23501
  ## Image framing (per-image)
23509
23502
 
@@ -24615,9 +24608,9 @@ Each question is a micro variable-reward event - the same dopamine loop that kee
24615
24608
 
24616
24609
  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.
24617
24610
 
24618
- ## Color Brain Questions (ColorSwatch composition)
24611
+ ## Color Brain Questions (ColorCards composition)
24619
24612
 
24620
- Inspired by the Color Brain board game - every answer is identified by its colours. Show flat colour chips, ask "what has these colours?". Use the \`ColorSwatch\` composition for the question card.
24613
+ Inspired by the Color Brain board game - every answer is identified by its colours. Show flat colour chips, ask "what has these colours?". Use the \`ColorCards\` composition for the question card.
24621
24614
 
24622
24615
  **Colour palette constraint:** Swatches are solid flat chips. Only use clearly distinguishable basic colours: red, blue, green, yellow, white, black, orange, purple, pink, brown, grey. No navy vs blue, no teal vs cyan - they look the same as flat chips. The skill is picking subjects where a combo of basic colours is unique enough to identify.
24623
24616
 
@@ -25003,6 +24996,14 @@ function createServer() {
25003
24996
  name: "clipform-mcp-server",
25004
24997
  version: MCP_VERSION
25005
24998
  });
24999
+ const _registerTool = server.registerTool.bind(server);
25000
+ server.registerTool = (name, config2, handler) => {
25001
+ return _registerTool(
25002
+ name,
25003
+ config2,
25004
+ (...args) => runWithMcpTool(name, () => handler(...args))
25005
+ );
25006
+ };
25006
25007
  registerCreateFormTool(server);
25007
25008
  registerListFormsTool(server);
25008
25009
  registerGetFormTool(server);
@@ -25039,4 +25040,4 @@ export {
25039
25040
  setApiKey,
25040
25041
  createServer
25041
25042
  };
25042
- //# sourceMappingURL=chunk-3EHPLYCP.js.map
25043
+ //# sourceMappingURL=chunk-2SPXROLZ.js.map