@clipform/mcp-server 1.32.0 → 1.34.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.
- package/README.md +11 -4
- package/dist/{chunk-B32WK2NQ.js → chunk-JID43EDM.js} +6 -6
- package/dist/chunk-JID43EDM.js.map +1 -0
- package/dist/{chunk-ELO7KCMZ.js → chunk-JOJJ2XFL.js} +74 -86
- package/dist/{chunk-ELO7KCMZ.js.map → chunk-JOJJ2XFL.js.map} +1 -1
- package/dist/{chunk-NHFRYNJ3.js → chunk-KWEBCAPF.js} +10 -10
- package/dist/{chunk-NHFRYNJ3.js.map → chunk-KWEBCAPF.js.map} +1 -1
- package/dist/{chunk-HQGYGAE4.js → chunk-ZVHI2V7B.js} +124 -30
- package/dist/chunk-ZVHI2V7B.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.js +2 -2
- package/dist/resources.js +2 -2
- package/dist/server.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-B32WK2NQ.js.map +0 -1
- package/dist/chunk-HQGYGAE4.js.map +0 -1
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
getWorkflowText,
|
|
7
7
|
objectType,
|
|
8
8
|
registerPrompts
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-JOJJ2XFL.js";
|
|
10
10
|
import {
|
|
11
11
|
GUIDE_TYPES,
|
|
12
12
|
QUIZ_VARIANTS,
|
|
13
13
|
getGuideContent,
|
|
14
14
|
getGuideUri,
|
|
15
15
|
registerResources
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JID43EDM.js";
|
|
17
17
|
import {
|
|
18
18
|
BUSINESS,
|
|
19
19
|
CONTACT_FIELDS,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
errorResult,
|
|
30
30
|
resolveFormType,
|
|
31
31
|
textResult
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-KWEBCAPF.js";
|
|
33
33
|
import {
|
|
34
34
|
__commonJS,
|
|
35
35
|
__export,
|
|
@@ -17196,7 +17196,8 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17196
17196
|
);
|
|
17197
17197
|
}
|
|
17198
17198
|
planContext = {
|
|
17199
|
-
auth_mode: me.auth_mode,
|
|
17199
|
+
auth_mode: me.auth_mode ?? "anonymous",
|
|
17200
|
+
is_authenticated: me.auth_mode != null && me.auth_mode !== "anonymous",
|
|
17200
17201
|
workspace_id: workspaceId,
|
|
17201
17202
|
workspace_name: me.workspace?.name ?? null,
|
|
17202
17203
|
plan_name: me.plan?.name ?? "Free",
|
|
@@ -17206,7 +17207,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17206
17207
|
const contentCount = nodes.filter((q) => !NON_COUNTABLE_TYPES.includes(q.type)).length;
|
|
17207
17208
|
if (planContext.node_limit !== null && contentCount > planContext.node_limit) {
|
|
17208
17209
|
const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
|
|
17209
|
-
const message = planContext.
|
|
17210
|
+
const message = planContext.is_authenticated ? `Your '${planContext.workspace_name}' workspace is on the ${planContext.plan_name} plan, capped at ${planContext.node_limit} nodes per form. You asked for ${contentCount}. Either rerun with ${planContext.node_limit} nodes or upgrade at ${upgradeUrl}.` : `Anonymous sessions are capped at ${planContext.node_limit} nodes per form (${planContext.plan_name} plan). You asked for ${contentCount}. Either rerun with ${planContext.node_limit} nodes, or sign in to your Clipform account so forms land in your workspace with your real plan limits.`;
|
|
17210
17211
|
return errorResult(message);
|
|
17211
17212
|
}
|
|
17212
17213
|
if (planContext.form_limit !== null) {
|
|
@@ -17216,14 +17217,14 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17216
17217
|
if (forms.length >= planContext.form_limit) {
|
|
17217
17218
|
const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
|
|
17218
17219
|
const formList = forms.map((f, i) => ` ${i + 1}. "${f.title || "Untitled"}" (id: ${f.id})`).join("\n");
|
|
17219
|
-
const options = planContext.
|
|
17220
|
+
const options = planContext.is_authenticated ? ` - Upgrade for unlimited forms: ${upgradeUrl}
|
|
17220
17221
|
- Or delete one of the forms below to free a slot.` : ` - Delete one of the forms below to free a slot.
|
|
17221
17222
|
- Or sign in to a Clipform account to keep these forms and get higher limits.`;
|
|
17222
17223
|
return errorResult(
|
|
17223
17224
|
[
|
|
17224
17225
|
`LIMIT REACHED - the form was NOT created. The plan's form limit is full.`,
|
|
17225
17226
|
``,
|
|
17226
|
-
`This ${planContext.
|
|
17227
|
+
`This ${planContext.is_authenticated ? `workspace ('${planContext.workspace_name}')` : "anonymous session"} is on the ${planContext.plan_name} plan, which allows ${planContext.form_limit} forms, and it's already full. To make a new one:`,
|
|
17227
17228
|
options,
|
|
17228
17229
|
``,
|
|
17229
17230
|
`Current forms:`,
|
|
@@ -17304,7 +17305,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17304
17305
|
const nodePart = planContext.node_limit === null ? "unlimited nodes" : `up to ${planContext.node_limit} nodes per form`;
|
|
17305
17306
|
const formPart = planContext.form_limit === null ? "unlimited forms" : `up to ${planContext.form_limit} forms`;
|
|
17306
17307
|
const limitNote = `${formPart}, ${nodePart}`;
|
|
17307
|
-
if (planContext.
|
|
17308
|
+
if (planContext.is_authenticated) {
|
|
17308
17309
|
lines.push(
|
|
17309
17310
|
``,
|
|
17310
17311
|
`Plan: ${planContext.plan_name} (${limitNote}) in workspace '${planContext.workspace_name}'.`
|
|
@@ -18448,6 +18449,98 @@ function registerListAssetsTool(server) {
|
|
|
18448
18449
|
);
|
|
18449
18450
|
}
|
|
18450
18451
|
|
|
18452
|
+
// ../../node_modules/.pnpm/uuid@11.1.1/node_modules/uuid/dist/esm/stringify.js
|
|
18453
|
+
var byteToHex = [];
|
|
18454
|
+
for (let i = 0; i < 256; ++i) {
|
|
18455
|
+
byteToHex.push((i + 256).toString(16).slice(1));
|
|
18456
|
+
}
|
|
18457
|
+
function unsafeStringify(arr, offset = 0) {
|
|
18458
|
+
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
18459
|
+
}
|
|
18460
|
+
|
|
18461
|
+
// ../../node_modules/.pnpm/uuid@11.1.1/node_modules/uuid/dist/esm/rng.js
|
|
18462
|
+
import { randomFillSync } from "crypto";
|
|
18463
|
+
var rnds8Pool = new Uint8Array(256);
|
|
18464
|
+
var poolPtr = rnds8Pool.length;
|
|
18465
|
+
function rng() {
|
|
18466
|
+
if (poolPtr > rnds8Pool.length - 16) {
|
|
18467
|
+
randomFillSync(rnds8Pool);
|
|
18468
|
+
poolPtr = 0;
|
|
18469
|
+
}
|
|
18470
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
18471
|
+
}
|
|
18472
|
+
|
|
18473
|
+
// ../../node_modules/.pnpm/uuid@11.1.1/node_modules/uuid/dist/esm/native.js
|
|
18474
|
+
import { randomUUID } from "crypto";
|
|
18475
|
+
var native_default = { randomUUID };
|
|
18476
|
+
|
|
18477
|
+
// ../../node_modules/.pnpm/uuid@11.1.1/node_modules/uuid/dist/esm/v4.js
|
|
18478
|
+
function v4(options, buf, offset) {
|
|
18479
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
18480
|
+
return native_default.randomUUID();
|
|
18481
|
+
}
|
|
18482
|
+
options = options || {};
|
|
18483
|
+
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
18484
|
+
if (rnds.length < 16) {
|
|
18485
|
+
throw new Error("Random bytes length must be >= 16");
|
|
18486
|
+
}
|
|
18487
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
18488
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
18489
|
+
if (buf) {
|
|
18490
|
+
offset = offset || 0;
|
|
18491
|
+
if (offset < 0 || offset + 16 > buf.length) {
|
|
18492
|
+
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
18493
|
+
}
|
|
18494
|
+
for (let i = 0; i < 16; ++i) {
|
|
18495
|
+
buf[offset + i] = rnds[i];
|
|
18496
|
+
}
|
|
18497
|
+
return buf;
|
|
18498
|
+
}
|
|
18499
|
+
return unsafeStringify(rnds);
|
|
18500
|
+
}
|
|
18501
|
+
var v4_default = v4;
|
|
18502
|
+
|
|
18503
|
+
// src/lib/render-jobs.ts
|
|
18504
|
+
var jobs = /* @__PURE__ */ new Map();
|
|
18505
|
+
var MAX_AGE_MS = 30 * 60 * 1e3;
|
|
18506
|
+
var RENDER_TIMING = {
|
|
18507
|
+
expectedRange: "15-120 seconds",
|
|
18508
|
+
pollDelay: "~15 seconds"
|
|
18509
|
+
};
|
|
18510
|
+
function createJob(tool) {
|
|
18511
|
+
const job = {
|
|
18512
|
+
id: v4_default(),
|
|
18513
|
+
status: "rendering",
|
|
18514
|
+
tool,
|
|
18515
|
+
createdAt: Date.now()
|
|
18516
|
+
};
|
|
18517
|
+
jobs.set(job.id, job);
|
|
18518
|
+
return job;
|
|
18519
|
+
}
|
|
18520
|
+
function completeJob(id, result) {
|
|
18521
|
+
const job = jobs.get(id);
|
|
18522
|
+
if (job) {
|
|
18523
|
+
job.status = "complete";
|
|
18524
|
+
job.result = result;
|
|
18525
|
+
}
|
|
18526
|
+
}
|
|
18527
|
+
function failJob(id, error2) {
|
|
18528
|
+
const job = jobs.get(id);
|
|
18529
|
+
if (job) {
|
|
18530
|
+
job.status = "failed";
|
|
18531
|
+
job.error = error2;
|
|
18532
|
+
}
|
|
18533
|
+
}
|
|
18534
|
+
function getJob(id) {
|
|
18535
|
+
return jobs.get(id);
|
|
18536
|
+
}
|
|
18537
|
+
function pruneJobs() {
|
|
18538
|
+
const cutoff = Date.now() - MAX_AGE_MS;
|
|
18539
|
+
for (const [id, job] of jobs) {
|
|
18540
|
+
if (job.createdAt < cutoff) jobs.delete(id);
|
|
18541
|
+
}
|
|
18542
|
+
}
|
|
18543
|
+
|
|
18451
18544
|
// src/tools/generate-video.ts
|
|
18452
18545
|
function registerGenerateVideoTool(server) {
|
|
18453
18546
|
server.registerTool(
|
|
@@ -18456,7 +18549,9 @@ function registerGenerateVideoTool(server) {
|
|
|
18456
18549
|
title: "Generate Video",
|
|
18457
18550
|
description: `Generate a video from images, video clips, or both, synced to an audio track. Use this for narrated question backgrounds, topic visualisations, or any form node that benefits from video. Combine with clipform_generate_tts for narrated audio and clipform_search_media for royalty-free images. Creates 9:16 (720x1280) with Ken Burns pan/zoom effects and transitions. Returns a public URL when complete.
|
|
18458
18551
|
|
|
18459
|
-
Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by default). Duration matches audio_url or set duration_seconds explicitly
|
|
18552
|
+
Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by default). Duration matches audio_url or set duration_seconds explicitly.
|
|
18553
|
+
|
|
18554
|
+
For multi-question builds, pass wait: false on every render: each call returns a job ID immediately, so all renders run in parallel - then collect URLs with clipform_check_render. Sequential waiting renders take ${RENDER_TIMING.expectedRange} EACH.`,
|
|
18460
18555
|
inputSchema: {
|
|
18461
18556
|
items: external_exports.array(
|
|
18462
18557
|
external_exports.object({
|
|
@@ -18477,13 +18572,14 @@ Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by defau
|
|
|
18477
18572
|
duration: external_exports.number().optional().default(1).describe("Transition duration in seconds (default: 1)")
|
|
18478
18573
|
}).optional(),
|
|
18479
18574
|
style_preset: STYLE_PRESET_ENUM.optional(),
|
|
18480
|
-
background_color: external_exports.string().optional().describe("Background color (default '#000')")
|
|
18575
|
+
background_color: external_exports.string().optional().describe("Background color (default '#000')"),
|
|
18576
|
+
wait: external_exports.boolean().optional().default(true).describe("true (default) blocks until the video is ready and returns its URL. false returns a job ID immediately - fire all renders first, then poll clipform_check_render. Use false whenever rendering more than one video.")
|
|
18481
18577
|
},
|
|
18482
18578
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
18483
18579
|
},
|
|
18484
|
-
async ({ items, audio_url, duration_seconds, random_effects, transition, style_preset, background_color }) => {
|
|
18580
|
+
async ({ items, audio_url, duration_seconds, random_effects, transition, style_preset, background_color, wait }) => {
|
|
18485
18581
|
const workspace_id = getMcpAuth()?.workspace_id;
|
|
18486
|
-
const
|
|
18582
|
+
const apiCall = () => callApi("/internal/generate-video", {
|
|
18487
18583
|
timeoutMs: 3e5,
|
|
18488
18584
|
// local renders pay a cold webpack bundle; Lambda is fast but bursty
|
|
18489
18585
|
body: {
|
|
@@ -18497,6 +18593,21 @@ Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by defau
|
|
|
18497
18593
|
workspace_id
|
|
18498
18594
|
}
|
|
18499
18595
|
});
|
|
18596
|
+
if (wait === false) {
|
|
18597
|
+
const job = createJob("clipform_generate_video");
|
|
18598
|
+
apiCall().then((r) => {
|
|
18599
|
+
if (r.ok) completeJob(job.id, r.data);
|
|
18600
|
+
else failJob(job.id, r.error);
|
|
18601
|
+
}).catch((err) => failJob(job.id, err instanceof Error ? err.message : String(err)));
|
|
18602
|
+
return textResult(
|
|
18603
|
+
[
|
|
18604
|
+
`Render started (${items.length} item${items.length > 1 ? "s" : ""}).`,
|
|
18605
|
+
`Job ID: ${job.id}`,
|
|
18606
|
+
`Fire any remaining renders now, then poll clipform_check_render. Renders typically take ${RENDER_TIMING.expectedRange}.`
|
|
18607
|
+
].join("\n")
|
|
18608
|
+
);
|
|
18609
|
+
}
|
|
18610
|
+
const result = await apiCall();
|
|
18500
18611
|
if (!result.ok) return errorResult(result.error);
|
|
18501
18612
|
const data = result.data;
|
|
18502
18613
|
return textResult(
|
|
@@ -18510,23 +18621,6 @@ Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by defau
|
|
|
18510
18621
|
);
|
|
18511
18622
|
}
|
|
18512
18623
|
|
|
18513
|
-
// src/lib/render-jobs.ts
|
|
18514
|
-
var jobs = /* @__PURE__ */ new Map();
|
|
18515
|
-
var MAX_AGE_MS = 30 * 60 * 1e3;
|
|
18516
|
-
var RENDER_TIMING = {
|
|
18517
|
-
expectedRange: "15-120 seconds",
|
|
18518
|
-
pollDelay: "~15 seconds"
|
|
18519
|
-
};
|
|
18520
|
-
function getJob(id) {
|
|
18521
|
-
return jobs.get(id);
|
|
18522
|
-
}
|
|
18523
|
-
function pruneJobs() {
|
|
18524
|
-
const cutoff = Date.now() - MAX_AGE_MS;
|
|
18525
|
-
for (const [id, job] of jobs) {
|
|
18526
|
-
if (job.createdAt < cutoff) jobs.delete(id);
|
|
18527
|
-
}
|
|
18528
|
-
}
|
|
18529
|
-
|
|
18530
18624
|
// src/tools/check-render.ts
|
|
18531
18625
|
function registerCheckRenderTool(server) {
|
|
18532
18626
|
server.registerTool(
|
|
@@ -18879,4 +18973,4 @@ export {
|
|
|
18879
18973
|
JSONRPCMessageSchema,
|
|
18880
18974
|
createServer
|
|
18881
18975
|
};
|
|
18882
|
-
//# sourceMappingURL=chunk-
|
|
18976
|
+
//# sourceMappingURL=chunk-ZVHI2V7B.js.map
|