@clipform/mcp-server 4.2.1 → 4.2.3

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.
@@ -42,18 +42,6 @@ declare function isAnonymousAccountRequired(result: {
42
42
  code?: string;
43
43
  reason?: string;
44
44
  }): boolean;
45
- /**
46
- * Builds the `_meta["mcp/www_authenticate"]` array a tool result carries to
47
- * trigger the Apps SDK's tool-level OAuth escalation (#4088): an array of
48
- * RFC 6750 Bearer challenge strings, the runtime half of the two-part
49
- * contract (the other half is the static MCP_TOOL_SECURITY declaration) -
50
- * per the Apps SDK auth docs, metadata alone never triggers the flow,
51
- * only a runtime error carrying this `_meta` key does. Same challenge shape
52
- * `apps/api/src/mcp/handler.ts` already emits as a real `WWW-Authenticate`
53
- * HTTP header for a bad/missing Bearer - this is the tool-result equivalent
54
- * for a caller that reached a tool (anonymously) but hit a feature that
55
- * requires an account.
56
- */
57
45
  declare function authChallengeMeta(errorCode: string, description: string, resourceMetadata?: string): Record<string, unknown>;
58
46
  declare function textResult(text: string): {
59
47
  content: {
@@ -6,7 +6,7 @@ import {
6
6
  recoveryHint,
7
7
  structuredResult,
8
8
  textResult
9
- } from "./chunk-GZZGKW5A.js";
9
+ } from "./chunk-ZRBMT7BF.js";
10
10
  import "./chunk-VPF6MLKM.js";
11
11
  import "./chunk-PBQ5BQWD.js";
12
12
  import "./chunk-PZ5AY32C.js";
@@ -2,7 +2,7 @@ import {
2
2
  CURRENT_PLAN_VERSIONS,
3
3
  PLAN_LIMITS,
4
4
  callApi
5
- } from "./chunk-GZZGKW5A.js";
5
+ } from "./chunk-ZRBMT7BF.js";
6
6
  import {
7
7
  getMcpAuth
8
8
  } from "./chunk-VPF6MLKM.js";
@@ -22,6 +22,13 @@ var ANON_PREVIEW_NOTICE = "Preview accounts are temporary - claim yours to keep
22
22
  function anonPreviewNotice() {
23
23
  return getMcpAuth()?.is_anonymous ? ANON_PREVIEW_NOTICE : null;
24
24
  }
25
+ var ANON_PREVIEW_LINK_LABEL = "Preview (answers not saved)";
26
+ var ANON_PREVIEW_ONLY_NOTICE = 'The form is ready as a preview. To go live, say "publish it" - that prompts sign-in and keeps this chat connected to your account. In the preview, answers are not saved: do not share the preview link with respondents.';
27
+ var PREVIEW_URL_DESCRIPTION = "Private preview of the form. Answers are not saved, so do not share it with respondents.";
28
+ var CLAIM_URL_DESCRIPTION = "One-time link to sign up, keep this form and publish it.";
29
+ function isPreviewViewerUrl(url) {
30
+ return typeof url === "string" && url.includes("mode=preview");
31
+ }
25
32
 
26
33
  // src/lib/session-context.ts
