@clipform/mcp-server 2.2.1 → 2.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.
- package/dist/auth-context.js +2 -1
- package/dist/{chunk-HCZI2UJ5.js → chunk-G3PMV62Z.js} +2 -20
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/{chunk-ZGXMBGOB.js → chunk-GRIUCE3Q.js} +10 -8
- package/dist/{chunk-ZGXMBGOB.js.map → chunk-GRIUCE3Q.js.map} +1 -1
- package/dist/chunk-PBQ5BQWD.js +60 -0
- package/dist/chunk-PBQ5BQWD.js.map +1 -0
- package/dist/chunk-V4L5RQWK.js +21 -0
- package/dist/{chunk-HCZI2UJ5.js.map → chunk-V4L5RQWK.js.map} +1 -1
- package/dist/{chunk-HWGDTUKS.js → chunk-WCPHQ4GU.js} +3 -3
- package/dist/{chunk-AHJXNS4R.js → chunk-WNXTF76M.js} +4 -7
- package/dist/chunk-WNXTF76M.js.map +1 -0
- package/dist/{chunk-VY52B6DR.js → chunk-YZJZ5NPL.js} +38 -52
- package/dist/chunk-YZJZ5NPL.js.map +1 -0
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/prompts.js +5 -3
- package/dist/resources.js +5 -3
- package/dist/server.js +7 -5
- package/dist/telemetry.d.ts +27 -0
- package/dist/telemetry.js +16 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +5 -1
- package/dist/chunk-AHJXNS4R.js.map +0 -1
- package/dist/chunk-VY52B6DR.js.map +0 -1
- /package/dist/{chunk-HWGDTUKS.js.map → chunk-WCPHQ4GU.js.map} +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/lib/telemetry.ts
|
|
2
|
+
function isTelemetryEnabled() {
|
|
3
|
+
return process.env.DO_NOT_TRACK !== "1" && process.env.CLIPFORM_TELEMETRY !== "off";
|
|
4
|
+
}
|
|
5
|
+
var mcpVersion = "unknown";
|
|
6
|
+
function setMcpVersion(version) {
|
|
7
|
+
mcpVersion = version;
|
|
8
|
+
}
|
|
9
|
+
function getApiBaseUrl() {
|
|
10
|
+
return process.env.API_URL || null;
|
|
11
|
+
}
|
|
12
|
+
var customReporter = null;
|
|
13
|
+
function setErrorReporter(reporter) {
|
|
14
|
+
customReporter = reporter;
|
|
15
|
+
}
|
|
16
|
+
function reportError(report) {
|
|
17
|
+
if (customReporter) {
|
|
18
|
+
customReporter(report);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!isTelemetryEnabled()) return;
|
|
22
|
+
const base = getApiBaseUrl();
|
|
23
|
+
if (!base) return;
|
|
24
|
+
const payload = {
|
|
25
|
+
...report,
|
|
26
|
+
error_message: report.error_message.slice(0, 500),
|
|
27
|
+
mcp_version: mcpVersion,
|
|
28
|
+
runtime: detectRuntime(),
|
|
29
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
30
|
+
};
|
|
31
|
+
fetch(`${base}/v1/telemetry/errors`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
34
|
+
body: JSON.stringify(payload),
|
|
35
|
+
signal: AbortSignal.timeout(2e3)
|
|
36
|
+
}).catch(() => {
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function isServerFault(status) {
|
|
40
|
+
return status >= 500;
|
|
41
|
+
}
|
|
42
|
+
function isAuthFault(status, code) {
|
|
43
|
+
if (status === 401) return true;
|
|
44
|
+
if (status === 403) return code === "FORBIDDEN";
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
function detectRuntime() {
|
|
48
|
+
if (process.env.CLAUDE_CODE) return "claude-code";
|
|
49
|
+
if (process.env.CURSOR_SESSION_ID) return "cursor";
|
|
50
|
+
return "stdio";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
setMcpVersion,
|
|
55
|
+
setErrorReporter,
|
|
56
|
+
reportError,
|
|
57
|
+
isServerFault,
|
|
58
|
+
isAuthFault
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=chunk-PBQ5BQWD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/telemetry.ts"],"sourcesContent":["// Evaluated per-call (not frozen at module load) so a runtime env change is\n// actually observed - also what makes the ordering fix below testable.\nfunction isTelemetryEnabled(): boolean {\n return process.env.DO_NOT_TRACK !== \"1\" && process.env.CLIPFORM_TELEMETRY !== \"off\";\n}\n\nlet mcpVersion = \"unknown\";\n\nexport function setMcpVersion(version: string) {\n mcpVersion = version;\n}\n\nfunction getApiBaseUrl(): string | null {\n return process.env.API_URL || null;\n}\n\nexport interface ErrorReport {\n error_type: string;\n error_message: string;\n tool_name?: string;\n status_code?: number;\n api_path?: string;\n}\n\nexport type ErrorReporter = (report: ErrorReport) => void;\n\n// Overridable delivery mechanism (#2599 follow-up). The default below POSTs\n// to /v1/telemetry/errors - correct for the external stdio MCP client, which\n// runs as its own process talking to a real remote API over HTTP. The\n// in-process host (apps/api, via packages/mcp-client) runs this same package\n// IN the API process itself, so that default would have the API POST\n// itself: mcp_version is only ever set by the stdio bin (src/index.ts), so\n// the endpoint's version-format check 400s every in-process report, and\n// /v1/telemetry/errors' 10/min IP rate limit is shared across every\n// in-process channel on one loopback IP. The in-process host installs its\n// own reporter (apps/api/src/lib/mcp-telemetry-bridge.ts) that captures\n// straight through its own PostHog client instead. Exported so that bridge\n// can install it; pass null to restore the default HTTP reporter.\nlet customReporter: ErrorReporter | null = null;\n\nexport function setErrorReporter(reporter: ErrorReporter | null): void {\n customReporter = reporter;\n}\n\nexport function reportError(report: ErrorReport) {\n // An installed reporter (the in-process host) takes precedence over the\n // opt-out gate below - DO_NOT_TRACK/CLIPFORM_TELEMETRY are an end-user\n // choice for the EXTERNAL stdio client and have no server-side meaning;\n // whatever those vars happen to be set to in the API's own environment\n // must never silently suppress the AI channels' own error capture (#2599\n // minor - the gate used to run before this check).\n if (customReporter) {\n customReporter(report);\n return;\n }\n\n if (!isTelemetryEnabled()) return;\n\n const base = getApiBaseUrl();\n if (!base) return;\n\n const payload = {\n ...report,\n error_message: report.error_message.slice(0, 500),\n mcp_version: mcpVersion,\n runtime: detectRuntime(),\n timestamp: new Date().toISOString(),\n };\n\n fetch(`${base}/v1/telemetry/errors`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(2_000),\n }).catch(() => {});\n}\n\nexport function isServerFault(status: number): boolean {\n return status >= 500;\n}\n\n/**\n * Genuine auth failures - unlike 404/422 these are never ordinary user/agent\n * error, so they're worth reporting even though they're 4xx (#2599). A 401\n * has no other legitimate cause in this API, so it always reports. A 403,\n * though, is NOT always an auth fault here: PLAN_LIMIT and other\n * business-logic denials (feature gating) also respond 403, and those are\n * ordinary user error - exactly the noise this stays silent for. Only the\n * API's own `code` on the JSON error body (from `{ code, error }`, e.g.\n * `create-form.ts`'s ApiError) tells them apart, so a 403 only counts as an\n * auth fault when the code says so; an unrecognized/missing code on a 403\n * stays silent rather than risk misclassifying a new business-logic denial.\n */\nexport function isAuthFault(status: number, code?: string): boolean {\n if (status === 401) return true;\n if (status === 403) return code === \"FORBIDDEN\";\n return false;\n}\n\nfunction detectRuntime(): string {\n if (process.env.CLAUDE_CODE) return \"claude-code\";\n if (process.env.CURSOR_SESSION_ID) return \"cursor\";\n return \"stdio\";\n}\n"],"mappings":";AAEA,SAAS,qBAA8B;AACrC,SAAO,QAAQ,IAAI,iBAAiB,OAAO,QAAQ,IAAI,uBAAuB;AAChF;AAEA,IAAI,aAAa;AAEV,SAAS,cAAc,SAAiB;AAC7C,eAAa;AACf;AAEA,SAAS,gBAA+B;AACtC,SAAO,QAAQ,IAAI,WAAW;AAChC;AAwBA,IAAI,iBAAuC;AAEpC,SAAS,iBAAiB,UAAsC;AACrE,mBAAiB;AACnB;AAEO,SAAS,YAAY,QAAqB;AAO/C,MAAI,gBAAgB;AAClB,mBAAe,MAAM;AACrB;AAAA,EACF;AAEA,MAAI,CAAC,mBAAmB,EAAG;AAE3B,QAAM,OAAO,cAAc;AAC3B,MAAI,CAAC,KAAM;AAEX,QAAM,UAAU;AAAA,IACd,GAAG;AAAA,IACH,eAAe,OAAO,cAAc,MAAM,GAAG,GAAG;AAAA,IAChD,aAAa;AAAA,IACb,SAAS,cAAc;AAAA,IACvB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AAEA,QAAM,GAAG,IAAI,wBAAwB;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,IAC5B,QAAQ,YAAY,QAAQ,GAAK;AAAA,EACnC,CAAC,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACnB;AAEO,SAAS,cAAc,QAAyB;AACrD,SAAO,UAAU;AACnB;AAcO,SAAS,YAAY,QAAgB,MAAwB;AAClE,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO,SAAS;AACpC,SAAO;AACT;AAEA,SAAS,gBAAwB;AAC/B,MAAI,QAAQ,IAAI,YAAa,QAAO;AACpC,MAAI,QAAQ,IAAI,kBAAmB,QAAO;AAC1C,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/lib/auth-context.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
var storage = new AsyncLocalStorage();
|
|
4
|
+
function runWithMcpAuth(ctx, fn) {
|
|
5
|
+
return storage.run(ctx, fn);
|
|
6
|
+
}
|
|
7
|
+
function getMcpAuth() {
|
|
8
|
+
return storage.getStore();
|
|
9
|
+
}
|
|
10
|
+
function runWithMcpTool(toolName, fn) {
|
|
11
|
+
const parent = storage.getStore();
|
|
12
|
+
if (!parent) return fn();
|
|
13
|
+
return storage.run({ ...parent, tool_name: toolName }, fn);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
runWithMcpAuth,
|
|
18
|
+
getMcpAuth,
|
|
19
|
+
runWithMcpTool
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=chunk-V4L5RQWK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/auth-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Identity of the end-user a given MCP request is acting on behalf of.\n *\n * Populated by the in-process host (apps/api) after validating an OAuth\n * Bearer on /mcp. The MCP tool layer reads it to decide which workspace\n * forms should land in and which plan tier gates apply.\n *\n * NOT populated when the server runs as a local stdio process (npx) - in\n * that mode tools fall back to edit-token based auth.\n */\nexport interface McpAuthContext {\n user_id: string;\n workspace_id: string;\n scopes: string[];\n tool_name?: string;\n api_key?: string;\n}\n\nconst storage = new AsyncLocalStorage<McpAuthContext>();\n\nexport function runWithMcpAuth<T>(ctx: McpAuthContext, fn: () => Promise<T> | T): Promise<T> | T {\n return storage.run(ctx, fn);\n}\n\nexport function getMcpAuth(): McpAuthContext | undefined {\n return storage.getStore();\n}\n\nexport function runWithMcpTool<T>(toolName: string, fn: () => Promise<T> | T): Promise<T> | T {\n const parent = storage.getStore();\n if (!parent) return fn();\n return storage.run({ ...parent, tool_name: toolName }, fn);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/lib/auth-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Identity of the end-user a given MCP request is acting on behalf of.\n *\n * Populated by the in-process host (apps/api) after validating an OAuth\n * Bearer on /mcp. The MCP tool layer reads it to decide which workspace\n * forms should land in and which plan tier gates apply.\n *\n * NOT populated when the server runs as a local stdio process (npx) - in\n * that mode tools fall back to edit-token based auth.\n */\nexport interface McpAuthContext {\n user_id: string;\n workspace_id: string;\n scopes: string[];\n tool_name?: string;\n api_key?: string;\n}\n\nconst storage = new AsyncLocalStorage<McpAuthContext>();\n\nexport function runWithMcpAuth<T>(ctx: McpAuthContext, fn: () => Promise<T> | T): Promise<T> | T {\n return storage.run(ctx, fn);\n}\n\nexport function getMcpAuth(): McpAuthContext | undefined {\n return storage.getStore();\n}\n\nexport function runWithMcpTool<T>(toolName: string, fn: () => Promise<T> | T): Promise<T> | T {\n const parent = storage.getStore();\n if (!parent) return fn();\n return storage.run({ ...parent, tool_name: toolName }, fn);\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAoBlC,IAAM,UAAU,IAAI,kBAAkC;AAE/C,SAAS,eAAkB,KAAqB,IAA0C;AAC/F,SAAO,QAAQ,IAAI,KAAK,EAAE;AAC5B;AAEO,SAAS,aAAyC;AACvD,SAAO,QAAQ,SAAS;AAC1B;AAEO,SAAS,eAAkB,UAAkB,IAA0C;AAC5F,QAAM,SAAS,QAAQ,SAAS;AAChC,MAAI,CAAC,OAAQ,QAAO,GAAG;AACvB,SAAO,QAAQ,IAAI,EAAE,GAAG,QAAQ,WAAW,SAAS,GAAG,EAAE;AAC3D;","names":[]}
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
WORKFLOW_TYPES,
|
|
3
3
|
getDiscoveryParams,
|
|
4
4
|
getSessionContextWithAuth
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YZJZ5NPL.js";
|
|
6
6
|
import {
|
|
7
7
|
__export
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-G3PMV62Z.js";
|
|
9
9
|
|
|
10
10
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
11
11
|
var external_exports = {};
|
|
@@ -4708,4 +4708,4 @@ export {
|
|
|
4708
4708
|
getWorkflowText,
|
|
4709
4709
|
registerPrompts
|
|
4710
4710
|
};
|
|
4711
|
-
//# sourceMappingURL=chunk-
|
|
4711
|
+
//# sourceMappingURL=chunk-WCPHQ4GU.js.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ALL_VARIANTS,
|
|
3
|
+
FORM_TYPES,
|
|
3
4
|
FORM_TYPE_KEYS,
|
|
4
5
|
callApi,
|
|
5
6
|
getSessionContext
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-YZJZ5NPL.js";
|
|
7
8
|
|
|
8
9
|
// src/lib/guides.ts
|
|
9
10
|
var guidesPromise = null;
|
|
@@ -54,11 +55,7 @@ var GUIDE_DESCRIPTIONS = {
|
|
|
54
55
|
"application": "Craft knowledge for application and evaluation forms - multi-section structure, video responses for behavioural questions, screening",
|
|
55
56
|
"booking": "Craft knowledge for event registration and booking forms - minimal friction, video welcome, confirmation flow"
|
|
56
57
|
};
|
|
57
|
-
var QUIZ_VARIANT_DESCRIPTIONS =
|
|
58
|
-
"personality": "Addendum for personality quizzes - category design, option weighting, outcome writing, no right/wrong answers",
|
|
59
|
-
"comprehension": "Addendum for YouTube comprehension quizzes - extracting questions from transcripts, distractor design, audience adaptation",
|
|
60
|
-
"composition": "Addendum for composition quizzes - guess formats built from rendered compositions: mechanic selection, the clue clip + answer feedback default (with an optional reveal payoff node), difficulty design"
|
|
61
|
-
};
|
|
58
|
+
var QUIZ_VARIANT_DESCRIPTIONS = FORM_TYPES.quiz.variant_descriptions;
|
|
62
59
|
function getGuideUri(type, variant) {
|
|
63
60
|
if (type === "quiz" && variant) {
|
|
64
61
|
return `clipform://guides/quiz/${variant}`;
|
|
@@ -136,4 +133,4 @@ export {
|
|
|
136
133
|
getGuideUri,
|
|
137
134
|
registerResources
|
|
138
135
|
};
|
|
139
|
-
//# sourceMappingURL=chunk-
|
|
136
|
+
//# sourceMappingURL=chunk-WNXTF76M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/guides.ts","../src/resources.ts"],"sourcesContent":["import { callApi } from \"./api-client.js\";\n\n/**\n * Craft guide bodies live server-side (#470) - the npm package is a shim\n * that fetches them at runtime instead of bundling them in the tarball.\n * One fetch per process (all ~30KB of guides in a single response), cached;\n * a failed fetch is NOT cached so the next read retries.\n */\n\nexport interface RemoteGuide {\n type: string;\n variant: string | null;\n uri: string;\n mimeType: string;\n text: string;\n}\n\nlet guidesPromise: Promise<Map<string, RemoteGuide>> | null = null;\n\nasync function loadGuides(): Promise<Map<string, RemoteGuide>> {\n const result = await callApi(\"/internal/mcp/guides\", { method: \"GET\" });\n if (!result.ok) {\n throw new Error(`Failed to load guides: ${result.error}`);\n }\n const guides = (result.data as { guides?: RemoteGuide[] }).guides ?? [];\n return new Map(guides.map((g) => [g.uri, g]));\n}\n\n/** Fetch a guide body by its clipform:// URI. Returns null when unavailable. */\nexport async function fetchGuideText(uri: string): Promise<string | null> {\n if (!guidesPromise) {\n guidesPromise = loadGuides();\n }\n try {\n const guides = await guidesPromise;\n return guides.get(uri)?.text ?? null;\n } catch (err) {\n guidesPromise = null; // don't cache failures - retry on the next read\n // Loud, not silent: a misconfigured API_URL/key otherwise degrades every\n // guide to the stub with nothing in the logs.\n console.error(`[guides] Failed to fetch ${uri}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n}\n\n/** Shown in place of a guide when the API is unreachable or unauthenticated. */\nexport function guideFallbackText(uri: string): string {\n return [\n `# Guide unavailable`,\n ``,\n `The craft guide (${uri}) could not be loaded from the Clipform API.`,\n `Guides are served at runtime and need a reachable API with a valid key -`,\n `check API_URL and CLIPFORM_API_KEY, then try again.`,\n ``,\n `General principles in the meantime: write narration for the ear (short,`,\n `conversational), never reveal answers in narration or media, and keep`,\n `forms tight - every question must earn its place.`,\n ].join(\"\\n\");\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getSessionContext } from \"./lib/session-context.js\";\nimport { FORM_TYPE_KEYS, FORM_TYPES, ALL_VARIANTS } from \"@vid-master/config\";\n\nexport const GUIDE_TYPES = FORM_TYPE_KEYS as readonly string[] as readonly [string, ...string[]];\nexport type GuideType = (typeof FORM_TYPE_KEYS)[number];\n\nexport const QUIZ_VARIANTS = ALL_VARIANTS as readonly string[] as readonly [string, ...string[]];\nexport type QuizVariant = (typeof ALL_VARIANTS)[number];\n// Guide BODIES are not bundled here (#470): the npm tarball ships only this\n// registration metadata. Text is fetched from the API at runtime - see\n// lib/guides.ts (cached per process, stub fallback when unreachable).\nimport { fetchGuideText, guideFallbackText } from \"./lib/guides.js\";\n\nconst GUIDE_DESCRIPTIONS: Record<GuideType, string> = {\n \"quiz\": \"Craft knowledge for writing engaging quizzes - difficulty curves, question psychology, narration style, scoring\",\n \"survey\": \"Craft knowledge for feedback surveys, NPS, and research forms - brevity, rating scales, respondent fatigue\",\n \"interview\": \"Craft knowledge for building interview forms - warm-up pacing, open questions, consent, video responses\",\n \"funnel\": \"Craft knowledge for lead qualification funnels - planned feature, conditional routing coming soon\",\n \"testimonial\": \"Craft knowledge for collecting testimonials and customer stories on video - storytelling prompts, comfort techniques, consent\",\n \"application\": \"Craft knowledge for application and evaluation forms - multi-section structure, video responses for behavioural questions, screening\",\n \"booking\": \"Craft knowledge for event registration and booking forms - minimal friction, video welcome, confirmation flow\",\n};\n\n// Sourced from config (single source of truth) so the agent-facing resource\n// descriptions can't drift from FORM_TYPES.quiz.variant_descriptions (#2534,\n// the exact pair packages/config/CLAUDE.md warns about).\nconst QUIZ_VARIANT_DESCRIPTIONS = FORM_TYPES.quiz.variant_descriptions;\n\nexport function getGuideUri(type: GuideType, variant?: QuizVariant): string {\n if (type === \"quiz\" && variant) {\n return `clipform://guides/quiz/${variant}`;\n }\n return `clipform://guides/${type}`;\n}\n\nasync function readGuide(uri: string): Promise<string> {\n return (await fetchGuideText(uri)) ?? guideFallbackText(uri);\n}\n\nexport function registerResources(server: McpServer) {\n for (const type of GUIDE_TYPES) {\n server.registerResource(\n `guide-${type}`,\n getGuideUri(type),\n {\n description: GUIDE_DESCRIPTIONS[type],\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\" as const], priority: 0.8 },\n },\n async () => ({\n contents: [{\n uri: getGuideUri(type),\n mimeType: \"text/markdown\",\n text: await readGuide(getGuideUri(type)),\n }],\n }),\n );\n }\n\n for (const variant of QUIZ_VARIANTS) {\n server.registerResource(\n `guide-quiz-${variant}`,\n getGuideUri(\"quiz\", variant),\n {\n description: QUIZ_VARIANT_DESCRIPTIONS[variant],\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\" as const], priority: 0.8 },\n },\n async () => ({\n contents: [{\n uri: getGuideUri(\"quiz\", variant),\n mimeType: \"text/markdown\",\n text: await readGuide(getGuideUri(\"quiz\", variant)),\n }],\n }),\n );\n }\n\n server.registerResource(\n \"context-session\",\n \"clipform://context/session\",\n {\n description:\n \"Current session info: auth mode, workspace, plan tier, node limits, feature flags. Read this before planning content to know your constraints.\",\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\"], priority: 1.0 },\n },\n async () => {\n const text = await getSessionContext();\n return {\n contents: [\n {\n uri: \"clipform://context/session\",\n mimeType: \"text/markdown\",\n text: text || \"Session context unavailable - API may not be reachable.\",\n },\n ],\n };\n }\n );\n}\n"],"mappings":";;;;;;;;;AAiBA,IAAI,gBAA0D;AAE9D,eAAe,aAAgD;AAC7D,QAAM,SAAS,MAAM,QAAQ,wBAAwB,EAAE,QAAQ,MAAM,CAAC;AACtE,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,IAAI,MAAM,0BAA0B,OAAO,KAAK,EAAE;AAAA,EAC1D;AACA,QAAM,SAAU,OAAO,KAAoC,UAAU,CAAC;AACtE,SAAO,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9C;AAGA,eAAsB,eAAe,KAAqC;AACxE,MAAI,CAAC,eAAe;AAClB,oBAAgB,WAAW;AAAA,EAC7B;AACA,MAAI;AACF,UAAM,SAAS,MAAM;AACrB,WAAO,OAAO,IAAI,GAAG,GAAG,QAAQ;AAAA,EAClC,SAAS,KAAK;AACZ,oBAAgB;AAGhB,YAAQ,MAAM,4BAA4B,GAAG,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACpG,WAAO;AAAA,EACT;AACF;AAGO,SAAS,kBAAkB,KAAqB;AACrD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,oBAAoB,GAAG;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;;;ACtDO,IAAM,cAAc;AAGpB,IAAM,gBAAgB;AAO7B,IAAM,qBAAgD;AAAA,EACpD,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,eAAe;AAAA,EACf,eAAe;AAAA,EACf,WAAW;AACb;AAKA,IAAM,4BAA4B,WAAW,KAAK;AAE3C,SAAS,YAAY,MAAiB,SAA+B;AAC1E,MAAI,SAAS,UAAU,SAAS;AAC9B,WAAO,0BAA0B,OAAO;AAAA,EAC1C;AACA,SAAO,qBAAqB,IAAI;AAClC;AAEA,eAAe,UAAU,KAA8B;AACrD,SAAQ,MAAM,eAAe,GAAG,KAAM,kBAAkB,GAAG;AAC7D;AAEO,SAAS,kBAAkB,QAAmB;AACnD,aAAW,QAAQ,aAAa;AAC9B,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb,YAAY,IAAI;AAAA,MAChB;AAAA,QACE,aAAa,mBAAmB,IAAI;AAAA,QACpC,UAAU;AAAA,QACV,aAAa,EAAE,UAAU,CAAC,WAAoB,GAAG,UAAU,IAAI;AAAA,MACjE;AAAA,MACA,aAAa;AAAA,QACX,UAAU,CAAC;AAAA,UACT,KAAK,YAAY,IAAI;AAAA,UACrB,UAAU;AAAA,UACV,MAAM,MAAM,UAAU,YAAY,IAAI,CAAC;AAAA,QACzC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,aAAW,WAAW,eAAe;AACnC,WAAO;AAAA,MACL,cAAc,OAAO;AAAA,MACrB,YAAY,QAAQ,OAAO;AAAA,MAC3B;AAAA,QACE,aAAa,0BAA0B,OAAO;AAAA,QAC9C,UAAU;AAAA,QACV,aAAa,EAAE,UAAU,CAAC,WAAoB,GAAG,UAAU,IAAI;AAAA,MACjE;AAAA,MACA,aAAa;AAAA,QACX,UAAU,CAAC;AAAA,UACT,KAAK,YAAY,QAAQ,OAAO;AAAA,UAChC,UAAU;AAAA,UACV,MAAM,MAAM,UAAU,YAAY,QAAQ,OAAO,CAAC;AAAA,QACpD,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MACF,UAAU;AAAA,MACV,aAAa,EAAE,UAAU,CAAC,WAAW,GAAG,UAAU,EAAI;AAAA,IACxD;AAAA,IACA,YAAY;AACV,YAAM,OAAO,MAAM,kBAAkB;AACrC,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK;AAAA,YACL,UAAU;AAAA,YACV,MAAM,QAAQ;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getMcpAuth
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V4L5RQWK.js";
|
|
4
|
+
import {
|
|
5
|
+
isAuthFault,
|
|
6
|
+
isServerFault,
|
|
7
|
+
reportError
|
|
8
|
+
} from "./chunk-PBQ5BQWD.js";
|
|
4
9
|
|
|
5
10
|
// ../config/features.js
|
|
6
11
|
var FEATURES = {
|
|
@@ -331,9 +336,9 @@ var FEATURES = {
|
|
|
331
336
|
status: "planned",
|
|
332
337
|
category: "sharing"
|
|
333
338
|
},
|
|
334
|
-
|
|
335
|
-
name: "
|
|
336
|
-
description: "
|
|
339
|
+
dynamic_link: {
|
|
340
|
+
name: "Dynamic links",
|
|
341
|
+
description: "A named link that points at a form, so the target can be swapped from the dashboard without changing the embed code or printed link",
|
|
337
342
|
enabled: false,
|
|
338
343
|
status: "beta",
|
|
339
344
|
category: "sharing"
|
|
@@ -1049,7 +1054,10 @@ var NODE_TYPES = {
|
|
|
1049
1054
|
type: "object",
|
|
1050
1055
|
properties: {
|
|
1051
1056
|
max_files: { max: 20, min: 1, type: "number", label: "Max files", default: 5, description: "Maximum number of files respondents can upload" },
|
|
1052
|
-
|
|
1057
|
+
// Capped at 50, not 500: respondent file uploads go straight to the Supabase
|
|
1058
|
+
// `file-uploads` bucket, and SUPABASE_STORAGE_MAX_BYTES is a hard ceiling there.
|
|
1059
|
+
// Anything above it passed validation and then 500'd at the storage layer (#2556).
|
|
1060
|
+
max_file_size_mb: { max: 50, min: 1, type: "number", label: "Max file size (MB)", default: 50, description: "Maximum size per file (1-50 MB)" },
|
|
1053
1061
|
allowed_media_types: {
|
|
1054
1062
|
type: "array",
|
|
1055
1063
|
items: { enum: ["images", "videos", "documents", "all"], type: "string" },
|
|
@@ -2187,11 +2195,15 @@ var BUSINESS = {
|
|
|
2187
2195
|
support: "support@clipform.io",
|
|
2188
2196
|
sales: "sales@clipform.io",
|
|
2189
2197
|
hello: "hello@clipform.io",
|
|
2190
|
-
founder: "andy@clipform.io"
|
|
2198
|
+
founder: "andy@clipform.io",
|
|
2199
|
+
// Inbound form-builder address (Cloudflare Email Routing -> email-inbound
|
|
2200
|
+
// worker). Also the Reply-To on bot replies so a reply reaches the bot.
|
|
2201
|
+
new: "new@clipform.io"
|
|
2191
2202
|
},
|
|
2192
2203
|
urls: getUrls()
|
|
2193
2204
|
};
|
|
2194
2205
|
var MEDIA_DIRECT_MAX_BYTES = 20 * 1024 * 1024;
|
|
2206
|
+
var SUPABASE_STORAGE_MAX_BYTES = 50 * 1024 * 1024;
|
|
2195
2207
|
var VIDEO_COMPRESS_FLOOR_BYTES = 1 * 1024 * 1024;
|
|
2196
2208
|
var HELP_LINKS = buildHelpLinks(BUSINESS.urls.docs);
|
|
2197
2209
|
var CONTACT_FIELDS = [
|
|
@@ -2276,50 +2288,17 @@ function exposedCompositionIds(includeNonPublic) {
|
|
|
2276
2288
|
var SCENE_LAYER_COMPOSITION_IDS = COMPOSITION_REGISTRY.filter((c) => c.scene_layer).map((c) => c.id);
|
|
2277
2289
|
var MCP_TOOL_TIERS = {};
|
|
2278
2290
|
|
|
2279
|
-
// src/lib/telemetry.ts
|
|
2280
|
-
var telemetryEnabled = process.env.DO_NOT_TRACK !== "1" && process.env.CLIPFORM_TELEMETRY !== "off";
|
|
2281
|
-
var mcpVersion = "unknown";
|
|
2282
|
-
function setMcpVersion(version) {
|
|
2283
|
-
mcpVersion = version;
|
|
2284
|
-
}
|
|
2285
|
-
function getApiBaseUrl() {
|
|
2286
|
-
return process.env.API_URL || null;
|
|
2287
|
-
}
|
|
2288
|
-
function reportError(report) {
|
|
2289
|
-
if (!telemetryEnabled) return;
|
|
2290
|
-
const base = getApiBaseUrl();
|
|
2291
|
-
if (!base) return;
|
|
2292
|
-
const payload = {
|
|
2293
|
-
...report,
|
|
2294
|
-
error_message: report.error_message.slice(0, 500),
|
|
2295
|
-
mcp_version: mcpVersion,
|
|
2296
|
-
runtime: detectRuntime(),
|
|
2297
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2298
|
-
};
|
|
2299
|
-
fetch(`${base}/v1/telemetry/errors`, {
|
|
2300
|
-
method: "POST",
|
|
2301
|
-
headers: { "Content-Type": "application/json" },
|
|
2302
|
-
body: JSON.stringify(payload),
|
|
2303
|
-
signal: AbortSignal.timeout(2e3)
|
|
2304
|
-
}).catch(() => {
|
|
2305
|
-
});
|
|
2306
|
-
}
|
|
2307
|
-
function isServerFault(status) {
|
|
2308
|
-
return status >= 500;
|
|
2309
|
-
}
|
|
2310
|
-
function detectRuntime() {
|
|
2311
|
-
if (process.env.CLAUDE_CODE) return "claude-code";
|
|
2312
|
-
if (process.env.CURSOR_SESSION_ID) return "cursor";
|
|
2313
|
-
return "stdio";
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
2291
|
// src/lib/api-client.ts
|
|
2317
|
-
function
|
|
2292
|
+
function getApiBaseUrl() {
|
|
2318
2293
|
const url = process.env.API_URL;
|
|
2319
2294
|
if (!url) {
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2295
|
+
const message = "API_URL must be set (e.g. http://localhost:3003 or https://api.clipform.io)";
|
|
2296
|
+
reportError({
|
|
2297
|
+
error_type: "config_error",
|
|
2298
|
+
error_message: message,
|
|
2299
|
+
tool_name: getMcpAuth()?.tool_name
|
|
2300
|
+
});
|
|
2301
|
+
throw new Error(message);
|
|
2323
2302
|
}
|
|
2324
2303
|
return url;
|
|
2325
2304
|
}
|
|
@@ -2327,14 +2306,20 @@ function getAuthHeaders() {
|
|
|
2327
2306
|
const authCtx = getMcpAuth();
|
|
2328
2307
|
const apiKey = authCtx?.api_key || process.env.CLIPFORM_API_KEY;
|
|
2329
2308
|
if (!apiKey) {
|
|
2330
|
-
|
|
2309
|
+
const message = "CLIPFORM_API_KEY must be set.";
|
|
2310
|
+
reportError({
|
|
2311
|
+
error_type: "config_error",
|
|
2312
|
+
error_message: message,
|
|
2313
|
+
tool_name: authCtx?.tool_name
|
|
2314
|
+
});
|
|
2315
|
+
throw new Error(message);
|
|
2331
2316
|
}
|
|
2332
2317
|
return { "Authorization": `Bearer ${apiKey}` };
|
|
2333
2318
|
}
|
|
2334
2319
|
async function callApi(path, options = {}) {
|
|
2335
2320
|
const { body, params, timeoutMs = 3e4 } = options;
|
|
2336
2321
|
const method = options.method || (body ? "POST" : "GET");
|
|
2337
|
-
const base =
|
|
2322
|
+
const base = getApiBaseUrl();
|
|
2338
2323
|
const prefix = path.startsWith("/internal/") ? "" : "/v1";
|
|
2339
2324
|
let url = `${base}${prefix}${path}`;
|
|
2340
2325
|
if (params) {
|
|
@@ -2380,7 +2365,8 @@ Error: ${message}`
|
|
|
2380
2365
|
const hint = recoveryHint(response.status, path);
|
|
2381
2366
|
const apiMsg = data.error || `API error (${response.status})`;
|
|
2382
2367
|
const errorMsg = hint ? `${apiMsg} - ${hint}` : apiMsg;
|
|
2383
|
-
|
|
2368
|
+
const errorCode = typeof data.code === "string" ? data.code : void 0;
|
|
2369
|
+
if (isServerFault(response.status) || isAuthFault(response.status, errorCode)) {
|
|
2384
2370
|
reportError({
|
|
2385
2371
|
error_type: `http_${response.status}`,
|
|
2386
2372
|
error_message: errorMsg,
|
|
@@ -2486,6 +2472,7 @@ export {
|
|
|
2486
2472
|
NODE_TYPES,
|
|
2487
2473
|
ACTIVE_NODE_TYPES,
|
|
2488
2474
|
NON_COUNTABLE_TYPES,
|
|
2475
|
+
FORM_TYPES,
|
|
2489
2476
|
FORM_TYPE_KEYS,
|
|
2490
2477
|
WORKFLOW_TYPES,
|
|
2491
2478
|
FORM_TYPE_ALIASES,
|
|
@@ -2500,7 +2487,6 @@ export {
|
|
|
2500
2487
|
SLIDESHOW_TRANSITIONS,
|
|
2501
2488
|
exposedCompositionIds,
|
|
2502
2489
|
MCP_TOOL_TIERS,
|
|
2503
|
-
setMcpVersion,
|
|
2504
2490
|
callApi,
|
|
2505
2491
|
errorResult,
|
|
2506
2492
|
textResult,
|
|
@@ -2508,4 +2494,4 @@ export {
|
|
|
2508
2494
|
getSessionContextWithAuth,
|
|
2509
2495
|
getSessionContext
|
|
2510
2496
|
};
|
|
2511
|
-
//# sourceMappingURL=chunk-
|
|
2497
|
+
//# sourceMappingURL=chunk-YZJZ5NPL.js.map
|