@clipform/mcp-server 3.0.0 → 3.3.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.
@@ -6,7 +6,7 @@ import {
6
6
  getWorkflowText,
7
7
  objectType,
8
8
  registerPrompts
9
- } from "./chunk-KCF7ZVDI.js";
9
+ } from "./chunk-22BV4NSB.js";
10
10
  import {
11
11
  GUIDE_TYPES,
12
12
  QUIZ_VARIANTS,
@@ -15,31 +15,36 @@ import {
15
15
  getGuideUri,
16
16
  guideFallbackText,
17
17
  registerResources
18
- } from "./chunk-EXV5M7YV.js";
18
+ } from "./chunk-ELK26KWM.js";
19
19
  import {
20
20
  ACTIVE_NODE_TYPES,
21
21
  CONTACT_FIELDS,
22
22
  FEATURE_FLAGS,
23
23
  FORM_TYPE_ALIASES,
24
24
  FULL_CAPS,
25
+ MCP_ANON_PUBLISH_GATE,
26
+ MCP_TOOL_SECURITY,
25
27
  MCP_TOOL_TIERS,
26
28
  MCP_TOOL_TOOLSETS,
27
29
  NODE_TYPES,
28
30
  NON_COUNTABLE_TYPES,
31
+ authChallengeMeta,
29
32
  bfsOrder,
30
33
  callApi,
31
34
  capsFromToolsets,
32
35
  defaultTarget,
33
36
  errorResult,
34
37
  exposedCompositionIds,
38
+ isAnonymousAccountRequired,
35
39
  resolveFormType,
36
40
  structuredResult,
37
41
  textResult
38
- } from "./chunk-KNOCYZHG.js";
42
+ } from "./chunk-SEZ37YSH.js";
39
43
  import {
40
44
  getMcpAuth,
45
+ runWithMcpAuth,
41
46
  runWithMcpTool
42
- } from "./chunk-HZMMOSEM.js";
47
+ } from "./chunk-A5CFGL27.js";
43
48
  import {
44
49
  __commonJS,
45
50
  __export,
@@ -114,7 +119,7 @@ var require_code = __commonJS({
114
119
  }
115
120
  exports._ = _;
116
121
  var plus = new _Code("+");
117
- function str(strs, ...args) {
122
+ function str2(strs, ...args) {
118
123
  const expr = [safeStringify(strs[0])];
119
124
  let i = 0;
120
125
  while (i < args.length) {
@@ -125,7 +130,7 @@ var require_code = __commonJS({
125
130
  optimize(expr);
126
131
  return new _Code(expr);
127
132
  }
128
- exports.str = str;
133
+ exports.str = str2;
129
134
  function addCodeArg(code, arg) {
130
135
  if (arg instanceof _Code)
131
136
  code.push(...arg._items);
@@ -168,7 +173,7 @@ var require_code = __commonJS({
168
173
  return;
169
174
  }
170
175
  function strConcat(c1, c2) {
171
- return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`;
176
+ return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str2`${c1}${c2}`;
172
177
  }
173
178
  exports.strConcat = strConcat;
174
179
  function interpolate(x) {
@@ -1130,22 +1135,22 @@ var require_util = __commonJS({
1130
1135
  return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
1131
1136
  }
1132
1137
  exports.schemaRefOrVal = schemaRefOrVal;
1133
- function unescapeFragment(str) {
1134
- return unescapeJsonPointer(decodeURIComponent(str));
1138
+ function unescapeFragment(str2) {
1139
+ return unescapeJsonPointer(decodeURIComponent(str2));
1135
1140
  }
1136
1141
  exports.unescapeFragment = unescapeFragment;
1137
- function escapeFragment(str) {
1138
- return encodeURIComponent(escapeJsonPointer(str));
1142
+ function escapeFragment(str2) {
1143
+ return encodeURIComponent(escapeJsonPointer(str2));
1139
1144
  }
1140
1145
  exports.escapeFragment = escapeFragment;
1141
- function escapeJsonPointer(str) {
1142
- if (typeof str == "number")
1143
- return `${str}`;
1144
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
1146
+ function escapeJsonPointer(str2) {
1147
+ if (typeof str2 == "number")
1148
+ return `${str2}`;
1149
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
1145
1150
  }
1146
1151
  exports.escapeJsonPointer = escapeJsonPointer;
1147
- function unescapeJsonPointer(str) {
1148
- return str.replace(/~1/g, "/").replace(/~0/g, "~");
1152
+ function unescapeJsonPointer(str2) {
1153
+ return str2.replace(/~1/g, "/").replace(/~0/g, "~");
1149
1154
  }
1150
1155
  exports.unescapeJsonPointer = unescapeJsonPointer;
1151
1156
  function eachItem(xs, f) {
@@ -2170,8 +2175,8 @@ var require_json_schema_traverse = __commonJS({
2170
2175
  post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
2171
2176
  }
2172
2177
  }
2173
- function escapeJsonPtr(str) {
2174
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
2178
+ function escapeJsonPtr(str2) {
2179
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
2175
2180
  }
2176
2181
  }
2177
2182
  });
@@ -3235,10 +3240,10 @@ var require_utils = __commonJS({
3235
3240
  return { host, isIPV6: false };
3236
3241
  }
3237
3242
  }
3238
- function findToken(str, token) {
3243
+ function findToken(str2, token) {
3239
3244
  let ind = 0;
3240
- for (let i = 0; i < str.length; i++) {
3241
- if (str[i] === token) ind++;
3245
+ for (let i = 0; i < str2.length; i++) {
3246
+ if (str2[i] === token) ind++;
3242
3247
  }
3243
3248
  return ind;
3244
3249
  }
@@ -3975,7 +3980,7 @@ var require_core = __commonJS({
3975
3980
  var util_1 = require_util();
3976
3981
  var $dataRefSchema = require_data();
3977
3982
  var uri_1 = require_uri();
3978
- var defaultRegExp = (str, flags) => new RegExp(str, flags);
3983
+ var defaultRegExp = (str2, flags) => new RegExp(str2, flags);
3979
3984
  defaultRegExp.code = "new RegExp";
3980
3985
  var META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"];
3981
3986
  var EXT_SCOPE_NAMES = /* @__PURE__ */ new Set([
@@ -4770,16 +4775,16 @@ var require_ucs2length = __commonJS({
4770
4775
  "../../node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/runtime/ucs2length.js"(exports) {
4771
4776
  "use strict";
4772
4777
  Object.defineProperty(exports, "__esModule", { value: true });
4773
- function ucs2length(str) {
4774
- const len = str.length;
4778
+ function ucs2length(str2) {
4779
+ const len = str2.length;
4775
4780
  let length = 0;
4776
4781
  let pos = 0;
4777
4782
  let value;
4778
4783
  while (pos < len) {
4779
4784
  length++;
4780
- value = str.charCodeAt(pos++);
4785
+ value = str2.charCodeAt(pos++);
4781
4786
  if (value >= 55296 && value <= 56319 && pos < len) {
4782
- value = str.charCodeAt(pos);
4787
+ value = str2.charCodeAt(pos);
4783
4788
  if ((value & 64512) === 56320)
4784
4789
  pos++;
4785
4790
  }
@@ -6662,8 +6667,8 @@ var require_formats = __commonJS({
6662
6667
  }
6663
6668
  var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
6664
6669
  var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
6665
- function date3(str) {
6666
- const matches = DATE.exec(str);
6670
+ function date3(str2) {
6671
+ const matches = DATE.exec(str2);
6667
6672
  if (!matches)
6668
6673
  return false;
6669
6674
  const year = +matches[1];
@@ -6682,8 +6687,8 @@ var require_formats = __commonJS({
6682
6687
  }
6683
6688
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
6684
6689
  function getTime(strictTimeZone) {
6685
- return function time3(str) {
6686
- const matches = TIME.exec(str);
6690
+ return function time3(str2) {
6691
+ const matches = TIME.exec(str2);
6687
6692
  if (!matches)
6688
6693
  return false;
6689
6694
  const hr = +matches[1];
@@ -6729,8 +6734,8 @@ var require_formats = __commonJS({
6729
6734
  var DATE_TIME_SEPARATOR = /t|\s/i;
6730
6735
  function getDateTime(strictTimeZone) {
6731
6736
  const time3 = getTime(strictTimeZone);
6732
- return function date_time(str) {
6733
- const dateTime = str.split(DATE_TIME_SEPARATOR);
6737
+ return function date_time(str2) {
6738
+ const dateTime = str2.split(DATE_TIME_SEPARATOR);
6734
6739
  return dateTime.length === 2 && date3(dateTime[0]) && time3(dateTime[1]);
6735
6740
  };
6736
6741
  }
@@ -6755,13 +6760,13 @@ var require_formats = __commonJS({
6755
6760
  }
6756
6761
  var NOT_URI_FRAGMENT = /\/|:/;
6757
6762
  var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
6758
- function uri(str) {
6759
- return NOT_URI_FRAGMENT.test(str) && URI.test(str);
6763
+ function uri(str2) {
6764
+ return NOT_URI_FRAGMENT.test(str2) && URI.test(str2);
6760
6765
  }
6761
6766
  var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
6762
- function byte(str) {
6767
+ function byte(str2) {
6763
6768
  BYTE.lastIndex = 0;
6764
- return BYTE.test(str);
6769
+ return BYTE.test(str2);
6765
6770
  }
6766
6771
  var MIN_INT32 = -(2 ** 31);
6767
6772
  var MAX_INT32 = 2 ** 31 - 1;
@@ -6775,11 +6780,11 @@ var require_formats = __commonJS({
6775
6780
  return true;
6776
6781
  }
6777
6782
  var Z_ANCHOR = /[^\\]\\Z/;
6778
- function regex(str) {
6779
- if (Z_ANCHOR.test(str))
6783
+ function regex(str2) {
6784
+ if (Z_ANCHOR.test(str2))
6780
6785
  return false;
6781
6786
  try {
6782
- new RegExp(str);
6787
+ new RegExp(str2);
6783
6788
  return true;
6784
6789
  } catch (e) {
6785
6790
  return false;
@@ -7115,14 +7120,14 @@ function promiseAllObject(promisesObj) {
7115
7120
  }
7116
7121
  function randomString(length = 10) {
7117
7122
  const chars = "abcdefghijklmnopqrstuvwxyz";
7118
- let str = "";
7123
+ let str2 = "";
7119
7124
  for (let i = 0; i < length; i++) {
7120
- str += chars[Math.floor(Math.random() * chars.length)];
7125
+ str2 += chars[Math.floor(Math.random() * chars.length)];
7121
7126
  }
7122
- return str;
7127
+ return str2;
7123
7128
  }
7124
- function esc(str) {
7125
- return JSON.stringify(str);
7129
+ function esc(str2) {
7130
+ return JSON.stringify(str2);
7126
7131
  }
7127
7132
  var captureStackTrace = Error.captureStackTrace ? Error.captureStackTrace : (..._args) => {
7128
7133
  };
@@ -7210,8 +7215,8 @@ var getParsedType = (data) => {
7210
7215
  };
7211
7216
  var propertyKeyTypes = /* @__PURE__ */ new Set(["string", "number", "symbol"]);
7212
7217
  var primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "bigint", "boolean", "symbol", "undefined"]);
7213
- function escapeRegex(str) {
7214
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7218
+ function escapeRegex(str2) {
7219
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7215
7220
  }
7216
7221
  function clone(inst, def, params) {
7217
7222
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -17187,6 +17192,48 @@ function applyNodeDefaults(node) {
17187
17192
  }
17188
17193
  }
17189
17194
 
17195
+ // src/lib/workspace-target.ts
17196
+ var PINNED_WORKSPACE_ERROR = "This connection is pinned to one workspace, so a workspace argument can't be used. Omit it to act in the connection's workspace.";
17197
+ async function runWithWorkspaceTarget(args, run) {
17198
+ const input = args && typeof args === "object" ? args : {};
17199
+ const workspaceId = typeof input.workspace_id === "string" && input.workspace_id ? input.workspace_id : void 0;
17200
+ const formId = typeof input.form_id === "string" && input.form_id ? input.form_id : void 0;
17201
+ if (!workspaceId && !formId) return run();
17202
+ const ctx = getMcpAuth();
17203
+ const resolve = ctx?.resolveWorkspace;
17204
+ if (!resolve) {
17205
+ if (workspaceId) return errorResult(PINNED_WORKSPACE_ERROR);
17206
+ return run();
17207
+ }
17208
+ if (workspaceId && !formId && workspaceId === ctx.default_workspace_id) {
17209
+ return run();
17210
+ }
17211
+ let resolved;
17212
+ try {
17213
+ resolved = await resolve({
17214
+ ...workspaceId ? { workspace_id: workspaceId } : {},
17215
+ ...formId ? { form_id: formId } : {}
17216
+ });
17217
+ } catch (err) {
17218
+ return errorResult(err instanceof Error ? err.message : "Could not resolve that workspace.");
17219
+ }
17220
+ if (!resolved) return run();
17221
+ return runWithMcpAuth(
17222
+ {
17223
+ ...ctx,
17224
+ workspace_id: resolved.workspace_id,
17225
+ api_key: resolved.api_key
17226
+ },
17227
+ run
17228
+ );
17229
+ }
17230
+ var workspaceIdInput = external_exports.string().uuid().optional().describe(
17231
+ "Workspace to act in (id from clipform_whoami workspaces). Omit to use the connection's default workspace."
17232
+ );
17233
+ var formIdTargetInput = external_exports.string().uuid().optional().describe(
17234
+ "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."
17235
+ );
17236
+
17190
17237
  // src/tools/create-form.ts
17191
17238
  function registerCreateFormTool(server) {
17192
17239
  server.registerTool(
@@ -17211,6 +17258,7 @@ Example: A form that asks a question, collects contact info, then finishes:
17211
17258
  ]
17212
17259
  }`,
17213
17260
  inputSchema: {
17261
+ workspace_id: workspaceIdInput,
17214
17262
  title: external_exports.string().describe("Form title"),
17215
17263
  nodes: external_exports.array(NodeSchema).min(1).describe("Ordered list of nodes/steps for the form"),
17216
17264
  show_step_counter: external_exports.boolean().optional().describe("Show step counter (e.g. '1/5'). Set true for quizzes."),
@@ -17229,7 +17277,8 @@ Example: A form that asks a question, collects contact info, then finishes:
17229
17277
  viewer_url: external_exports.string().describe("Form URL for respondents - a live public URL once authenticated/published, or a scoped preview-only link for an anonymous session"),
17230
17278
  dashboard_url: external_exports.string().optional().describe("Builder URL (sign in to edit)"),
17231
17279
  nodes: external_exports.array(external_exports.object({ id: external_exports.string(), type: external_exports.string(), prompt: external_exports.string() })).describe("Created nodes in order - IDs for attach_node_media / update_node"),
17232
- plan: external_exports.object({ name: external_exports.string(), auth_mode: external_exports.string() }).optional().describe("Plan and auth context for this session")
17280
+ plan: external_exports.object({ name: external_exports.string(), auth_mode: external_exports.string() }).optional().describe("Plan and auth context for this session"),
17281
+ workspace_name: external_exports.string().optional().describe("Name of the workspace the form was created in")
17233
17282
  },
17234
17283
  annotations: {
17235
17284
  readOnlyHint: false,
@@ -17318,7 +17367,8 @@ Example: A form that asks a question, collects contact info, then finishes:
17318
17367
  timeoutMs: 12e4
17319
17368
  });
17320
17369
  if (!createResult.ok) {
17321
- return errorResult(`Form NOT created (nothing was saved): ${createResult.error}`);
17370
+ const message = `Form NOT created (nothing was saved): ${createResult.error}`;
17371
+ return isAnonymousAccountRequired(createResult) ? errorResult(message, authChallengeMeta("invalid_token", message)) : errorResult(message);
17322
17372
  }
17323
17373
  const { data } = createResult;
17324
17374
  const formId = data.form_id;
@@ -17342,6 +17392,7 @@ Example: A form that asks a question, collects contact info, then finishes:
17342
17392
  isPreview ? `Form created as a preview draft.` : `Form created and live.`,
17343
17393
  ``,
17344
17394
  `Title: ${title}`,
17395
+ ...planContext?.is_authenticated && planContext.workspace_name ? [`Workspace: ${planContext.workspace_name}`] : [],
17345
17396
  `Form ID: ${formId}`,
17346
17397
  ``,
17347
17398
  `Node IDs (use directly with attach_node_media / update_node - no get_form round-trip needed):`,
@@ -17389,15 +17440,111 @@ Example: A form that asks a question, collects contact info, then finishes:
17389
17440
  // runtime. structuredContent should return only relevant data (ChatGPT
17390
17441
  // App Directory response-hygiene policy, #797).
17391
17442
  nodes: createdNodes.map((n) => ({ id: n.id, type: n.type, prompt: n.prompt })),
17392
- ...planContext ? { plan: { name: planContext.plan_name, auth_mode: planContext.auth_mode } } : {}
17443
+ ...planContext ? { plan: { name: planContext.plan_name, auth_mode: planContext.auth_mode } } : {},
17444
+ ...planContext?.is_authenticated && planContext.workspace_name ? { workspace_name: planContext.workspace_name } : {}
17393
17445
  }
17394
17446
  };
17395
17447
  }
17396
17448
  );
17397
17449
  }
17398
17450
 
17399
- // src/tools/import-form.ts
17400
- function detectImportPlatform(url) {
17451
+ // ../form-sources/dist/types.js
17452
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
17453
+ var str = (v) => typeof v === "string" ? v : "";
17454
+ var DEFAULT_QUESTION_CAP = 50;
17455
+ var MAX_OPTIONS = 10;
17456
+
17457
+ // ../form-sources/dist/patterns.js
17458
+ var firstMatch = (url, patterns) => {
17459
+ for (const re of patterns) {
17460
+ const id = re.exec(url)?.[1];
17461
+ if (id)
17462
+ return id;
17463
+ }
17464
+ return null;
17465
+ };
17466
+ var VIDEOASK_PATTERNS = [
17467
+ /^https?:\/\/api\.videoask\.com\/forms\/sharing\/(f[a-z0-9]{8})(?:[/?#]|$)/i,
17468
+ /^https?:\/\/(?:www\.)?videoask\.com\/(f[a-z0-9]{8})(?:[/?#]|$)/i
17469
+ ];
17470
+ var detectVideoAskId = (url) => firstMatch(url, VIDEOASK_PATTERNS);
17471
+ var TYPEFORM_ATTR_KINDS = {
17472
+ "data-tf-widget": "inline",
17473
+ "data-tf-popup": "popup",
17474
+ "data-tf-slider": "slider",
17475
+ "data-tf-sidetab": "slider",
17476
+ "data-tf-popover": "popover"
17477
+ };
17478
+ var TALLY_ATTR_KINDS = {
17479
+ "data-tally-src": "inline",
17480
+ "data-tally-open": "popup"
17481
+ };
17482
+ var EMBED_ATTR_NAMES = [...Object.keys(TYPEFORM_ATTR_KINDS), ...Object.keys(TALLY_ATTR_KINDS)];
17483
+ function pushUnique(out, ref) {
17484
+ if (!out.some((r) => r.id === ref.id))
17485
+ out.push(ref);
17486
+ }
17487
+ function detectVideoAskScript(text) {
17488
+ const out = [];
17489
+ if (!/VIDEOASK_EMBED_CONFIG/.test(text))
17490
+ return out;
17491
+ for (const m of text.matchAll(/videoask\.com\/(f[a-z0-9]{8})(?![a-z0-9])/gi))
17492
+ pushUnique(out, { id: m[1].toLowerCase() });
17493
+ return out;
17494
+ }
17495
+
17496
+ // ../form-sources/dist/providers/videoask.js
17497
+ function optionText(o) {
17498
+ if (typeof o === "string")
17499
+ return o;
17500
+ if (isObj(o))
17501
+ return str(o.label) || str(o.text) || str(o.value) || str(o.title);
17502
+ return "";
17503
+ }
17504
+ var videoask = {
17505
+ id: "videoask",
17506
+ detect: detectVideoAskId,
17507
+ detectScript: detectVideoAskScript,
17508
+ fetchSpec: (id) => ({ url: `https://api.videoask.com/forms/sharing/${id}`, responseType: "json" }),
17509
+ /**
17510
+ * `GET api.videoask.com/forms/sharing/<id>`. Personal/account fields
17511
+ * (`author_id`, `organization_id`, ...) are dropped by construction: only the
17512
+ * fields below are copied.
17513
+ */
17514
+ summarize(id, json, opts) {
17515
+ if (!isObj(json) || !Array.isArray(json.questions))
17516
+ throw new Error("not a VideoAsk form payload");
17517
+ const transcriptCap = opts?.transcriptCap;
17518
+ const questions = json.questions.slice(0, opts?.questionCap ?? DEFAULT_QUESTION_CAP).map((q) => {
17519
+ const o = isObj(q) ? q : {};
17520
+ const transcript = str(o.transcription);
17521
+ return {
17522
+ label: str(o.label),
17523
+ type: str(o.type),
17524
+ options: (Array.isArray(o.options) ? o.options : []).map(optionText).filter(Boolean).slice(0, MAX_OPTIONS),
17525
+ transcript: transcriptCap === void 0 ? transcript : transcript.slice(0, transcriptCap)
17526
+ };
17527
+ });
17528
+ return {
17529
+ provider: "videoask",
17530
+ id,
17531
+ title: str(json.title),
17532
+ questions,
17533
+ meta: {
17534
+ ...str(json.created_at) ? { created_at: str(json.created_at) } : {},
17535
+ ...str(json.updated_at) ? { last_edited_at: str(json.updated_at) } : {},
17536
+ // hide_branding is a paid-plan feature.
17537
+ paid_plan: json.hide_branding === true,
17538
+ asks_contact: ["requires_contact_email", "requires_contact_info", "requires_contact_name", "requires_contact_phone_number"].some((k) => json[k] === true),
17539
+ ...str(json.status) ? { status: str(json.status) } : {}
17540
+ }
17541
+ };
17542
+ }
17543
+ };
17544
+
17545
+ // ../form-sources/dist/index.js
17546
+ var IMPORTABLE_PROVIDERS = ["google", "typeform", "tally"];
17547
+ function detectProvider(url) {
17401
17548
  let parsed;
17402
17549
  try {
17403
17550
  parsed = new URL(url);
@@ -17405,17 +17552,22 @@ function detectImportPlatform(url) {
17405
17552
  return null;
17406
17553
  }
17407
17554
  const hostname2 = parsed.hostname.toLowerCase();
17408
- if (hostname2 === "docs.google.com" || hostname2 === "forms.gle") {
17555
+ if (hostname2 === "docs.google.com" || hostname2 === "forms.gle")
17409
17556
  return "google";
17410
- }
17411
- if (hostname2.includes("typeform.com") && parsed.pathname.includes("/to/")) {
17557
+ if (hostname2.includes("typeform.com") && parsed.pathname.includes("/to/"))
17412
17558
  return "typeform";
17413
- }
17414
- if (hostname2.includes("tally.so")) {
17559
+ if (hostname2.includes("tally.so"))
17415
17560
  return "tally";
17416
- }
17561
+ if (videoask.detect(url))
17562
+ return "videoask";
17417
17563
  return null;
17418
17564
  }
17565
+
17566
+ // src/tools/import-form.ts
17567
+ function detectImportPlatform(url) {
17568
+ const provider = detectProvider(url);
17569
+ return provider && IMPORTABLE_PROVIDERS.includes(provider) ? provider : null;
17570
+ }
17419
17571
  function pluralQuestions(n) {
17420
17572
  return `${n} question${n === 1 ? "" : "s"}`;
17421
17573
  }
@@ -17426,6 +17578,7 @@ function registerImportFormTool(server) {
17426
17578
  title: "Import Form",
17427
17579
  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.`,
17428
17580
  inputSchema: {
17581
+ workspace_id: workspaceIdInput,
17429
17582
  url: external_exports.string().url().describe("Public URL of the Google Form, Typeform, or Tally form to import")
17430
17583
  },
17431
17584
  outputSchema: {
@@ -17438,7 +17591,8 @@ function registerImportFormTool(server) {
17438
17591
  skipped: external_exports.number().describe("Questions that could not be converted"),
17439
17592
  skipped_details: external_exports.array(external_exports.record(external_exports.string(), external_exports.string())).describe("Per-skipped-question title, reason, and source question type"),
17440
17593
  warnings: external_exports.array(external_exports.string()).describe("Non-fatal issues surfaced during the import")
17441
- })
17594
+ }),
17595
+ workspace_name: external_exports.string().optional().describe("Name of the workspace the form was created in")
17442
17596
  },
17443
17597
  annotations: {
17444
17598
  readOnlyHint: false,
@@ -17462,6 +17616,7 @@ function registerImportFormTool(server) {
17462
17616
  }
17463
17617
  const me = meResult.data;
17464
17618
  const workspaceId = me.workspace?.id ?? null;
17619
+ const workspaceName = me.auth_mode && me.auth_mode !== "anonymous" ? me.workspace?.name ?? void 0 : void 0;
17465
17620
  if (!workspaceId) {
17466
17621
  return errorResult(
17467
17622
  "No workspace available. This usually means authentication is misconfigured - check your API key or OAuth connection."
@@ -17504,7 +17659,9 @@ function registerImportFormTool(server) {
17504
17659
  `Pass form_id (the UUID above, not a share_id) to follow-up tools (get_form, add_node, update_node, etc.).`
17505
17660
  );
17506
17661
  }
17662
+ if (formId && workspaceName) lines.push(``, `Created in workspace '${workspaceName}'.`);
17507
17663
  return structuredResult(lines.join("\n"), {
17664
+ ...workspaceName ? { workspace_name: workspaceName } : {},
17508
17665
  form_id: formId,
17509
17666
  viewer_url: viewerUrl,
17510
17667
  import_summary: {
@@ -17528,6 +17685,7 @@ function registerListFormsTool(server) {
17528
17685
  title: "List Clipforms",
17529
17686
  description: `List forms in your workspace with optional filtering. Returns paginated results (cursor-based). next_cursor is included when more results exist. To find or open a form by name, pass search with part of its title instead of paging through results.`,
17530
17687
  inputSchema: {
17688
+ workspace_id: workspaceIdInput,
17531
17689
  limit: external_exports.number().int().min(1).max(100).optional().describe("Number of forms to return (default 25, max 100)"),
17532
17690
  cursor: external_exports.string().optional().describe(
17533
17691
  "Pagination cursor from previous response's next_cursor"
@@ -17811,6 +17969,11 @@ function registerGetFormTool(server) {
17811
17969
  }
17812
17970
 
17813
17971
  // src/tools/update-form.ts
17972
+ if (MCP_ANON_PUBLISH_GATE.tool !== "clipform_update_form") {
17973
+ throw new Error(
17974
+ `update-form.ts registers "clipform_update_form" but MCP_ANON_PUBLISH_GATE.tool is "${MCP_ANON_PUBLISH_GATE.tool}" - update both sides together (packages/config/index.js and this file's registerTool call) or the Claude lazy-auth HTTP gate silently stops matching this tool.`
17975
+ );
17976
+ }
17814
17977
  function registerUpdateFormTool(server) {
17815
17978
  server.registerTool(
17816
17979
  "clipform_update_form",
@@ -17871,7 +18034,7 @@ function registerUpdateFormTool(server) {
17871
18034
  body
17872
18035
  });
17873
18036
  if (!result.ok) {
17874
- return errorResult(result.error);
18037
+ return isAnonymousAccountRequired(result) ? errorResult(result.error, authChallengeMeta("invalid_token", result.error)) : errorResult(result.error);
17875
18038
  }
17876
18039
  }
17877
18040
  if (tags) {
@@ -17880,7 +18043,7 @@ function registerUpdateFormTool(server) {
17880
18043
  body: { tags }
17881
18044
  });
17882
18045
  if (!tagResult.ok) {
17883
- return errorResult(tagResult.error);
18046
+ return isAnonymousAccountRequired(tagResult) ? errorResult(tagResult.error, authChallengeMeta("invalid_token", tagResult.error)) : errorResult(tagResult.error);
17884
18047
  }
17885
18048
  }
17886
18049
  const updates = [];
@@ -18347,7 +18510,8 @@ function registerWhoamiTool(server) {
18347
18510
  node_limit: external_exports.number().nullable().describe("Max nodes per form, or null if unlimited"),
18348
18511
  form_limit: external_exports.number().nullable().describe("Max forms in the workspace, or null if unlimited")
18349
18512
  }).describe("Plan limits for the active workspace"),
18350
- workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string() })).optional().describe("All workspaces in the caller's company - only present for session (dashboard) auth"),
18513
+ workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string() })).optional().describe("Workspaces the caller can target with workspace_id - present for session (dashboard) auth and the OAuth connector"),
18514
+ default_workspace_id: external_exports.string().optional().describe("OAuth connector only: the workspace used when a tool call omits workspace_id"),
18351
18515
  links: external_exports.object({
18352
18516
  dashboard: external_exports.string().describe("Dashboard home for the active workspace."),
18353
18517
  forms: external_exports.string().describe("Forms list for the active workspace.")
@@ -18385,11 +18549,26 @@ function registerWhoamiTool(server) {
18385
18549
  if (Array.isArray(me.workspaces)) {
18386
18550
  structured.workspaces = me.workspaces.map((w) => ({ id: w.id, name: w.name }));
18387
18551
  }
18552
+ const ctx = getMcpAuth();
18553
+ if (!structured.workspaces && ctx?.listWorkspaces) {
18554
+ try {
18555
+ const list = await ctx.listWorkspaces();
18556
+ structured.workspaces = list.map((w) => ({ id: w.id, name: w.name }));
18557
+ if (ctx.default_workspace_id) structured.default_workspace_id = ctx.default_workspace_id;
18558
+ } catch {
18559
+ }
18560
+ }
18388
18561
  if (me.links && typeof me.links.dashboard === "string" && typeof me.links.forms === "string") {
18389
18562
  structured.links = { dashboard: me.links.dashboard, forms: me.links.forms };
18390
18563
  }
18391
18564
  const workspaceLine = workspace ? `workspace '${workspace.name}'` : "no workspace (anonymous)";
18392
18565
  const lines = [`Auth: ${authMode}. ${workspaceLine}. Plan: ${plan.name}.`];
18566
+ if (Array.isArray(structured.workspaces) && ctx?.listWorkspaces) {
18567
+ const list = structured.workspaces;
18568
+ lines.push(
18569
+ `Workspaces you can act in (pass one of these ids as the workspace argument to target it; omit it to use the default): ` + list.map((w) => `${w.name} (${w.id})`).join(", ")
18570
+ );
18571
+ }
18393
18572
  if (structured.links) {
18394
18573
  const links = structured.links;
18395
18574
  lines.push(`Dashboard: ${links.dashboard}`, `Forms list: ${links.forms}`);
@@ -18578,7 +18757,8 @@ function registerDeleteNodeTool(server) {
18578
18757
 
18579
18758
  // src/tools/upload-media-asset.ts
18580
18759
  var MediaAssetItemSchema = external_exports.object({
18581
- media_type: external_exports.enum(["video", "still"]).describe("Type of media"),
18760
+ media_type: external_exports.enum(["video", "still", "document"]).describe("Type of media. Use document for a consent-agreement PDF (url required, PDF only)."),
18761
+ filename: external_exports.string().max(200).optional().describe("Display filename for a document (media_type document). Defaults to the URL's file name."),
18582
18762
  media_source: external_exports.enum(["uploaded", "recorded"]).default("uploaded").describe("How the media was created"),
18583
18763
  url: external_exports.string().url().optional().describe("Public URL that links directly to the media file itself. When provided, the server fetches and stores it directly."),
18584
18764
  captions: external_exports.array(
@@ -18624,9 +18804,16 @@ function mapFileEntryToItem(file) {
18624
18804
  if (category === "video") {
18625
18805
  return { ok: true, item: { media_type: "video", media_source: "uploaded", url: file.download_url } };
18626
18806
  }
18807
+ if (mimeType === "application/pdf") {
18808
+ const filename = file.name ?? file.file_name;
18809
+ return {
18810
+ ok: true,
18811
+ item: { media_type: "document", media_source: "uploaded", url: file.download_url, ...filename ? { filename } : {} }
18812
+ };
18813
+ }
18627
18814
  return {
18628
18815
  ok: false,
18629
- error: `Attached file type "${mimeType || "unknown"}" is not supported. Attach an image or video file, or provide a direct URL instead.`
18816
+ error: `Attached file type "${mimeType || "unknown"}" is not supported. Attach an image, video or PDF file, or provide a direct URL instead.`
18630
18817
  };
18631
18818
  }
18632
18819
  function registerUploadMediaAssetTool(server) {
@@ -18636,38 +18823,55 @@ function registerUploadMediaAssetTool(server) {
18636
18823
  title: "Upload Media Asset",
18637
18824
  description: `Put one or more media files into your workspace media library (max 10, uploaded sequentially). This is step one of attaching media to a node - follow up with clipform_attach_node_media to place the returned media_asset_id on a node.
18638
18825
 
18639
- When a public URL is provided, the media is fetched and stored automatically. For video: ingested via Mux. For image: stored in Supabase. Word-level captions from clipform_generate_tts enable per-word highlighting in the viewer once attached - pass the caption_ref it returned instead of hand-copying the captions array.
18826
+ When a public URL is provided, the media is fetched and stored automatically. The API picks where each asset is stored based on its size and type. Word-level captions from clipform_generate_tts enable per-word highlighting in the viewer once attached - pass the caption_ref it returned instead of hand-copying the captions array.
18827
+
18828
+ PDFs (media_type document) are for consent agreements: they return a document descriptor, not a media_asset_id.
18640
18829
 
18641
18830
  Provide media via \`items\` (explicit type + URL), \`files\` (files attached by the host client), or both. At least one is required.`,
18642
18831
  inputSchema: {
18832
+ workspace_id: workspaceIdInput,
18833
+ form_id: formIdTargetInput,
18643
18834
  items: external_exports.array(MediaAssetItemSchema).min(1).max(10).optional().describe("One or more media items to create as workspace library assets"),
18644
- files: external_exports.array(FileEntrySchema).min(1).max(10).optional().describe("Files attached by the host client. Each becomes a video or still library asset based on its declared mime_type.")
18835
+ files: external_exports.array(FileEntrySchema).min(1).max(10).optional().describe("Files attached by the host client. Each becomes a video or still library asset (or a consent-agreement document for application/pdf) based on its declared mime_type.")
18645
18836
  },
18646
18837
  outputSchema: {
18647
18838
  results: external_exports.array(
18648
18839
  external_exports.object({
18649
- media_asset_id: external_exports.string().optional(),
18840
+ media_asset_id: external_exports.string().optional().describe("Absent for documents"),
18650
18841
  media_type: external_exports.string(),
18842
+ document: external_exports.object({
18843
+ storage_path: external_exports.string(),
18844
+ filename: external_exports.string(),
18845
+ url: external_exports.string(),
18846
+ version: external_exports.string()
18847
+ }).optional().describe("Consent-agreement descriptor (documents only) - set as consent_items[i].document via clipform_update_node"),
18651
18848
  stored: external_exports.boolean().describe("Bytes already stored (URL-based); false when an upload_url is pending"),
18652
18849
  upload_url: external_exports.string().optional(),
18653
18850
  upload_method: external_exports.string().optional(),
18654
18851
  ok: external_exports.boolean(),
18655
18852
  error: external_exports.string().optional()
18656
18853
  })
18657
- ).describe("One result per item, in order")
18854
+ ).describe("One result per item, in order"),
18855
+ workspace_name: external_exports.string().optional().describe("Name of the workspace the media library entries were created in")
18658
18856
  },
18659
18857
  annotations: {
18660
18858
  readOnlyHint: false,
18661
18859
  destructiveHint: false,
18662
18860
  idempotentHint: false,
18663
- openWorldHint: false
18861
+ // Open-world: fetches an arbitrary caller-supplied URL (and may hand
18862
+ // larger video to a third-party streaming provider) (#4434).
18863
+ openWorldHint: true
18664
18864
  },
18665
18865
  // Apps SDK convention (openai-apps-sdk-examples#185): a host client only
18666
18866
  // hydrates a file attachment into a TOP-LEVEL tool argument named here -
18667
18867
  // same mechanism create-form.ts uses for anthropic/alwaysLoad.
18668
18868
  _meta: { "openai/fileParams": ["files"] }
18669
18869
  },
18670
- async ({ items, files }) => {
18870
+ // `form_id` is consumed centrally by `runWithWorkspaceTarget` (server.ts)
18871
+ // BEFORE this handler runs - it's a routing hint only (#4621), never
18872
+ // forwarded to the API: this route takes no form_id and /me below still
18873
+ // resolves the (now-targeted) workspace.
18874
+ async ({ items, files, form_id: _form_id }) => {
18671
18875
  if ((!items || items.length === 0) && (!files || files.length === 0)) {
18672
18876
  return errorResult("Provide at least one media item via `items` or `files`.");
18673
18877
  }
@@ -18681,6 +18885,7 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
18681
18885
  }
18682
18886
  const me = meResult.data;
18683
18887
  const workspaceId = me.workspace?.id ?? null;
18888
+ const workspaceName = me.auth_mode && me.auth_mode !== "anonymous" ? me.workspace?.name ?? void 0 : void 0;
18684
18889
  if (!workspaceId) {
18685
18890
  return errorResult(
18686
18891
  "No workspace available. This usually means authentication is misconfigured - check your API key or OAuth connection."
@@ -18712,6 +18917,7 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
18712
18917
  media_source: item.media_source
18713
18918
  };
18714
18919
  if (item.url) createBody.url = item.url;
18920
+ if (item.filename) createBody.filename = item.filename;
18715
18921
  if (item.captions) createBody.captions = item.captions;
18716
18922
  if (item.caption_ref) createBody.caption_ref = item.caption_ref;
18717
18923
  if (item.ai_generated !== void 0) createBody.ai_generated = item.ai_generated;
@@ -18722,6 +18928,33 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
18722
18928
  results.push({ media_type: item.media_type, stored: false, ok: false, error: created.error });
18723
18929
  continue;
18724
18930
  }
18931
+ if (item.media_type === "document") {
18932
+ const d = created.data;
18933
+ if (typeof d.storage_path !== "string" || typeof d.url !== "string" || typeof d.version !== "string" || !d.version) {
18934
+ const error2 = "document upload did not return a document descriptor.";
18935
+ lines.push(`FAILED: ${error2}`);
18936
+ lines.push("");
18937
+ results.push({ media_type: "document", stored: false, ok: false, error: error2 });
18938
+ continue;
18939
+ }
18940
+ const document = {
18941
+ storage_path: d.storage_path,
18942
+ filename: String(d.filename ?? item.filename ?? "agreement.pdf"),
18943
+ url: d.url,
18944
+ version: d.version
18945
+ };
18946
+ successCount++;
18947
+ results.push({ media_type: "document", stored: true, ok: true, document });
18948
+ lines.push(
18949
+ [
18950
+ `Document stored: ${document.filename}`,
18951
+ `Descriptor: ${JSON.stringify(document)}`,
18952
+ `Set this on a Details node consent item via clipform_update_node: consent_items[i].document = <descriptor>, require_scroll_to_accept: true. Do not use clipform_attach_node_media.`
18953
+ ].join("\n")
18954
+ );
18955
+ lines.push("");
18956
+ continue;
18957
+ }
18725
18958
  const mediaAssetId = created.data.media_asset_id;
18726
18959
  if (!mediaAssetId) {
18727
18960
  const error2 = `upload did not return a media asset id (route ${String(created.data.route ?? "unknown")}).`;
@@ -18763,9 +18996,12 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
18763
18996
  lines.unshift(`Media asset upload: ${successCount}/${entries.length} succeeded
18764
18997
  `);
18765
18998
  }
18766
- lines.push(`Attach these to nodes with clipform_attach_node_media.`);
18999
+ if (results.some((r) => r.ok && r.media_asset_id)) {
19000
+ lines.push(`Attach these to nodes with clipform_attach_node_media.`);
19001
+ }
18767
19002
  if (successCount === 0) return errorResult(lines.join("\n"));
18768
- return structuredResult(lines.join("\n"), { results });
19003
+ if (workspaceName) lines.push(`Workspace: ${workspaceName}`);
19004
+ return structuredResult(lines.join("\n"), { results, ...workspaceName ? { workspace_name: workspaceName } : {} });
18769
19005
  }
18770
19006
  );
18771
19007
  }
@@ -18776,9 +19012,10 @@ function registerCompleteMediaUploadTool(server) {
18776
19012
  "clipform_complete_media_upload",
18777
19013
  {
18778
19014
  title: "Complete Media Upload",
18779
- description: `Confirm a signed-PUT still image upload finished, after PUTting the bytes to the upload_url returned by clipform_upload_media_asset. The API verifies the object actually landed in storage before flipping the asset from processing to ready - call this right after the PUT succeeds, or the asset stays invisible in the library. Not needed for video uploads (TUS/Mux settle automatically).`,
19015
+ description: `Confirm a signed-PUT still image upload finished, after PUTting the bytes to the upload_url returned by clipform_upload_media_asset. The API verifies the object actually landed in storage before flipping the asset from processing to ready - call this right after the PUT succeeds, or the asset stays invisible in the library. Not needed for video uploads (they settle automatically).`,
18780
19016
  inputSchema: {
18781
- media_asset_id: external_exports.string().uuid().describe("The media_asset_id returned by clipform_upload_media_asset for the still image")
19017
+ media_asset_id: external_exports.string().uuid().describe("The media_asset_id returned by clipform_upload_media_asset for the still image"),
19018
+ form_id: formIdTargetInput
18782
19019
  },
18783
19020
  outputSchema: {
18784
19021
  media_asset_id: external_exports.string(),
@@ -18791,7 +19028,10 @@ function registerCompleteMediaUploadTool(server) {
18791
19028
  openWorldHint: false
18792
19029
  }
18793
19030
  },
18794
- async ({ media_asset_id }) => {
19031
+ // `form_id` (#4621) is a routing hint only, consumed centrally by
19032
+ // `runWithWorkspaceTarget` (server.ts) before this handler runs - never
19033
+ // forwarded to the API below (this route takes no form_id).
19034
+ async ({ media_asset_id, form_id: _form_id }) => {
18795
19035
  const meResult = await callApi("/me", { method: "GET" });
18796
19036
  if (!meResult.ok) {
18797
19037
  return errorResult(`Unable to determine workspace: ${meResult.error}`);
@@ -18970,7 +19210,7 @@ function registerDeleteNodeMediaTool(server) {
18970
19210
  "clipform_delete_node_media",
18971
19211
  {
18972
19212
  title: "Delete Node Media",
18973
- description: `Remove media from a node. Deletes the media record and cleans up external resources (Mux video asset, storage file). Requires user confirmation before it runs.`,
19213
+ description: `Remove media from a node. Deletes the media record and the stored media asset (including any copy held by a third-party video provider). Requires user confirmation before it runs.`,
18974
19214
  inputSchema: {
18975
19215
  form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
18976
19216
  node_id: external_exports.string().describe("The node ID"),
@@ -18982,7 +19222,9 @@ function registerDeleteNodeMediaTool(server) {
18982
19222
  // Deleting already-removed media has no further effect - textbook
18983
19223
  // idempotent delete (#2976).
18984
19224
  idempotentHint: true,
18985
- openWorldHint: false
19225
+ // Open-world: also deletes any copy held by a third-party video
19226
+ // provider (#4434).
19227
+ openWorldHint: true
18986
19228
  }
18987
19229
  },
18988
19230
  async ({ form_id, node_id, confirm }) => {
@@ -19124,10 +19366,10 @@ function registerSearchNewsTool(server) {
19124
19366
  server.registerTool(
19125
19367
  "clipform_search_news",
19126
19368
  {
19127
- title: "Search News (fallback)",
19128
- description: `Fallback news lookup for clients without native web search. Returns structured current-news articles from NewsAPI and The Guardian.
19369
+ title: "Search Recent News",
19370
+ description: `Search recent news articles from NewsAPI and The Guardian and return them as structured results.
19129
19371
 
19130
- Coverage: recent events, people, and topics (post-May-2025). Does NOT cover timeless topics (history, geography, science). Narrower and less current than native web search tools (WebSearch, web fetch) when available.
19372
+ Coverage: recent events, people, and topics (post-May-2025). Does NOT cover timeless topics (history, geography, science).
19131
19373
 
19132
19374
  Returns: article title, source, author, date, URL, description, and image URL per result.`,
19133
19375
  inputSchema: {
@@ -19212,6 +19454,8 @@ Use the tone parameter to direct HOW the voice speaks. Always set a tone that ma
19212
19454
 
19213
19455
  Pass one item or many (max 10) - multiple items run in parallel. Returns audio_url and caption_ref per item - pass caption_ref downstream to clipform_render_composition / clipform_render_video_template / clipform_upload_media_asset to attach this run's word-level captions instead of hand-copying the captions array.`,
19214
19456
  inputSchema: {
19457
+ workspace_id: workspaceIdInput,
19458
+ form_id: formIdTargetInput,
19215
19459
  items: external_exports.array(TtsItemSchema).min(1).max(10).describe("One or more TTS items to generate")
19216
19460
  },
19217
19461
  outputSchema: {
@@ -19227,7 +19471,11 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio_u
19227
19471
  },
19228
19472
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
19229
19473
  },
19230
- async ({ items }) => {
19474
+ // `form_id` (#4621) is a routing hint only, consumed centrally by
19475
+ // `runWithWorkspaceTarget` (server.ts) before this handler runs - it
19476
+ // swaps the auth context's workspace_id read below, but is never
19477
+ // forwarded to the API itself (the /internal/tts route takes no form_id).
19478
+ async ({ items, form_id: _form_id }) => {
19231
19479
  const workspace_id = getMcpAuth()?.workspace_id;
19232
19480
  const results = await Promise.allSettled(
19233
19481
  items.map(
@@ -20609,6 +20857,15 @@ function createServer(options) {
20609
20857
  { ...MCP_SERVER_IDENTITY },
20610
20858
  { instructions: buildInstructions(caps) }
20611
20859
  );
20860
+ const rawServer = server.server;
20861
+ const nativeSetRequestHandler = rawServer.setRequestHandler.bind(rawServer);
20862
+ let baseListToolsHandler;
20863
+ rawServer.setRequestHandler = (schema, handler) => {
20864
+ if (schema === ListToolsRequestSchema && baseListToolsHandler === void 0) {
20865
+ baseListToolsHandler = handler;
20866
+ }
20867
+ return nativeSetRequestHandler(schema, handler);
20868
+ };
20612
20869
  const _registerTool = server.registerTool.bind(server);
20613
20870
  server.registerTool = (name, config2, handler) => {
20614
20871
  const requiredTier = MCP_TOOL_TIERS[name] ?? 0;
@@ -20632,7 +20889,7 @@ function createServer(options) {
20632
20889
  config2,
20633
20890
  (...args) => runWithMcpTool(name, async () => {
20634
20891
  try {
20635
- return await handler(...args);
20892
+ return await runWithWorkspaceTarget(args[0], () => handler(...args));
20636
20893
  } catch (err) {
20637
20894
  const error2 = err instanceof Error ? err : new Error(String(err));
20638
20895
  options?.onToolError?.(name, error2);
@@ -20675,6 +20932,25 @@ function createServer(options) {
20675
20932
  registerGetWorkflowTool(server, caps);
20676
20933
  registerPrompts(server, caps);
20677
20934
  registerResources(server, caps);
20935
+ rawServer.setRequestHandler = nativeSetRequestHandler;
20936
+ if (baseListToolsHandler) {
20937
+ nativeSetRequestHandler(ListToolsRequestSchema, async (request, extra) => {
20938
+ const base = await baseListToolsHandler(request, extra);
20939
+ return {
20940
+ ...base,
20941
+ tools: base.tools.map((tool) => {
20942
+ const security = MCP_TOOL_SECURITY[tool.name];
20943
+ if (security === void 0) return tool;
20944
+ const existingMeta = tool._meta ?? {};
20945
+ return {
20946
+ ...tool,
20947
+ securitySchemes: security,
20948
+ _meta: { ...existingMeta, securitySchemes: security }
20949
+ };
20950
+ })
20951
+ };
20952
+ });
20953
+ }
20678
20954
  return server;
20679
20955
  }
20680
20956
 
@@ -20685,4 +20961,4 @@ export {
20685
20961
  buildInstructions,
20686
20962
  createServer
20687
20963
  };
20688
- //# sourceMappingURL=chunk-QBPE32AV.js.map
20964
+ //# sourceMappingURL=chunk-4XCXDH3I.js.map