27
34
  async function getSessionContextWithAuth() {
@@ -96,7 +103,12 @@ export {
96
103
  capsFromToolsets,
97
104
  ANON_PREVIEW_NOTICE,
98
105
  anonPreviewNotice,
106
+ ANON_PREVIEW_LINK_LABEL,
107
+ ANON_PREVIEW_ONLY_NOTICE,
108
+ PREVIEW_URL_DESCRIPTION,
109
+ CLAIM_URL_DESCRIPTION,
110
+ isPreviewViewerUrl,
99
111
  getSessionContextWithAuth,
100
112
  getSessionContext
101
113
  };
102
- //# sourceMappingURL=chunk-O4LOUBGW.js.map
114
+ //# sourceMappingURL=chunk-E7C34M2J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/caps.ts","../src/lib/anon-notice.ts","../src/lib/session-context.ts"],"sourcesContent":["import type { McpToolset } from \"@vid-master/config\";\n\n/**\n * What a connected surface can actually do, derived from the connect-time\n * toolset filter (#3309) so the guidance layers (server instructions, prompt\n * recipes, `clipform_get_workflow`, craft guides) never reference a tool the\n * surface doesn't register (#3681).\n *\n * The Claude Connectors Directory surface (`mcp.clipform.io/forms`) is\n * account + forms only: no media generation and no research helpers. A\n * workflow that still says \"call clipform_generate_tts\" there sends the model\n * at a tool that doesn't exist, which shows up in review as failed tool\n * calls. Every guidance producer takes a ServerCaps and gates on it.\n */\nexport interface ServerCaps {\n /** Narration, video, image, music, render, and asset tools are registered. */\n media: boolean;\n /** News lookup and the generation audit log are registered. */\n research: boolean;\n}\n\n/** Every toolset registered - stdio/npx and the bare remote endpoint. */\nexport const FULL_CAPS: ServerCaps = Object.freeze({ media: true, research: true });\n\nexport function capsFromToolsets(toolsets?: readonly McpToolset[]): ServerCaps {\n if (toolsets === undefined) return FULL_CAPS;\n return {\n media: toolsets.includes(\"media\"),\n research: toolsets.includes(\"research\"),\n };\n}\n","import { getMcpAuth } from \"./auth-context.js\";\n\n/**\n * #4650: anonymous (preview) accounts are temporary. Date-free by design -\n * the purge is not enforced yet, so no expiry date or day count is promised\n * (exact dates are follow-up #4662). Defined once; every anonymous-lane\n * surface (create/update_form text, whoami, session resource) reuses it.\n */\nexport const ANON_PREVIEW_NOTICE =\n \"Preview accounts are temporary - claim yours to keep your forms.\";\n\n/** The notice for an anonymous MCP caller, or null for everyone else. */\nexport function anonPreviewNotice(): string | null {\n return getMcpAuth()?.is_anonymous ? ANON_PREVIEW_NOTICE : null;\n}\n\n/**\n * #4845: an anonymous build's link is a private preview, never a respondent\n * link (answers are not saved). Wording defined once; create_form, get_form,\n * the format-form link label and the output-schema descriptions all reuse it.\n */\nexport const ANON_PREVIEW_LINK_LABEL = \"Preview (answers not saved)\";\n\nexport const ANON_PREVIEW_ONLY_NOTICE =\n 'The form is ready as a preview. To go live, say \"publish it\" - that prompts sign-in and keeps this chat connected to your account. In the preview, answers are not saved: do not share the preview link with respondents.';\n\nexport const PREVIEW_URL_DESCRIPTION =\n \"Private preview of the form. Answers are not saved, so do not share it with respondents.\";\n\nexport const CLAIM_URL_DESCRIPTION =\n \"One-time link to sign up, keep this form and publish it.\";\n\n/** True for a viewer link in preview mode (an anonymous caller's draft). */\nexport function isPreviewViewerUrl(url: unknown): boolean {\n return typeof url === \"string\" && url.includes(\"mode=preview\");\n}\n","import { BUSINESS, PLAN_LIMITS, CURRENT_PLAN_VERSIONS } from \"@vid-master/config\";\nimport { callApi } from \"./api-client.js\";\nimport { ANON_PREVIEW_NOTICE } from \"./anon-notice.js\";\nimport { getMcpAuth } from \"./auth-context.js\";\n\nexport type AuthMode = \"authenticated\" | \"anonymous\";\n\nexport interface SessionContextResult {\n text: string;\n authMode: AuthMode;\n}\n\nexport async function getSessionContextWithAuth(): Promise<SessionContextResult> {\n let me: any;\n const authCtx = getMcpAuth();\n if (authCtx?.loadInternalContent && !authCtx.api_key) {\n // #4657: the no-workspace anon lane (get_workflow with no mint) has no\n // workspace for /me to describe, so plan caps default to Free.\n const free = PLAN_LIMITS[CURRENT_PLAN_VERSIONS[0]];\n me = {\n auth_mode: \"anonymous\",\n plan: {\n name: free.name,\n tier: free.tier,\n node_limit: free.node_limit,\n form_limit: free.form_limit ?? null,\n custom_theme: free.custom_theme,\n },\n };\n } else {\n let result;\n try {\n result = await callApi(\"/me\", { method: \"GET\" });\n } catch {\n return { text: \"\", authMode: \"anonymous\" };\n }\n if (!result.ok) return { text: \"\", authMode: \"anonymous\" };\n me = result.data as any;\n }\n // The API's auth_mode is \"api_key\" | \"oauth\" | \"session\" | \"anonymous\".\n // Anything credentialed counts as authenticated - the old `=== \"oauth\"`\n // check matched nothing the API returns, so API-key sessions were labelled\n // anonymous with a \"sign in to unlock\" nudge right next to their real\n // plan (#412 F6).\n // The anon MCP lane runs as a minted bot key (/me says \"api_key\"), so the\n // auth context is the truth for anonymous (#4653).\n const apiAuthMode: string = getMcpAuth()?.is_anonymous\n ? \"anonymous\"\n : ((me.auth_mode as string) ?? \"anonymous\");\n const authMode: AuthMode = apiAuthMode === \"anonymous\" ? \"anonymous\" : \"authenticated\";\n const plan = me.plan;\n const lines: string[] = [];\n\n lines.push(\"## Your Session\");\n if (authMode === \"authenticated\") {\n lines.push(\n apiAuthMode === \"api_key\"\n ? \"Auth: API key (workspace-scoped)\"\n : \"Auth: signed in (connected to a Clipform account)\"\n );\n // Workspace NAME only - the workspace UUID and the raw user_id/company_id\n // are internal database identifiers the model never needs (response\n // hygiene / disclosure guard, #2976).\n lines.push(`Workspace: ${me.workspace?.name ?? \"Unknown\"}`);\n } else {\n lines.push(\"Auth: anonymous (not connected to a Clipform account)\");\n if (me.workspace) {\n lines.push(`Workspace: ${me.workspace.name}`);\n }\n }\n lines.push(`Plan: ${plan.name} (tier ${plan.tier})`);\n if (plan.node_limit !== null) {\n lines.push(`Question limit: ${plan.node_limit} per form`);\n } else {\n lines.push(\"Questions: unlimited\");\n }\n if (plan.form_limit !== null) {\n lines.push(`Form limit: ${plan.form_limit} forms`);\n } else {\n lines.push(\"Forms: unlimited\");\n }\n if (plan.custom_theme === false) {\n lines.push(\"Custom themes: not available on this plan\");\n }\n if (authMode === \"anonymous\") {\n // Neutral statement of fact, no upsell/CTA copy (commerce sweep, #2976):\n // an anonymous session's forms are not tied to an account until one signs in.\n lines.push(`\\nAnonymous session: forms are not tied to a Clipform account until you sign in.`);\n lines.push(ANON_PREVIEW_NOTICE);\n }\n\n return { text: lines.join(\"\\n\"), authMode };\n}\n\nexport async function getSessionContext(): Promise<string> {\n const { text } = await getSessionContextWithAuth();\n return text;\n}\n"],"mappings":";;;;;;;;;;AAsBO,IAAM,YAAwB,OAAO,OAAO,EAAE,OAAO,MAAM,UAAU,KAAK,CAAC;AAE3E,SAAS,iBAAiB,UAA8C;AAC7E,MAAI,aAAa,OAAW,QAAO;AACnC,SAAO;AAAA,IACL,OAAO,SAAS,SAAS,OAAO;AAAA,IAChC,UAAU,SAAS,SAAS,UAAU;AAAA,EACxC;AACF;;;ACtBO,IAAM,sBACX;AAGK,SAAS,oBAAmC;AACjD,SAAO,WAAW,GAAG,eAAe,sBAAsB;AAC5D;AAOO,IAAM,0BAA0B;AAEhC,IAAM,2BACX;AAEK,IAAM,0BACX;AAEK,IAAM,wBACX;AAGK,SAAS,mBAAmB,KAAuB;AACxD,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,cAAc;AAC/D;;;ACvBA,eAAsB,4BAA2D;AAC/E,MAAI;AACJ,QAAM,UAAU,WAAW;AAC3B,MAAI,SAAS,uBAAuB,CAAC,QAAQ,SAAS;AAGpD,UAAM,OAAO,YAAY,sBAAsB,CAAC,CAAC;AACjD,SAAK;AAAA,MACH,WAAW;AAAA,MACX,MAAM;AAAA,QACJ,MAAM,KAAK;AAAA,QACX,MAAM,KAAK;AAAA,QACX,YAAY,KAAK;AAAA,QACjB,YAAY,KAAK,cAAc;AAAA,QAC/B,cAAc,KAAK;AAAA,MACrB;AAAA,IACF;AAAA,EACF,OAAO;AACL,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,QAAQ,OAAO,EAAE,QAAQ,MAAM,CAAC;AAAA,IACjD,QAAQ;AACN,aAAO,EAAE,MAAM,IAAI,UAAU,YAAY;AAAA,IAC3C;AACA,QAAI,CAAC,OAAO,GAAI,QAAO,EAAE,MAAM,IAAI,UAAU,YAAY;AACzD,SAAK,OAAO;AAAA,EACd;AAQA,QAAM,cAAsB,WAAW,GAAG,eACtC,cACE,GAAG,aAAwB;AACjC,QAAM,WAAqB,gBAAgB,cAAc,cAAc;AACvE,QAAM,OAAO,GAAG;AAChB,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,iBAAiB;AAC5B,MAAI,aAAa,iBAAiB;AAChC,UAAM;AAAA,MACJ,gBAAgB,YACZ,qCACA;AAAA,IACN;AAIA,UAAM,KAAK,cAAc,GAAG,WAAW,QAAQ,SAAS,EAAE;AAAA,EAC5D,OAAO;AACL,UAAM,KAAK,uDAAuD;AAClE,QAAI,GAAG,WAAW;AAChB,YAAM,KAAK,cAAc,GAAG,UAAU,IAAI,EAAE;AAAA,IAC9C;AAAA,EACF;AACA,QAAM,KAAK,SAAS,KAAK,IAAI,UAAU,KAAK,IAAI,GAAG;AACnD,MAAI,KAAK,eAAe,MAAM;AAC5B,UAAM,KAAK,mBAAmB,KAAK,UAAU,WAAW;AAAA,EAC1D,OAAO;AACL,UAAM,KAAK,sBAAsB;AAAA,EACnC;AACA,MAAI,KAAK,eAAe,MAAM;AAC5B,UAAM,KAAK,eAAe,KAAK,UAAU,QAAQ;AAAA,EACnD,OAAO;AACL,UAAM,KAAK,kBAAkB;AAAA,EAC/B;AACA,MAAI,KAAK,iBAAiB,OAAO;AAC/B,UAAM,KAAK,2CAA2C;AAAA,EACxD;AACA,MAAI,aAAa,aAAa;AAG5B,UAAM,KAAK;AAAA,+EAAkF;AAC7F,UAAM,KAAK,mBAAmB;AAAA,EAChC;AAEA,SAAO,EAAE,MAAM,MAAM,KAAK,IAAI,GAAG,SAAS;AAC5C;AAEA,eAAsB,oBAAqC;AACzD,QAAM,EAAE,KAAK,IAAI,MAAM,0BAA0B;AACjD,SAAO;AACT;","names":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  FULL_CAPS,
3
3
  getSessionContext
4
- } from "./chunk-O4LOUBGW.js";
4
+ } from "./chunk-E7C34M2J.js";
5
5
  import {
6
6
  ALL_VARIANTS,
7
7
  FORM_TYPES,
8
8
  FORM_TYPE_KEYS,
9
9
  callApi
10
- } from "./chunk-GZZGKW5A.js";
10
+ } from "./chunk-ZRBMT7BF.js";
11
11
  import {
12
12
  getMcpAuth
13
13
  } from "./chunk-VPF6MLKM.js";
