@clipform/mcp-server 1.13.1 → 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
  }
@@ -22282,6 +22285,7 @@ async function callInternalApi(path, options = {}) {
22282
22285
  headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
22283
22286
  headers["X-Mcp-User"] = mcpAuth.user_id;
22284
22287
  headers["X-Mcp-Workspace"] = mcpAuth.workspace_id;
22288
+ if (mcpAuth.tool_name) headers["X-Mcp-Tool"] = mcpAuth.tool_name;
22285
22289
  } else if (_apiKey) {
22286
22290
  headers["Authorization"] = `Bearer ${_apiKey}`;
22287
22291
  } else if (INTERNAL_SECRET) {
@@ -22343,7 +22347,7 @@ All type definitions and config schemas are derived from @vid-master/config (ans
22343
22347
  Example: A form that asks a question, collects contact info, then finishes:
22344
22348
  {
22345
22349
  title: "Quick Survey",
22346
- questions: [
22350
+ nodes: [
22347
22351
  { type: "open", prompt: "What's your biggest challenge?" },
22348
22352
  { type: "contact", prompt: "Leave your details", config: { fields: [{ id: "first_name", required: true }, { id: "email", required: true }] } },
22349
22353
  { type: "end_screen", prompt: "Thanks for your response!" }
@@ -22351,11 +22355,11 @@ Example: A form that asks a question, collects contact info, then finishes:
22351
22355
  }`,
22352
22356
  inputSchema: {
22353
22357
  title: external_exports.string().describe("Form title"),
22354
- 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"),
22355
22359
  show_step_counter: external_exports.boolean().optional().describe("Show step counter (e.g. '1/5'). Set true for quizzes."),
22356
22360
  disable_back_navigation: external_exports.boolean().optional().describe("Prevent going back. Set true for quizzes."),
22357
- primary_color: external_exports.string().optional().describe("Primary/brand color (hex or CSS color). Used for buttons and accents."),
22358
- 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')."),
22359
22363
  font_family: external_exports.string().optional().describe("Font family name (e.g. 'Inter', 'Roboto', 'Playfair Display')."),
22360
22364
  embed_autoplay: external_exports.boolean().optional().describe("Auto-play video when embedded (default: false). When off, embeds show a thumbnail + play button."),
22361
22365
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for indexing (e.g. ['quiz', 'trivia', 'arsenal']). Include format, genre, and topics.")
@@ -22367,7 +22371,7 @@ Example: A form that asks a question, collects contact info, then finishes:
22367
22371
  openWorldHint: true
22368
22372
  }
22369
22373
  },
22370
- 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 }) => {
22371
22375
  let planContext = null;
22372
22376
  const meResult = await callApi("/me", { method: "GET" });
22373
22377
  if (!meResult.ok) {
@@ -22387,10 +22391,10 @@ Example: A form that asks a question, collects contact info, then finishes:
22387
22391
  plan_name: me.plan?.name ?? "Free",
22388
22392
  node_limit: me.plan?.node_limit ?? null
22389
22393
  };
22390
- const contentCount = questions.filter((q) => q.type !== "end_screen").length;
22394
+ const contentCount = nodes.filter((q) => q.type !== "end_screen").length;
22391
22395
  if (planContext.node_limit !== null && contentCount > planContext.node_limit) {
22392
22396
  const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
22393
- 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.`;
22394
22398
  return errorResult(message);
22395
22399
  }
22396
22400
  const createResult = await callApi("/forms", {
@@ -22416,29 +22420,13 @@ Example: A form that asks a question, collects contact info, then finishes:
22416
22420
  body: settingsBody
22417
22421
  });
22418
22422
  }
22419
- for (const q of questions) {
22420
- if (q.type === "end_screen") {
22421
- const getResult = await callApi(`/forms/${formId}`, {
22422
- method: "GET"
22423
- });
22424
- if (getResult.ok) {
22425
- const questions_list = getResult.data.questions;
22426
- const endScreen = questions_list?.find((qq) => qq.type === "end_screen");
22427
- if (endScreen) {
22428
- await callApi(`/forms/${formId}/nodes/${endScreen.id}`, {
22429
- method: "PATCH",
22430
- body: { prompt: q.prompt, ...q.config ? { config: q.config } : {} }
22431
- });
22432
- continue;
22433
- }
22434
- }
22435
- }
22423
+ for (const q of nodes) {
22436
22424
  const addResult = await callApi(`/forms/${formId}/nodes`, {
22437
22425
  method: "POST",
22438
- body: { question: q }
22426
+ body: { node: q }
22439
22427
  });
22440
22428
  if (!addResult.ok) {
22441
- return errorResult(`Failed to add question "${q.prompt}": ${addResult.error}`);
22429
+ return errorResult(`Failed to add node "${q.prompt}": ${addResult.error}`);
22442
22430
  }
22443
22431
  }
22444
22432
  await callApi(`/forms/${formId}`, {
@@ -22455,7 +22443,7 @@ Example: A form that asks a question, collects contact info, then finishes:
22455
22443
  `Form created successfully!`,
22456
22444
  ``,
22457
22445
  `Title: ${title}`,
22458
- `Questions: ${questions.length}`,
22446
+ `Nodes: ${nodes.length}`,
22459
22447
  `Form ID: ${formId}`,
22460
22448
  ``,
22461
22449
  `FORM URL (share this with respondents): ${data.viewer_url}`
@@ -22469,7 +22457,7 @@ Example: A form that asks a question, collects contact info, then finishes:
22469
22457
  `Pass form_id on follow-up tools (get_form, add_node, upload_node_media, etc.).`
22470
22458
  );
22471
22459
  if (planContext) {
22472
- 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`;
22473
22461
  if (planContext.auth_mode === "oauth") {
22474
22462
  lines.push(
22475
22463
  ``,
@@ -22554,7 +22542,7 @@ function registerListFormsTool(server) {
22554
22542
 
22555
22543
  // src/lib/format-form.ts
22556
22544
  function formatFormState(data) {
22557
- const questions = data.questions;
22545
+ const nodes = data.nodes;
22558
22546
  const lines = [
22559
22547
  `Form: ${data.title}`,
22560
22548
  `Form ID: ${data.form_id}`,
@@ -22562,8 +22550,8 @@ function formatFormState(data) {
22562
22550
  ``,
22563
22551
  `Nodes (in order):`
22564
22552
  ];
22565
- for (let i = 0; i < questions.length; i++) {
22566
- const q = questions[i];
22553
+ for (let i = 0; i < nodes.length; i++) {
22554
+ const q = nodes[i];
22567
22555
  lines.push(` ${i + 1}. [${q.type}] ${q.prompt || "(no prompt)"}`);
22568
22556
  lines.push(` Node ID: ${q.id}`);
22569
22557
  if (q.required) lines.push(` Required: yes`);
@@ -22590,7 +22578,7 @@ function registerGetFormTool(server) {
22590
22578
  "clipform_get_form",
22591
22579
  {
22592
22580
  title: "Get Clipform",
22593
- 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.`,
22594
22582
  inputSchema: {
22595
22583
  form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)")
22596
22584
  },
@@ -22625,8 +22613,8 @@ function registerUpdateFormTool(server) {
22625
22613
  show_step_counter: external_exports.boolean().optional().describe("Show step counter (e.g. '1/5'). Recommended for quizzes."),
22626
22614
  disable_back_navigation: external_exports.boolean().optional().describe("Prevent respondents from going back. Recommended for quizzes."),
22627
22615
  total_steps: external_exports.number().nullable().optional().describe("Override the total step count shown in the step counter. Set null to auto-calculate."),
22628
- primary_color: external_exports.string().optional().describe("Primary/brand color (hex or CSS color). Used for buttons and accents."),
22629
- 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')."),
22630
22618
  font_family: external_exports.string().optional().describe("Font family name (e.g. 'Inter', 'Roboto', 'Playfair Display')."),
22631
22619
  embed_autoplay: external_exports.boolean().optional().describe("Auto-play video when embedded (default: false). When off, embeds show a thumbnail + play button."),
22632
22620
  description: external_exports.string().nullable().optional().describe("SEO description (meta description, og:description). Set null to clear."),
@@ -22721,7 +22709,7 @@ function registerDeleteFormTool(server) {
22721
22709
  "clipform_delete_form",
22722
22710
  {
22723
22711
  title: "Delete Clipform",
22724
- 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.`,
22725
22713
  inputSchema: {
22726
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)")
22727
22715
  },
@@ -22757,7 +22745,7 @@ ${NODE_TYPES_DESCRIPTION}
22757
22745
  All type definitions and config schemas are derived from @vid-master/config (answer-types).`,
22758
22746
  inputSchema: {
22759
22747
  form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
22760
- question: NodeSchema.describe("The node to add"),
22748
+ node: NodeSchema.describe("The node to add"),
22761
22749
  after_node_id: external_exports.string().optional().describe(
22762
22750
  "Insert after this node ID. Omit to append before the end screen."
22763
22751
  )
@@ -22769,8 +22757,8 @@ All type definitions and config schemas are derived from @vid-master/config (ans
22769
22757
  openWorldHint: true
22770
22758
  }
22771
22759
  },
22772
- async ({ form_id, question, after_node_id }) => {
22773
- const body = { question };
22760
+ async ({ form_id, node, after_node_id }) => {
22761
+ const body = { node };
22774
22762
  if (after_node_id) body.after_node_id = after_node_id;
22775
22763
  const result = await callApi(`/forms/${form_id}/nodes`, {
22776
22764
  method: "POST",
@@ -22782,8 +22770,8 @@ All type definitions and config schemas are derived from @vid-master/config (ans
22782
22770
  const confirmMsg = [
22783
22771
  `Node added successfully!`,
22784
22772
  `Node ID: ${result.data.node_id}`,
22785
- `Type: ${question.type}`,
22786
- `Prompt: ${question.prompt}`
22773
+ `Type: ${node.type}`,
22774
+ `Prompt: ${node.prompt}`
22787
22775
  ].join("\n");
22788
22776
  const formState = await fetchAndFormatFormState(form_id);
22789
22777
  return textResult(formState ? `${confirmMsg}
@@ -22805,13 +22793,13 @@ function registerUpdateNodeTool(server) {
22805
22793
  inputSchema: {
22806
22794
  form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
22807
22795
  node_id: external_exports.string().describe("The node ID to update"),
22808
- prompt: external_exports.string().optional().describe("New question text"),
22809
- label: external_exports.string().optional().describe("Short label for the question (used in logic builder)"),
22810
- 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"),
22811
22799
  required: external_exports.boolean().optional().describe("Whether an answer is required"),
22812
22800
  config: external_exports.record(external_exports.unknown()).optional().describe(CONFIG_DESCRIPTION),
22813
22801
  options: external_exports.array(OptionSchema).optional().describe(
22814
- "Replace all options (for choice questions). Omit to keep existing options."
22802
+ "Replace all options (for choice nodes). Omit to keep existing options."
22815
22803
  )
22816
22804
  },
22817
22805
  annotations: {
@@ -22922,7 +22910,7 @@ var MediaItemSchema = external_exports.object({
22922
22910
  ).optional().describe("Per-word timestamps within the segment")
22923
22911
  })
22924
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."),
22925
- 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")
22926
22914
  });
22927
22915
  function registerUploadNodeMediaTool(server) {
22928
22916
  server.registerTool(
@@ -23421,8 +23409,8 @@ var v4_default = v4;
23421
23409
  var jobs = /* @__PURE__ */ new Map();
23422
23410
  var MAX_AGE_MS = 30 * 60 * 1e3;
23423
23411
  var RENDER_TIMING = {
23424
- expectedRange: "15-45 seconds",
23425
- pollDelay: "~10 seconds"
23412
+ expectedRange: "15-120 seconds",
23413
+ pollDelay: "~15 seconds"
23426
23414
  };
23427
23415
  function createJob(tool) {
23428
23416
  const job = {
@@ -23508,7 +23496,7 @@ You are the director. Every stylistic choice below is yours - defaults exist for
23508
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.
23509
23497
  2. Produce narration audio (clipform_generate_tts).
23510
23498
  3. Call this tool with images + audio_url + your creative direction.
23511
- 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".
23512
23500
 
23513
23501
  ## Image framing (per-image)
23514
23502
 
@@ -24620,9 +24608,9 @@ Each question is a micro variable-reward event - the same dopamine loop that kee
24620
24608
 
24621
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.
24622
24610
 
24623
- ## Color Brain Questions (ColorSwatch composition)
24611
+ ## Color Brain Questions (ColorCards composition)
24624
24612
 
24625
- 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.
24626
24614
 
24627
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.
24628
24616
 
@@ -25008,6 +24996,14 @@ function createServer() {
25008
24996
  name: "clipform-mcp-server",
25009
24997
  version: MCP_VERSION
25010
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
+ };
25011
25007
  registerCreateFormTool(server);
25012
25008
  registerListFormsTool(server);
25013
25009
  registerGetFormTool(server);
@@ -25044,4 +25040,4 @@ export {
25044
25040
  setApiKey,
25045
25041
  createServer
25046
25042
  };
25047
- //# sourceMappingURL=chunk-NW2BYSDR.js.map
25043
+ //# sourceMappingURL=chunk-2SPXROLZ.js.map