@@ -155,4 +155,4 @@ export {
155
155
  getGuideUri,
156
156
  registerResources
157
157
  };
158
- //# sourceMappingURL=chunk-RKMGVU57.js.map
158
+ //# sourceMappingURL=chunk-MO3ABORB.js.map
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  FULL_CAPS,
3
3
  getSessionContextWithAuth
4
- } from "./chunk-O4LOUBGW.js";
4
+ } from "./chunk-E7C34M2J.js";
5
5
  import {
6
6
  WORKFLOW_TYPES,
7
7
  getDiscoveryParams
8
- } from "./chunk-GZZGKW5A.js";
8
+ } from "./chunk-ZRBMT7BF.js";
9
9
  import {
10
10
  __export
11
11
  } from "./chunk-PZ5AY32C.js";
@@ -14633,10 +14633,10 @@ var quizWorkflow = (args, sessionContext, authMode, caps) => {
14633
14633
  - title and message that fit the quiz's voice - short, punchy, and specific to the topic (not generic "Good job!"). Example: title: "Well played!", message: "Share your score and challenge a friend."
14634
14634
  - NOTE: Personalised messages per score tier (score_ranges) are coming soon. For now, write one great end screen message that works for all scores.
14635
14635
  ${TAGS_LINE}`,
14636
- caps.media && "**Generate narration** with clipform_generate_tts - batch ALL questions in a single call (pass an array of items). Tease the question - do NOT reveal the answer or read options aloud. Keep each narration 5-15 seconds.",
14636
+ caps.media && "**Generate narration** with clipform_generate_tts - batch ALL questions in a single call (pass an array of items). Narration asks the full question in natural words so the clip stands alone (never a verbatim read of the prompt, never the answer); the on-screen prompt is then a short reminder. Without narration, the prompt carries the full question.",
14637
14637
  caps.media && `**Build video** for each question - pass each question's node_id (and form_id) on the render call so it attaches automatically once done. Pick per question; mixing treatments across one quiz is fine:
14638
14638
  - Default - narrated slideshow: clipform_search_media (kind: "image", 3 images per question, batch all queries in one call), then clipform_render_video_template (template: "slideshow", controls.items + controls.audio_url) with wait: false per question - fires all renders in parallel - then collect URLs with clipform_check_render.
14639
- - Designed visual - when a recipe or composition fits the question or the user asks for one (e.g. a versus, a grid reveal, guess-the-city): recipe book first - clipform_list_video_templates and match the mechanic to a recipe, then clipform_render_video_template with its curated controls (wait: false, one call per question). Only if no recipe fits, drop to the raw primitives - clipform_list_compositions, then ONE clipform_render_composition call with items (all clips render in parallel). Collect job_ids with clipform_check_render. Both take narration via sound.narration.`,
14639
+ - Designed visual - when a recipe or composition fits the question or the user asks for one (e.g. a versus, a grid reveal, guess-the-city): recipe book first - clipform_list_video_templates and match the mechanic to a recipe, then clipform_render_video_template with its curated controls (wait: false, one call per question). Only if no recipe fits, drop to the raw primitives - clipform_list_compositions, then ONE clipform_render_composition call with items (all clips render in parallel). Collect job_ids with clipform_check_render. Recipes take the narration as audio_url (the clip sizes to it); raw compositions take it as sound.narration. Never pass a duration.`,
14640
14640
  caps.media && ATTACH_MEDIA_STEP,
14641
14641
  REPUBLISH_STEP,
14642
14642
  caps.research && `${LOG_STEP} (sources, images, attributions)`
@@ -14755,7 +14755,7 @@ Build a standard scored quiz${topic} instead: call clipform_get_workflow with ty
14755
14755
  - Optional reveal beat (type: "button"): add one only when the quiz wants extra context or celebration - the answer in the prompt ("It was Japan!"), config { button_text: "Next one" }
14756
14756
  - Final end_screen node: show_score: true, icon: "trophy", show_share_button: true, cta_type: "restart", cta_text: a short challenge. NOTE: per-tier messages (score_ranges) are coming soon - write one great message.
14757
14757
  - tags: format "quiz", genre "trivia", and 2-3 topic words`,
14758
- "**Generate narration** with clipform_generate_tts - batch ALL items in one call. The visual does the teasing: set stakes, never describe what's on screen or read the clue aloud. If a question uses the optional reveal node, its narration can celebrate.",
14758
+ "**Generate narration** with clipform_generate_tts - batch ALL items in one call. Narration asks the full question so the clip stands alone (compare mechanics name both sides; decode mechanics like flag or rebus ask the question but never decode the clue) and never describes what's on screen. An optional reveal node's narration gives the answer plus one surprising fact.",
14759
14759
  `${ATTACH_MEDIA_STEP} Attach the clue clip to each choice node; attach reveal clips to their button nodes only on questions that use one.`,
14760
14760
  REPUBLISH_STEP,
14761
14761
  caps.research && `${LOG_STEP} - record the composition IDs used per question alongside sources and attributions`
@@ -15224,4 +15224,4 @@ export {
15224
15224
  getWorkflowText,
15225
15225
  registerPrompts
15226
15226
  };
15227
- //# sourceMappingURL=chunk-QG354AVT.js.map
15227
+ //# sourceMappingURL=chunk-UYJLVVM3.js.map